#!/usr/bin/env php
<?php
/**
 * Generated by Box.
 *
 * @link https://github.com/herrera-io/php-box/
 */
if (class_exists('Phar')) {
Phar::mapPhar('default.phar');
require 'phar://' . __FILE__ . '/cli';
}
__HALT_COMPILER(); ?>
GX x              .   vendor/myclabs/deep-copy/fixtures/f005/Foo.php”   MDÐ\”   yÃ¼¸¶      ,   vendor/myclabs/deep-copy/fixtures/f002/A.php³  MDÐ\³  ÓUö¶      .   vendor/myclabs/deep-copy/fixtures/f003/Foo.phpP  MDÐ\P  æYmf¶      9   vendor/myclabs/deep-copy/fixtures/f004/UnclonableItem.phpÁ   MDÐ\Á   BïÇD¶      ,   vendor/myclabs/deep-copy/fixtures/f008/B.php7   MDÐ\7   2Ež—¶      ,   vendor/myclabs/deep-copy/fixtures/f008/A.phpÒ   MDÐ\Ò   c¶      ,   vendor/myclabs/deep-copy/fixtures/f001/B.phpú   MDÐ\ú   =æE¶      ,   vendor/myclabs/deep-copy/fixtures/f001/A.phpð   MDÐ\ð   %pO¶      ,   vendor/myclabs/deep-copy/fixtures/f006/B.phpU  MDÐ\U  ñ®ª¶      ,   vendor/myclabs/deep-copy/fixtures/f006/A.phpU  MDÐ\U  äŒxš¶      :   vendor/myclabs/deep-copy/fixtures/f007/FooDateInterval.phpÈ   MDÐ\È   ÎÂ±c¶      :   vendor/myclabs/deep-copy/fixtures/f007/FooDateTimeZone.phpÈ   MDÐ\È   +Öï�¶      &   vendor/myclabs/deep-copy/composer.jsonL  MDÐ\L  �½L¶      3   vendor/myclabs/deep-copy/src/DeepCopy/deep_copy.phpŒ  MDÐ\Œ  "e¯¶      A   vendor/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyMatcher.php³  MDÐ\³  ?S¾b¶      O   vendor/myclabs/deep-copy/src/DeepCopy/Matcher/Doctrine/DoctrineProxyMatcher.phpo  MDÐ\o  ?3¶      9   vendor/myclabs/deep-copy/src/DeepCopy/Matcher/Matcher.phpÕ   MDÐ\Õ   qƒe¶      E   vendor/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyNameMatcher.phpø  MDÐ\ø  YŸóx¶      E   vendor/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyTypeMatcher.php&  MDÐ\&  Ã¶      2   vendor/myclabs/deep-copy/src/DeepCopy/DeepCopy.php…  MDÐ\…  rÖŽ¶      >   vendor/myclabs/deep-copy/src/DeepCopy/Filter/ReplaceFilter.php�  MDÐ\�  a…Ë²¶      ;   vendor/myclabs/deep-copy/src/DeepCopy/Filter/KeepFilter.php  MDÐ\  Åõü¶      W   vendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineEmptyCollectionFilter.phpÁ  MDÐ\Á  ¤ï7ß¶      R   vendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineCollectionFilter.php  MDÐ\  �:D¶      M   vendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineProxyFilter.php“  MDÐ\“  ùí¶      7   vendor/myclabs/deep-copy/src/DeepCopy/Filter/Filter.php\  MDÐ\\  6Sö“¶      >   vendor/myclabs/deep-copy/src/DeepCopy/Filter/SetNullFilter.phpâ  MDÐ\â  ]´®]¶      A   vendor/myclabs/deep-copy/src/DeepCopy/TypeMatcher/TypeMatcher.phpÔ  MDÐ\Ô  sÔwÖ¶      B   vendor/myclabs/deep-copy/src/DeepCopy/Exception/CloneException.php   MDÐ\   L¥¯t¶      E   vendor/myclabs/deep-copy/src/DeepCopy/Exception/PropertyException.phpx   MDÐ\x   í4º¶      E   vendor/myclabs/deep-copy/src/DeepCopy/Reflection/ReflectionHelper.php~  MDÐ\~  :”Ì¶      B   vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/ReplaceFilter.php  MDÐ\  7³„¶      R   vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/Spl/SplDoublyLinkedListFilter.php  MDÐ\  Jºéœ¶      L   vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/Spl/SplDoublyLinkedList.php°   MDÐ\°   †³Ì¶      L   vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/Date/DateIntervalFilter.php  MDÐ\  GFòÐ¶      F   vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/ShallowCopyFilter.phpß   MDÐ\ß   šÑZÙ¶      ?   vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/TypeFilter.phpÂ   MDÐ\Â   ý-h¶         vendor/autoload.php²   MDÐ\²   Û¸¶      .   vendor/php-amqplib/php-amqplib/spec/parser.php2  MDÐ\2  ƒjl§¶      :   vendor/php-amqplib/php-amqplib/spec/amqp-rabbitmq-0.8.json�—  MDÐ\�—  _xH¶      <   vendor/php-amqplib/php-amqplib/spec/amqp-rabbitmq-0.9.1.json k  MDÐ\ k  ü_y¶      G   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Exchange/AMQPExchangeType.php»   MDÐ\»   8»—u¶      >   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Wire/AMQPDecimal.php�  MDÐ\�  éˆªË¶      A   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Wire/AbstractClient.php±  MDÐ\±  ªæô¶      <   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Wire/AMQPTable.php¦  MDÐ\¦  «ÿ/¶      =   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Wire/AMQPReader.phpÃ<  MDÐ\Ã<  ¿gY¤¶      ?   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Wire/Constants080.php  MDÐ\  Ü?¾ç¶      >   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Wire/IO/StreamIO.php¦0  MDÐ\¦0  �ŽÏÔ¶      @   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Wire/IO/AbstractIO.phpY  MDÐ\Y  ~«hG¶      >   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Wire/IO/SocketIO.phpò#  MDÐ\ò#  OWÏR¶      ?   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Wire/Constants091.phpæ  MDÐ\æ  ’+Mž¶      I   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Wire/AMQPAbstractCollection.phpÁ1  MDÐ\Á1  ³«Ó¶      =   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Wire/AMQPWriter.php½8  MDÐ\½8  ™vT¶      A   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Wire/GenericContent.php{  MDÐ\{  y9L¶      <   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Wire/AMQPArray.phpN  MDÐ\N  æþ3¶      M   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AMQPSocketConnection.php%  MDÐ\%  ã;B²¶      J   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AMQPSSLConnection.phpŽ  MDÐ\Ž  ´‚.œ¶      K   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AMQPLazyConnection.phpQ  MDÐ\Q  eÎè ¶      Q   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AMQPLazySocketConnection.phpÙ  MDÐ\Ù  Á5u¶      K   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AbstractConnection.php†x  MDÐ\†x  �2|¶      G   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AMQPConnection.php¢   MDÐ\¢   R…Z²¶      M   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AMQPStreamConnection.phpN  MDÐ\N  G L¶      A   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Message/AMQPMessage.php€	  MDÐ\€	  #f{¶      A   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Channel/AMQPChannel.php-ª  MDÐ\-ª  é%Ž·¶      E   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Channel/AbstractChannel.php:@  MDÐ\:@  Y�~¡¶      ?   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Helper/MiscHelper.phpÄ  MDÐ\Ä  ®©A�¶      I   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Helper/Protocol/Protocol091.php•Z  MDÐ\•Z  šG$:¶      I   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Helper/Protocol/Protocol080.php«ƒ  MDÐ\«ƒ  0x£m¶      E   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Helper/Protocol/Wait091.php»
  MDÐ\»
  ’;oÉ¶      J   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Helper/Protocol/MethodMap091.php½  MDÐ\½  Ó[I¶      J   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Helper/Protocol/MethodMap080.phpŒ  MDÐ\Œ  ó3è\¶      E   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Helper/Protocol/Wait080.php®  MDÐ\®  ˆ%k
¶      @   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Helper/DebugHelper.phpÍ  MDÐ\Í  öƒS@¶      N   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPExceptionInterface.phpL   MDÐ\L   ;f[Ü¶      U   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPConnectionClosedException.phpÓ   MDÐ\Ó   ²™t&¶      P   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPBasicCancelException.phpG  MDÐ\G  ßÞ4w¶      E   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPException.phpØ  MDÐ\Ø  ŠDŠ¶      Q   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPInvalidFrameException.phph   MDÐ\h   r�&²¶      M   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPDataReadException.phpd   MDÐ\d   (iM¶      M   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPProtocolException.phpÊ  MDÐ\Ê  ‰ûí®¶      J   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPLogicException.php   MDÐ\   $dL¶      L   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPTimeoutException.php  MDÐ\  £Øð¶      T   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPProtocolChannelException.phpl   MDÐ\l   ØàÏ¯¶      W   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPProtocolConnectionException.phpo   MDÐ\o   žu#]¶      T   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPInvalidArgumentException.phpŠ   MDÐ\Š   kfm¶      K   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPIOWaitException.phpb   MDÐ\b   Öü®¶      L   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPRuntimeException.php‚   MDÐ\‚   �š¬)¶      P   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPOutOfBoundsException.phpŠ   MDÐ\Š   Í¿�,¶      R   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPChannelClosedException.phpi   MDÐ\i   Üõê¶      K   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPNoDataException.php¸   MDÐ\¸   o?k§¶      O   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPOutOfRangeException.php‰   MDÐ\‰   …™Y¶      S   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPNotImplementedException.phpj   MDÐ\j   /±¹ÿ¶      L   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPChannelException.phpœ   MDÐ\œ   ;Ü7×¶      O   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPConnectionException.php¢   MDÐ\¢   ¹!o
¶      G   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPIOException.phpv   MDÐ\v   —†^¶      T   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPHeartbeatMissedException.phpt   MDÐ\t   XáÇ¡¶      K   vendor/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPSocketException.phpb   MDÐ\b   ÔZ”Ü¶      ,   vendor/php-amqplib/php-amqplib/composer.jsont  MDÐ\t  Ñzü‡¶      3   vendor/ezyang/htmlpurifier/test-settings.sample.php’
  MDÐ\’
  JGJ¶      &   vendor/ezyang/htmlpurifier/package.phpâ  MDÐ\â  }±Úð¶      H   vendor/ezyang/htmlpurifier/plugins/phorum/settings/migrate-sigs-form.phpƒ  MDÐ\ƒ  ùc½(¶      ;   vendor/ezyang/htmlpurifier/plugins/phorum/settings/form.php)  MDÐ\)  –˜†c¶      C   vendor/ezyang/htmlpurifier/plugins/phorum/settings/migrate-sigs.php}  MDÐ\}  ‡§O…¶      ;   vendor/ezyang/htmlpurifier/plugins/phorum/settings/save.phpK  MDÐ\K  @RXN¶      6   vendor/ezyang/htmlpurifier/plugins/phorum/settings.php™  MDÐ\™  T}œ¶      :   vendor/ezyang/htmlpurifier/plugins/phorum/htmlpurifier.phpU/  MDÐ\U/  Åxõ¶      9   vendor/ezyang/htmlpurifier/plugins/phorum/init-config.php¨  MDÐ\¨  '$”´¶      <   vendor/ezyang/htmlpurifier/plugins/phorum/config.default.php)  MDÐ\)  ÝPáé¶      <   vendor/ezyang/htmlpurifier/plugins/phorum/migrate.bbcode.php)  MDÐ\)  ËõÛ?¶      +   vendor/ezyang/htmlpurifier/release2-tag.php  MDÐ\  œFß¶      3   vendor/ezyang/htmlpurifier/test-settings.travis.php!
  MDÐ\!
  ›P”Ë¶      8   vendor/ezyang/htmlpurifier/library/HTMLPurifier.auto.php  MDÐ\  �aÙŠ¶      3   vendor/ezyang/htmlpurifier/library/HTMLPurifier.php4'  MDÐ\4'  jº�¶      <   vendor/ezyang/htmlpurifier/library/HTMLPurifier.autoload.php'  MDÐ\'  ïå¶      8   vendor/ezyang/htmlpurifier/library/HTMLPurifier.kses.php›  MDÐ\›  ÍK·#¶      A   vendor/ezyang/htmlpurifier/library/HTMLPurifier.safe-includes.phpÃ4  MDÐ\Ã4  ³á˜z¶      8   vendor/ezyang/htmlpurifier/library/HTMLPurifier.path.phpë   MDÐ\ë   úˆ\F¶      C   vendor/ezyang/htmlpurifier/library/HTMLPurifier.autoload-legacy.php  MDÐ\  Ðhý8¶      <   vendor/ezyang/htmlpurifier/library/HTMLPurifier.includes.php)  MDÐ\)  ÷&Hí¶      <   vendor/ezyang/htmlpurifier/library/HTMLPurifier.composer.phpe   MDÐ\e   ¹ÿŸ¶      ?   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Empty.phpø   MDÐ\ø   ïä[K¶      =   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Tag.phpà  MDÐ\à   ÉH¶      =   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/End.phpL  MDÐ\L  Œ¦Ý:¶      A   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Comment.php÷  MDÐ\÷  S»\¯¶      >   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Text.php-  MDÐ\-  åG­j¶      ?   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Start.php‡   MDÐ\‡   tdŽ/¶      <   vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef.php  MDÐ\  ª‹Îð¶      =   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTypes.phpT  MDÐ\T  ³šöp¶      =   vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme.php™  MDÐ\™  šçÃŽ¶      A   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform.phpÅ  MDÐ\Å  $<4¶      ?   vendor/ezyang/htmlpurifier/library/HTMLPurifier/ErrorStruct.phpe  MDÐ\e  �%f¶      N   vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer.php‚$  MDÐ\‚$  ²ŒÊ¶      U   vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php£  MDÐ\£  Ú¢ÿ¶      T   vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Memory.php
  MDÐ\
  2�´}¶      H   vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Null.phpH  MDÐ\H  Â8’¶      M   vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator.phpK	  MDÐ\K	  ÷áL¶      9   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token.php±  MDÐ\±  -C¶      ;   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Encoder.php/d  MDÐ\/d  U-Ï¶      H   vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyListIterator.phpa  MDÐ\a  È�¶¶      F   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Lang.phpX  MDÐ\X  ³†Âœ¶      H   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Border.php¤  MDÐ\¤  dEÅŽ¶      L   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeObject.phpa  MDÐ\a  Œ>^•¶      J   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgSpace.phpv  MDÐ\v  á°#¶      P   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ScriptRequired.php  MDÐ\  F]±¶      J   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Nofollow.php  MDÐ\  dÚÓs¶      K   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeParam.phpé	  MDÐ\é	  £çš¶      K   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/EnumToCSS.php½  MDÐ\½  I�•¶      L   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Background.php¸  MDÐ\¸  
ïëT¶      I   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BgColor.php   MDÐ\   Æ )A¶      H   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BdoDir.php  MDÐ\  rd;(¶      P   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetNoopener.phpþ  MDÐ\þ  µÌÕs¶      J   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/NameSync.php  MDÐ\  …J�¶      M   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgRequired.php3  MDÐ\3  óÝgŒ¶      K   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BoolToCSS.php?  MDÐ\?  $¿2¶      G   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Input.php@  MDÐ\@  Á´òU¶      J   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Textarea.phpW  MDÐ\W  >ù¨¶      H   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Length.phpØ  MDÐ\Ø  ž²³ô¶      R   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetNoreferrer.php  MDÐ\  Ù½žX¶      F   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Name.php(  MDÐ\(  d´Ø±¶      M   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetBlank.phpD  MDÐ\D  SI£¶      K   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeEmbed.php:  MDÐ\:  '«ªm¶      R   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/XMLCommonAttributes.php`  MDÐ\`  *ç·¶      I   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeObject.php›  MDÐ\›  ÿÓ„’¶      G   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Nofollow.phpû  MDÐ\û  6¸ŠH¶      C   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Ruby.php  MDÐ\  }ª9F¶      C   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy.phpÔ  MDÐ\Ô  eD>Ô¶      K   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Presentation.php‡  MDÐ\‡  Š(ž¶      U   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.phpb  MDÐ\b  ºå¼í¶      H   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Hypertext.phpä  MDÐ\ä  ö’ ¶      E   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Target.phpW  MDÐ\W  i'(ù¶      C   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/List.phpu  MDÐ\u  ÓÆ\¶      E   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Object.phpó  MDÐ\ó  cÊñ!¶      O   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/CommonAttributes.phpž  MDÐ\ž  øáÜH¶      B   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Bdo.php  MDÐ\  `)ÿ¶      Q   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.phpœ  MDÐ\œ  R í¶      P   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Transitional.php  MDÐ\  ðòÓò¶      J   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Strict.phpŠ  MDÐ\Š  sò	8¶      I   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/XHTML.php·  MDÐ\·  ž]ÂÏ¶      H   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Name.phpõ  MDÐ\õ  1Äì¶¶      O   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Proprietary.phpú  MDÐ\ú  Q:t¶      M   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetNoopener.php  MDÐ\  Œ¾è³¶      E   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tables.php0	  MDÐ\0	  6<À	¶      M   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/StyleAttribute.php  MDÐ\  IáåÓ¶      C   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Edit.phpš  MDÐ\š  9g;¶      O   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetNoreferrer.php  MDÐ\  ûñÉ=¶      C   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Name.php�  MDÐ\�  D9Ån¶      L   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeScripting.php_  MDÐ\_  É–C¶      H   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Scripting.php!	  MDÐ\!	  ="¦ã¶      J   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetBlank.php
  MDÐ\
  —ü¶      H   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeEmbed.phpI  MDÐ\I  ÞdV¶      E   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Iframe.php™  MDÐ\™  ér¶      C   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Text.phpk  MDÐ\k  9›þ¶      D   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Image.phpl  MDÐ\l  4\�^¶      D   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Forms.phpw  MDÐ\w  "¸t¶      E   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Legacy.phpß  MDÐ\ß  %f59¶      J   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Proprietary.phpã  MDÐ\ã  BDÜ0¶      A   vendor/ezyang/htmlpurifier/library/HTMLPurifier/CSSDefinition.phpuC  MDÐ\uC  ÷“=;¶      C   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DirectLex.phpP  MDÐ\P  ÿÞ&¶      @   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DOMLex.php¬/  MDÐ\¬/  ³È¾¶      >   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/PH5P.php5É MDÐ\5É ù‚8š¶      B   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLDefinition.phpSE  MDÐ\SE  ±¼ex¶      <   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy.phpú  MDÐ\ú  ó�IÎ¶      D   vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.phpŽ  MDÐ\Ž  ¥>¨¨¶      F   vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php¼  MDÐ\¼  RX¾d¶      C   vendor/ezyang/htmlpurifier/library/HTMLPurifier/LanguageFactory.phpÚ  MDÐ\Ú  LBËe¶      =   vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter.php=	  MDÐ\=	  &¾]u¶      E   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModuleManager.phpJ>  MDÐ\J>  48Y ¶      I   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/CSSDefinition.phpª  MDÐ\ª  ~5õè¶      J   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/HTMLDefinition.phpû(  MDÐ\û(  —zØ}¶      F   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.phpß9  MDÐ\ß9  �®]ü¶      @   vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform.phpJ  MDÐ\J  {±ê†¶      <   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Arborize.phpö	  MDÐ\ö	  Ð­Z¶¶      E   vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Font.php+  MDÐ\+  íÕA"¶      G   vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Simple.php–  MDÐ\–  ö!èµ¶      A   vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIDefinition.phpf  MDÐ\f  œñ5û¶      C   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrCollections.phpþ  MDÐ\þ  •0­ƒ¶      A   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrValidator.php¬  MDÐ\¬  ŒJˆ¶      =   vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser.phpF  MDÐ\F  äÝ Ù¶      O   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en-x-test.php«   MDÐ\«   §S3Ú¶      H   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en.phpÐ  MDÐ\Ð  Â"7t¶      S   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en-x-testmini.phpå   MDÐ\å   ¡ 7Ã¶      N   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/classes/en-x-test.php‰   MDÐ\‰   B¾ ¶      >   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Definition.phpQ  MDÐ\Q  ±&(þ¶      D   vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHashParser.php9  MDÐ\9   -#¶      =   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Bootstrap.php  MDÐ\  ÷¸ï¶      :   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Config.phpb{  MDÐ\b{  SÚ”â¶      @   vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyList.phpß
  MDÐ\ß
  O¦Š¶      9   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Queue.php  MDÐ\  ¯R9H¶      :   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Zipper.phpZ  MDÐ\Z  �ü#¶      H   vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/SafeIframe.php²  MDÐ\²  zá²¶      C   vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/Munge.phpC  MDÐ\C  d½ós¶      K   vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/HostBlacklist.php€  MDÐ\€  nr?¶      N   vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableResources.phpÎ  MDÐ\Î  �ë¦¼¶      J   vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/MakeAbsolute.phpa  MDÐ\a  $’Ý¶      V   vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternalResources.phpH  MDÐ\H  ãGúš¶      M   vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternal.php  MDÐ\  £ ¦³¶      S   vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.phpË  MDÐ\Ë  J¥	×¶      J   vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Validator.php¯   MDÐ\¯   ÓôØ¶      O   vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Id.php1  MDÐ\1  k¶      V   vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Directive.php±  MDÐ\±  éPÌ›¶      N   vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php$  MDÐ\$  9…è»¶      L   vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/Xml.php  MDÐ\  Ãª°¶      U   vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.phpý  MDÐ\ý  HÙûõ¶      J   vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Exception.php¢   MDÐ\¢   Id…ƒ¶      L   vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange.php  MDÐ\  …^ÕŽ¶      @   vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema.php  MDÐ\  74Á¶      8   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node.php   MDÐ\   „ ;’¶      J   vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCacheFactory.php�  MDÐ\�  F’¶      ;   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Context.phpJ
  MDÐ\J
  ¬ûŠ³¶      B   vendor/ezyang/htmlpurifier/library/HTMLPurifier/ErrorCollector.phpÉ  MDÐ\É  AiÈ»¶      C   vendor/ezyang/htmlpurifier/library/HTMLPurifier/DoctypeRegistry.php€  MDÐ\€  Åa£¶      >   vendor/ezyang/htmlpurifier/library/HTMLPurifier/ElementDef.phpc  MDÐ\c  ¾nœ;¶      B   vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/news.php¾  MDÐ\¾  h
µÕ¶      A   vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/tel.php˜  MDÐ\˜  &âtX¶      B   vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/file.phpü  MDÐ\ü  ¸EP¯¶      D   vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/mailto.php}  MDÐ\}  ~
~™¶      B   vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/http.php�  MDÐ\�  õ4Øò¶      B   vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/nntp.phpt  MDÐ\t  <•¶      C   vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/https.php*  MDÐ\*  •ße¶      B   vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/data.php  MDÐ\  Ô"×¶      A   vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/ftp.phpn  MDÐ\n  )(9l¶      :   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter.phpY  MDÐ\Y  ®z„¶      @   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Lang.php„	  MDÐ\„	  ð´¶      C   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Integer.phpó	  MDÐ\ó	  y§-A¶      F   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Border.php7  MDÐ\7  D”8¶      F   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Number.phpD  MDÐ\D  ÆKY¶      J   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Percentage.phpÿ  MDÐ\ÿ  ™Îëœ¶      N   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/TextDecoration.php„  MDÐ\„  w¹8ó¶      R   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/BackgroundPosition.phpF  MDÐ\F  ¶<Í«¶      I   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ListStyle.php_  MDÐ\_  ^€Ô¶      I   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Composite.php4  MDÐ\4  òì¶      J   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Background.phpˆ  MDÐ\ˆ  ø+^¯¶      D   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Font.phpÑ  MDÐ\Ñ  Ôbäò¶      H   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Multiple.php,  MDÐ\,  –“é¶      T   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php3  MDÐ\3  Ðˆ¡~¶      E   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Color.phpH  MDÐ\H  ìkþ¶      J   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/AlphaValue.php  MDÐ\  ô,Fê¶      E   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Ident.phpÔ  MDÐ\Ô  J£µ¶      F   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Filter.php	  MDÐ\	  ¥mn�¶      C   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/URI.php
  MDÐ\
  £LÓ�¶      R   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php=  MDÐ\=  A¢o¶      J   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/FontFamily.phpÁ$  MDÐ\Á$  ‰¡Ír¶      F   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Length.phpi  MDÐ\i  3§¶      @   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Enum.php£  MDÐ\£  Á…)¶      F   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Class.phpÍ  MDÐ\Í  Aø¶      E   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Bool.php`  MDÐ\`  *BíŸ¶      L   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/FrameTarget.phpB  MDÐ\B  piâi¶      G   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Pixels.php¼  MDÐ\¼  †Aÿ¶      C   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/ID.php„  MDÐ\„  ]È¶      F   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Color.php«  MDÐ\«  Qì2;¶      J   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/LinkTypes.phpè  MDÐ\è  ¿êÝ¶      I   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Nmtokens.phpa  MDÐ\a  %¤Aì¶      L   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/MultiLength.php4  MDÐ\4  îM¶I¶      G   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Length.phpâ  MDÐ\â  Z›×Ù¶      A   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Clone.phph  MDÐ\h  ´ò_ã¶      B   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Switch.php	  MDÐ\	  ¼…V~¶      ?   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI.php˜
  MDÐ\˜
  ºØ¶      ?   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS.phpó  MDÐ\ó  ˜Ù)¶      E   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email.phpW  MDÐ\W  xÇÙº¶      D   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv6.php­	  MDÐ\­	  Ñí›`¶      D   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Host.php`  MDÐ\`  yÍ?¶      D   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv4.phpæ  MDÐ\æ  c)œÁ¶      Q   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php8  MDÐ\8  wlL¶      @   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Text.php®  MDÐ\®  ñ¯yx¶      9   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer.phpz4  MDÐ\z4  Ïõ¶      7   vendor/ezyang/htmlpurifier/library/HTMLPurifier/URI.phpd)  MDÐ\d)  Lm,Ü¶      <   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language.php®  MDÐ\®  Ö8@¯¶      A   vendor/ezyang/htmlpurifier/library/HTMLPurifier/IDAccumulator.phpo  MDÐ\o  ;w/×¶      ?   vendor/ezyang/htmlpurifier/library/HTMLPurifier/ContentSets.php  MDÐ\  õ�ó>¶      >   vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHash.php.  MDÐ\.  ºC­½¶      >   vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule.phpŸ'  MDÐ\Ÿ'  ,§Ë¶      @   vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup.php’  MDÐ\’  RÛ)£¶      @   vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityParser.phpá&  MDÐ\á&  è²âx¶      F   vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParserException.php�   MDÐ\�   6_,¶      B   vendor/ezyang/htmlpurifier/library/HTMLPurifier/PercentEncoder.phpí  MDÐ\í  V¸{o¶      :   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Length.php  MDÐ\  Ž½D¶      @   vendor/ezyang/htmlpurifier/library/HTMLPurifier/TokenFactory.php  MDÐ\  ¯å¶      B   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/YouTube.php*  MDÐ\*  ñ�5–¶      M   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/ExtractStyleBlocks.php$5  MDÐ\$5  ò–y¶      @   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Element.php½  MDÐ\½  gÆÉ‘¶      @   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Comment.phpÕ  MDÐ\Õ  ªÇ´ö¶      =   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Text.phpd  MDÐ\d  Í®Šh¶      =   vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIParser.phpö  MDÐ\ö  …9ÛÌ¶      =   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Generator.php(  MDÐ\(  &8ƒç¶      A   vendor/ezyang/htmlpurifier/library/HTMLPurifier/UnitConverter.php’'  MDÐ\’'  úÃÿ¶      =   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Exception.php±   MDÐ\±   	¿$¶      C   vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache.phpI  MDÐ\I  �Ž¤q¶      ;   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer.php	  MDÐ\	  %ÁO¶      E   vendor/ezyang/htmlpurifier/library/HTMLPurifier/URISchemeRegistry.phpi	  MDÐ\i	  ·ì7Ô¶      ;   vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef.phpK  MDÐ\K  ·f¶      <   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector.php.#  MDÐ\.#  ÔÑÂ	¶      H   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveEmpty.php´  MDÐ\´  ¤
¶      G   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/SafeObject.phpo  MDÐ\o  %5>ƒ¶      Y   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.phpæ  MDÐ\æ  ÷Š<+¶      L   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/PurifierLinkify.php  MDÐ\  q“ÿ¶      J   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/AutoParagraph.phpä5  MDÐ\ä5  0šŸ‚¶      D   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/Linkify.phpë  MDÐ\ë  6Ô¶      K   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/DisplayLinkURI.php[  MDÐ\[  —âõ¶      F   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Composite.php×  MDÐ\×  ÄzÖ¶      K   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/MakeWellFormed.phpák  MDÐ\ák  Ó—æ¶      R   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/RemoveForeignElements.phpÒ#  MDÐ\Ò#  {0K_¶      O   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/ValidateAttributes.phpÓ  MDÐ\Ó  ÛÖ{¶      G   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/FixNesting.php…  MDÐ\…  ;ý6:¶      A   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Core.php  MDÐ\  0Šû&¶      ;   vendor/ezyang/htmlpurifier/library/HTMLPurifier/Doctype.php.  MDÐ\.  k¦Ö[¶      C   vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Custom.php­
  MDÐ\­
  /jk¶      B   vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Empty.phpb  MDÐ\b  +kÄ®¶      A   vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/List.phpÅ  MDÐ\Å  ŠÊÊZ¶      M   vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/StrictBlockquote.phpb  MDÐ\b  Ê?�d¶      E   vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Optional.php¹  MDÐ\¹  Å)¬¶      F   vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Chameleon.phpj  MDÐ\j  QÙMü¶      E   vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Required.php  MDÐ\  £êæ¶      B   vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Table.phpØ  MDÐ\Ø  ·¦#ý¶      8   vendor/ezyang/htmlpurifier/library/HTMLPurifier.func.php@  MDÐ\@  µfÑò¶      A   vendor/ezyang/htmlpurifier/maintenance/flush-definition-cache.php7  MDÐ\7  ÏŒi¶      >   vendor/ezyang/htmlpurifier/maintenance/generate-standalone.php  MDÐ\  Ìð1M¶      8   vendor/ezyang/htmlpurifier/maintenance/update-config.phpÌ  MDÐ\Ì  9¯s¶      6   vendor/ezyang/htmlpurifier/maintenance/add-vimline.php¯  MDÐ\¯  ]¾ÿû¶      @   vendor/ezyang/htmlpurifier/maintenance/old-remove-schema-def.php0  MDÐ\0  A¬ï?¶      ?   vendor/ezyang/htmlpurifier/maintenance/generate-entity-file.php¦  MDÐ\¦  Ú%Œ¶      9   vendor/ezyang/htmlpurifier/maintenance/config-scanner.php·  MDÐ\·  l™*¶      =   vendor/ezyang/htmlpurifier/maintenance/old-extract-schema.phpó  MDÐ\ó  ©
.ú¶      8   vendor/ezyang/htmlpurifier/maintenance/rename-config.php  MDÐ\  ¨d–‰¶      <   vendor/ezyang/htmlpurifier/maintenance/generate-includes.php
  MDÐ\
  P»«R¶      >   vendor/ezyang/htmlpurifier/maintenance/generate-ph5p-patch.phpí  MDÐ\í  KÚÓÿ¶      0   vendor/ezyang/htmlpurifier/maintenance/flush.phpg  MDÐ\g  £Gá¶      E   vendor/ezyang/htmlpurifier/maintenance/remove-trailing-whitespace.php¯  MDÐ\¯  Ï•¶      8   vendor/ezyang/htmlpurifier/maintenance/merge-library.phpª   MDÐ\ª   f}ûÄ¶      1   vendor/ezyang/htmlpurifier/maintenance/common.phpw  MDÐ\w  ÍDª¶      @   vendor/ezyang/htmlpurifier/maintenance/generate-schema-cache.phpT  MDÐ\T  u¥´ž¶      /   vendor/ezyang/htmlpurifier/maintenance/PH5P.php�w MDÐ\�w 
¶      B   vendor/ezyang/htmlpurifier/maintenance/old-remove-require-once.phpì  MDÐ\ì  ·(ùË¶      (   vendor/ezyang/htmlpurifier/composer.json€  MDÐ\€  Ñ™ë¶      .   vendor/ezyang/htmlpurifier/release1-update.phpn
  MDÐ\n
  ]W	)¶      8   vendor/ezyang/htmlpurifier/extras/HTMLPurifierExtras.php¬  MDÐ\¬  ­†îS¶      -   vendor/ezyang/htmlpurifier/extras/FSTools.php<  MDÐ\<  ú3�¶      A   vendor/ezyang/htmlpurifier/extras/HTMLPurifierExtras.autoload.phpÌ  MDÐ\Ì  /^¼ì¶      H   vendor/ezyang/htmlpurifier/extras/HTMLPurifierExtras.autoload-legacy.php  MDÐ\  »£·(¶      2   vendor/ezyang/htmlpurifier/extras/FSTools/File.php;  MDÐ\;  Õó$°¶      =   vendor/ezyang/htmlpurifier/extras/HTMLPurifierExtras.auto.php  MDÐ\  G•ÇM¶      A   vendor/ezyang/htmlpurifier/extras/ConfigDoc/HTMLXSLTProcessor.phpf	  MDÐ\f	  _‹·–¶      '   vendor/guzzlehttp/streams/composer.json£  MDÐ\£  šÐW¶      9   vendor/guzzlehttp/streams/src/MetadataStreamInterface.php  MDÐ\  ¹ç¥Í¶      *   vendor/guzzlehttp/streams/src/FnStream.phpE  MDÐ\E  p:.J¶      ,   vendor/guzzlehttp/streams/src/NullStream.php¡  MDÐ\¡  çH´1¶      1   vendor/guzzlehttp/streams/src/StreamInterface.phpÈ  MDÐ\È  íÅìÄ¶      /   vendor/guzzlehttp/streams/src/CachingStream.phpÙ  MDÐ\Ù  ì)ò‡¶      ,   vendor/guzzlehttp/streams/src/PumpStream.phpF  MDÐ\F  ú\‚¶      .   vendor/guzzlehttp/streams/src/NoSeekStream.php¯  MDÐ\¯  nØÑã¶      /   vendor/guzzlehttp/streams/src/InflateStream.php®  MDÐ\®  u²ä¶      5   vendor/guzzlehttp/streams/src/GuzzleStreamWrapper.phpÈ
  MDÐ\È
  ¹,¦¼¶      (   vendor/guzzlehttp/streams/src/Stream.php?  MDÐ\?  OÎy¶      1   vendor/guzzlehttp/streams/src/AsyncReadStream.php–  MDÐ\–  2@7í¶      0   vendor/guzzlehttp/streams/src/DroppingStream.php+  MDÐ\+  ™ø´]¶      0   vendor/guzzlehttp/streams/src/LazyOpenStream.phpR  MDÐ\R  ¼ó*ë¶      .   vendor/guzzlehttp/streams/src/BufferStream.php˜  MDÐ\˜  }X¶N¶      6   vendor/guzzlehttp/streams/src/StreamDecoratorTrait.phpŒ  MDÐ\Œ  ÁÁß‹¶      '   vendor/guzzlehttp/streams/src/Utils.php¨  MDÐ\¨   J„º¶      .   vendor/guzzlehttp/streams/src/AppendStream.php/  MDÐ\/  coü¶      -   vendor/guzzlehttp/streams/src/LimitStream.phpf  MDÐ\f  wD3¶      9   vendor/guzzlehttp/streams/src/Exception/SeekException.php9  MDÐ\9  kŸñ¶      A   vendor/guzzlehttp/streams/src/Exception/CannotAttachException.phpg   MDÐ\g   Ý1<Œ¶      '   vendor/guzzlehttp/ringphp/composer.json&  MDÐ\&  )6Ï¶      <   vendor/guzzlehttp/ringphp/src/Exception/ConnectException.phpŠ   MDÐ\Š   ë¸;J¶      >   vendor/guzzlehttp/ringphp/src/Exception/CancelledException.php‚   MDÐ\‚   êJ¶      J   vendor/guzzlehttp/ringphp/src/Exception/CancelledFutureAccessException.phpˆ   MDÐ\ˆ   ªkÃR¶      9   vendor/guzzlehttp/ringphp/src/Exception/RingException.php^   MDÐ\^   2Þøx¶      4   vendor/guzzlehttp/ringphp/src/Future/FutureArray.phpþ  MDÐ\þ  —¾w’¶      4   vendor/guzzlehttp/ringphp/src/Future/FutureValue.php&  MDÐ\&  I:Ú¶      8   vendor/guzzlehttp/ringphp/src/Future/FutureInterface.php©  MDÐ\©  ìp¼Y¶      8   vendor/guzzlehttp/ringphp/src/Future/BaseFutureTrait.phpÜ  MDÐ\Ü  Ï?8¶      =   vendor/guzzlehttp/ringphp/src/Future/FutureArrayInterface.phpÕ   MDÐ\Õ   œ«ó¶      =   vendor/guzzlehttp/ringphp/src/Future/CompletedFutureValue.phpa  MDÐ\a  ‹Œ‹~¶      =   vendor/guzzlehttp/ringphp/src/Future/CompletedFutureArray.phpb  MDÐ\b  Úý'.¶      9   vendor/guzzlehttp/ringphp/src/Future/MagicFutureTrait.php‹  MDÐ\‹  UYyµ¶      4   vendor/guzzlehttp/ringphp/src/Client/CurlFactory.phpæM  MDÐ\æM  Ù:‘²¶      9   vendor/guzzlehttp/ringphp/src/Client/CurlMultiHandler.php…  MDÐ\…  é»jŠ¶      4   vendor/guzzlehttp/ringphp/src/Client/ClientUtils.phpÐ
  MDÐ\Ð
  c %¶      4   vendor/guzzlehttp/ringphp/src/Client/CurlHandler.phpã  MDÐ\ã  S´”¶      4   vendor/guzzlehttp/ringphp/src/Client/MockHandler.php  MDÐ\  qŠ¬"¶      6   vendor/guzzlehttp/ringphp/src/Client/StreamHandler.php83  MDÐ\83  ¤8¶      3   vendor/guzzlehttp/ringphp/src/Client/Middleware.php‹  MDÐ\‹  ÑÜ¶      &   vendor/guzzlehttp/ringphp/src/Core.php+  MDÐ\+  ‹ÖY¶      '   vendor/composer/autoload_namespaces.php   MDÐ\   kÕâw¶         vendor/composer/ClassLoader.php“4  MDÐ\“4  ²z�–¶      !   vendor/composer/autoload_psr4.phpò  MDÐ\ò  ÄJ›Å¶      %   vendor/composer/autoload_classmap.php) MDÐ\) 0<Žû¶      #   vendor/composer/autoload_static.phpù~ MDÐ\ù~ (1G¶      !   vendor/composer/autoload_real.phpn	  MDÐ\n	  Q§±V¶         vendor/composer/installed.json±? MDÐ\±? ’Ât ¶      "   vendor/composer/autoload_files.php�  MDÐ\�  €Ô”¶      4   vendor/phpdocumentor/reflection-common/composer.jsonÛ  MDÐ\Û  lÅ:#¶      6   vendor/phpdocumentor/reflection-common/src/Element.php1  MDÐ\1  …iUÒ¶      =   vendor/phpdocumentor/reflection-common/src/ProjectFactory.php  MDÐ\  Q³"Ü¶      3   vendor/phpdocumentor/reflection-common/src/File.php7  MDÐ\7  ã©3"¶      6   vendor/phpdocumentor/reflection-common/src/Project.php  MDÐ\  /H� ¶      4   vendor/phpdocumentor/reflection-common/src/Fqsen.php  MDÐ\  ûôÓœ¶      7   vendor/phpdocumentor/reflection-common/src/Location.phpH  MDÐ\H  ?-ÿ¶      0   vendor/phpdocumentor/type-resolver/composer.json¡  MDÐ\¡  �¹X¶      7   vendor/phpdocumentor/type-resolver/src/TypeResolver.phpw$  MDÐ\w$  U°ºy¶      8   vendor/phpdocumentor/type-resolver/src/Types/Boolean.phpÄ  MDÐ\Ä  Ífÿ¶      8   vendor/phpdocumentor/type-resolver/src/Types/Integer.phpŽ  MDÐ\Ž  "s‰¶      6   vendor/phpdocumentor/type-resolver/src/Types/Self_.php  MDÐ\  øñ9'¶      6   vendor/phpdocumentor/type-resolver/src/Types/Void_.phpW  MDÐ\W  Öé¿Ö¶      6   vendor/phpdocumentor/type-resolver/src/Types/Null_.phpÈ  MDÐ\È  @¥%²¶      :   vendor/phpdocumentor/type-resolver/src/Types/Resource_.phpÏ  MDÐ\Ï  Nf­¶      7   vendor/phpdocumentor/type-resolver/src/Types/Float_.php½  MDÐ\½  Œw,¶      8   vendor/phpdocumentor/type-resolver/src/Types/Object_.php¾  MDÐ\¾  ÉðQ™¶      8   vendor/phpdocumentor/type-resolver/src/Types/String_.phpÉ  MDÐ\É  ¢½ï¶      9   vendor/phpdocumentor/type-resolver/src/Types/Nullable.phpÉ  MDÐ\É  öAbš¶      :   vendor/phpdocumentor/type-resolver/src/Types/Callable_.phpË  MDÐ\Ë  4É¿¶      :   vendor/phpdocumentor/type-resolver/src/Types/Iterable_.phpÈ  MDÐ\È  O¼ÉÓ¶      8   vendor/phpdocumentor/type-resolver/src/Types/Static_.phpU  MDÐ\U  ´ÞŸ¶      8   vendor/phpdocumentor/type-resolver/src/Types/Parent_.php4  MDÐ\4  �­j¶      7   vendor/phpdocumentor/type-resolver/src/Types/Scalar.php  MDÐ\  Uô“»¶      8   vendor/phpdocumentor/type-resolver/src/Types/Context.phpV  MDÐ\V  ž{ö¶      ?   vendor/phpdocumentor/type-resolver/src/Types/ContextFactory.phpî  MDÐ\î  Eì3ã¶      9   vendor/phpdocumentor/type-resolver/src/Types/Compound.php	  MDÐ\	  ¸“Ù|¶      5   vendor/phpdocumentor/type-resolver/src/Types/This.php©  MDÐ\©  ñhÂ²¶      7   vendor/phpdocumentor/type-resolver/src/Types/Array_.php-  MDÐ\-  æÊ_¶      7   vendor/phpdocumentor/type-resolver/src/Types/Mixed_.phpÐ  MDÐ\Ð  Ï¯ùÊ¶      /   vendor/phpdocumentor/type-resolver/src/Type.php±  MDÐ\±  ¯Ú[L¶      8   vendor/phpdocumentor/type-resolver/src/FqsenResolver.php	  MDÐ\	  ÅR‘Ù¶      6   vendor/phpdocumentor/reflection-docblock/composer.jsonß  MDÐ\ß  jñ�é¶      @   vendor/phpdocumentor/reflection-docblock/src/DocBlockFactory.php†$  MDÐ\†$  •Åƒ¦¶      D   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.phpþ  MDÐ\þ  "~.0¶      G   vendor/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.phpÍ  MDÐ\Í  ¬x…¶      =   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tag.phpu  MDÐ\u  â¹°¶      D   vendor/phpdocumentor/reflection-docblock/src/DocBlock/TagFactory.php  MDÐ\  P;Í¶      L   vendor/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.phpp  MDÐ\p  3çÌ{¶      D   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.phpÿ	  MDÐ\ÿ	  ¼yaÃ¶      L   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.phpå  MDÐ\å  s�ãÈ¶      C   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.phpN  MDÐ\N  VŒŠ¶      F   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php¿  MDÐ\¿  X�
c¶      C   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.php  MDÐ\  ¦ßÏ¶      D   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.php|  MDÐ\|  î°‘¶      E   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php¶  MDÐ\¶  ~Ý|õ¶      H   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter.php£  MDÐ\£  Dy7¶      C   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.phpP  MDÐ\P  T,¾¶      F   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php€  MDÐ\€  Í@‘¶      ]   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.php+  MDÐ\+  ìúÜ¶      W   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/AlignFormatter.phpú  MDÐ\ú  çû½©¶      K   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.phpâ  MDÐ\â  ë	2ô¶      G   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.phpÔ  MDÐ\Ô  ]]Hš¶      F   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.phpÄ	  MDÐ\Ä	  õ\ð¶      B   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.phpÄ	  MDÐ\Ä	  y%“¶      F   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.php_
  MDÐ\_
  î(oÙ¶      E   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.phpn  MDÐ\n  Ð¡ô¬¶      E   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.phpÝ	  MDÐ\Ý	  �ÅÍó¶      E   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php�  MDÐ\�  èÈˆØ¶      O   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/Strategy.phpÌ  MDÐ\Ì  ðÞR¶      S   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/StaticMethod.php×  MDÐ\×  ³2i¼¶      R   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Reference.php,  MDÐ\,  Å8(¿¶      L   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Url.phpM  MDÐ\M  �ž“|¶      N   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Fqsen.php´  MDÐ\´  ™�c¶      F   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.phpp  MDÐ\p  ßHˆ�¶      I   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php—
  MDÐ\—
  áƒ¶      E   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.phpF  MDÐ\F  .hÂ)¶      L   vendor/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.phpR.  MDÐ\R.  Ã˜¶¶      E   vendor/phpdocumentor/reflection-docblock/src/DocBlock/Description.php/  MDÐ\/  >÷	¶      I   vendor/phpdocumentor/reflection-docblock/src/DocBlockFactoryInterface.php!  MDÐ\!  Ø¦}¶      9   vendor/phpdocumentor/reflection-docblock/src/DocBlock.php  MDÐ\  ­´Z¶      *   vendor/doctrine/instantiator/phpbench.jsone   MDÐ\e   ”B\ã¶      *   vendor/doctrine/instantiator/composer.json   MDÐ\   ^ ¨¶      G   vendor/doctrine/instantiator/src/Doctrine/Instantiator/Instantiator.php„  MDÐ\„  €Ë›¶      P   vendor/doctrine/instantiator/src/Doctrine/Instantiator/InstantiatorInterface.php�  MDÐ\�  ˆÊ]D¶      W   vendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/ExceptionInterface.phpÀ   MDÐ\À   ãfš¶      ]   vendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/UnexpectedValueException.php|  MDÐ\|  3ë’à¶      ]   vendor/doctrine/instantiator/src/Doctrine/Instantiator/Exception/InvalidArgumentException.phpÇ  MDÐ\Ç  EwÙ¶      %   vendor/firebase/php-jwt/composer.jsonä  MDÐ\ä  8³ëY¶      9   vendor/firebase/php-jwt/src/SignatureInvalidException.phpf   MDÐ\f   ¤ŠY¶      4   vendor/firebase/php-jwt/src/BeforeValidException.phpa   MDÐ\a   (S6ÿ¶      0   vendor/firebase/php-jwt/src/ExpiredException.php]   MDÐ\]   z¶·¶      #   vendor/firebase/php-jwt/src/JWT.php8  MDÐ\8  ¨ôö¶      $   vendor/robmorgan/phinx/app/phinx.phpñ  MDÐ\ñ  =µ¥å¶      "   vendor/robmorgan/phinx/app/web.php  MDÐ\  ·_ŒÂ¶      $   vendor/robmorgan/phinx/composer.jsonë  MDÐ\ë  yÌ—G¶      7   vendor/robmorgan/phinx/src/Phinx/Seed/SeedInterface.phpä  MDÐ\ä  4‹~¶      6   vendor/robmorgan/phinx/src/Phinx/Seed/AbstractSeed.php  MDÐ\  ;÷>¿¶      .   vendor/robmorgan/phinx/src/Phinx/Util/Util.phpÌ  MDÐ\Ì  ÇÀZE¶      8   vendor/robmorgan/phinx/src/Phinx/Wrapper/TextWrapper.php"  MDÐ\"  �6˜o¶      C   vendor/robmorgan/phinx/src/Phinx/Config/NamespaceAwareInterface.phpÌ  MDÐ\Ì  1™ö¶      ;   vendor/robmorgan/phinx/src/Phinx/Config/ConfigInterface.phpô  MDÐ\ô  ë­� ¶      ?   vendor/robmorgan/phinx/src/Phinx/Config/NamespaceAwareTrait.phpˆ
  MDÐ\ˆ
  �ÿdœ¶      2   vendor/robmorgan/phinx/src/Phinx/Config/Config.phpb.  MDÐ\b.  †€¶¢¶      <   vendor/robmorgan/phinx/src/Phinx/Db/Adapter/ProxyAdapter.phpè!  MDÐ\è!  åÆ3Ò¶      :   vendor/robmorgan/phinx/src/Phinx/Db/Adapter/PdoAdapter.phpà&  MDÐ\à&  ÁžWn¶      B   vendor/robmorgan/phinx/src/Phinx/Db/Adapter/TimedOutputAdapter.phpf"  MDÐ\f"  ±¹€¼¶      <   vendor/robmorgan/phinx/src/Phinx/Db/Adapter/MysqlAdapter.php¸Š  MDÐ\¸Š  a¼Þ]¶      >   vendor/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterFactory.phpg  MDÐ\g  ÕIaí¶      B   vendor/robmorgan/phinx/src/Phinx/Db/Adapter/TablePrefixAdapter.php   MDÐ\   �¢€?¶      @   vendor/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterInterface.phpH8  MDÐ\H8  Ýk%É¶      ?   vendor/robmorgan/phinx/src/Phinx/Db/Adapter/PostgresAdapter.phpõ�  MDÐ\õ�  u5}¶      >   vendor/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterWrapper.php‡,  MDÐ\‡,  ¸WJÍ¶      @   vendor/robmorgan/phinx/src/Phinx/Db/Adapter/SqlServerAdapter.php‚‹  MDÐ\‚‹  ~µ›ó¶      =   vendor/robmorgan/phinx/src/Phinx/Db/Adapter/SQLiteAdapter.php»  MDÐ\»  a¶º…¶      ?   vendor/robmorgan/phinx/src/Phinx/Db/Adapter/AbstractAdapter.phpX  MDÐ\X  |x0¶      @   vendor/robmorgan/phinx/src/Phinx/Db/Adapter/WrapperInterface.php×  MDÐ\×  8œ†ª¶      3   vendor/robmorgan/phinx/src/Phinx/Db/Table/Index.php  MDÐ\  ¬Tž]¶      8   vendor/robmorgan/phinx/src/Phinx/Db/Table/ForeignKey.php"  MDÐ\"  ‚xYÇ¶      4   vendor/robmorgan/phinx/src/Phinx/Db/Table/Column.php€1  MDÐ\€1  •ËCº¶      -   vendor/robmorgan/phinx/src/Phinx/Db/Table.php%G  MDÐ\%G  ©l¶      G   vendor/robmorgan/phinx/src/Phinx/Migration/AbstractTemplateCreation.phpD
  MDÐ\D
  �¬qÿ¶      @   vendor/robmorgan/phinx/src/Phinx/Migration/CreationInterface.phpƒ  MDÐ\ƒ  8r{Ï¶      M   vendor/robmorgan/phinx/src/Phinx/Migration/IrreversibleMigrationException.php¤  MDÐ\¤  rs®¶      B   vendor/robmorgan/phinx/src/Phinx/Migration/Manager/Environment.php"+  MDÐ\"+  ã›… ¶      6   vendor/robmorgan/phinx/src/Phinx/Migration/Manager.php³„  MDÐ\³„  ¯Í-H¶      A   vendor/robmorgan/phinx/src/Phinx/Migration/MigrationInterface.phpY  MDÐ\Y  öpÎ¶      @   vendor/robmorgan/phinx/src/Phinx/Migration/AbstractMigration.phpV  MDÐ\V  pì1¶      =   vendor/robmorgan/phinx/src/Phinx/Console/PhinxApplication.php  MDÐ\  }@6¶      9   vendor/robmorgan/phinx/src/Phinx/Console/Command/Init.phpi  MDÐ\i  So4Ú¶      <   vendor/robmorgan/phinx/src/Phinx/Console/Command/SeedRun.phpã  MDÐ\ã  ·­þD¶      <   vendor/robmorgan/phinx/src/Phinx/Console/Command/Migrate.php“  MDÐ\“  ¢ÔT¯¶      ;   vendor/robmorgan/phinx/src/Phinx/Console/Command/Create.php…1  MDÐ\…1  0/z¶      =   vendor/robmorgan/phinx/src/Phinx/Console/Command/Rollback.php!  MDÐ\!  —(Çñ¶      9   vendor/robmorgan/phinx/src/Phinx/Console/Command/Test.phpC  MDÐ\C  d}»�¶      ;   vendor/robmorgan/phinx/src/Phinx/Console/Command/Status.php?  MDÐ\?  ä"c¶      ?   vendor/robmorgan/phinx/src/Phinx/Console/Command/Breakpoint.php¾  MDÐ\¾  Päp¶      D   vendor/robmorgan/phinx/src/Phinx/Console/Command/AbstractCommand.phpò(  MDÐ\ò(  KIã¶      ?   vendor/robmorgan/phinx/src/Phinx/Console/Command/SeedCreate.phpU  MDÐ\U  9£õ¶      2   vendor/robmorgan/phinx/src/composer_autoloader.phpM  MDÐ\M  e\ˆ¶      &   vendor/phpunit/php-timer/composer.json-  MDÐ\-  8òë¯¶      &   vendor/phpunit/php-timer/src/Timer.php	  MDÐ\	  ‘GTã¶      -   vendor/phpunit/php-token-stream/composer.json…  MDÐ\…  ¦j@Á¶      C   vendor/phpunit/php-token-stream/src/Token/Stream/CachingFactory.phpÂ  MDÐ\Â  	\hû¶      4   vendor/phpunit/php-token-stream/src/Token/Stream.phpò>  MDÐ\ò>  ·©ä¶      -   vendor/phpunit/php-token-stream/src/Token.php<f  MDÐ\<f  ÚiŠY¶      1   vendor/phpunit/phpunit-mock-objects/composer.jsonÑ  MDÐ\Ñ  HÒF¶      R   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invocation/Static.php�  MDÐ\�  ¼R¶      R   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invocation/Object.phpÝ  MDÐ\Ý  8›ˆ¿¶      K   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/MockObject.php•  MDÐ\•  ¥¼ö¶      Q   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/InvocationMocker.phpÚ  MDÐ\Ú  `8 ã¶      J   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invokable.php½  MDÐ\½  ˜ù ô¶      K   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invocation.phpò  MDÐ\ò  9T‚·¶      L   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/MockBuilder.phpI#  MDÐ\I#  yË¶      S   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/MethodName.php-  MDÐ\-  ¦=R¶      ^   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/ConsecutiveParameters.php•  MDÐ\•  o"Ä«¶      X   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedRecorder.phpb  MDÐ\b  ißòü¶      S   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/Invocation.php  MDÐ\  @â¸©¶      X   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/AnyInvokedCount.phpà  MDÐ\à  “Ds$¶      [   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtLeastOnce.phpÑ  MDÐ\Ñ  E±@G¶      \   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/StatelessInvocation.phpi  MDÐ\i  •œ�¶      V   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/AnyParameters.phpI  MDÐ\I  UÛÕ@¶      [   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtMostCount.phpò  MDÐ\ò  Í„-å¶      W   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtIndex.php	  MDÐ\	  7y·{¶      \   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtLeastCount.phpý  MDÐ\ý  0¸8¶      S   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/Parameters.php  MDÐ\  ²	}(¶      U   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedCount.php¡  MDÐ\¡  q¹à¶      H   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher.phpi!  MDÐ\i!  7ZRÐ¶      L   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/Return.php¤  MDÐ\¤  L¢f+¶      V   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ConsecutiveCalls.php“  MDÐ\“  2	Æ¶      T   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnArgument.phpÿ  MDÐ\ÿ  9)�`¶      U   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnReference.php;  MDÐ\;  lýó¶      W   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/MatcherCollection.phpò  MDÐ\ò  „ðv¶      T   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnValueMap.php|  MDÐ\|  uB	è¶      T   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnCallback.php•  MDÐ\•  g‰´`¶      P   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnSelf.phpÔ  MDÐ\Ô  fk€k¶      O   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/Exception.php&  MDÐ\&  _,
¶      E   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub.phpÛ  MDÐ\Û  ˆÌÁ¶      J   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Generator.phpW®  MDÐ\W®  €ö¡è¶      Y   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/InvocationMocker.php   MDÐ\   ÕÝîÏ¶      X   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/ParametersMatch.php  MDÐ\  w2¶      R   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Namespace.php  MDÐ\  ±óAF¶      Q   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Identity.phpœ  MDÐ\œ  w[¶      N   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Match.php  MDÐ\  jZ›¶      M   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Stub.phpX  MDÐ\X  Ñ•¶      X   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/MethodNameMatch.php‚  MDÐ\‚  $ˆõý¶      [   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Exception/RuntimeException.phpµ  MDÐ\µ  Y·n4¶      a   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Exception/BadMethodCallException.phpÁ  MDÐ\Á  ¥Ò).¶      T   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Exception/Exception.php¦  MDÐ\¦  ]ÃT¶      K   vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Verifiable.php²  MDÐ\²  ”'L¶      $   vendor/phpunit/phpunit/composer.json›  MDÐ\›  méÐ²¶      3   vendor/phpunit/phpunit/src/TextUI/ResultPrinter.phpoF  MDÐ\oF  €ýK¶      0   vendor/phpunit/phpunit/src/TextUI/TestRunner.phpÎ¾  MDÐ\Î¾  ��3,¶      -   vendor/phpunit/phpunit/src/TextUI/Command.phpøœ  MDÐ\øœ  |Œ.¶      (   vendor/phpunit/phpunit/src/Util/Type.phpH  MDÐ\H  V‰Vß¶      )   vendor/phpunit/phpunit/src/Util/Regex.phpq  MDÐ\q  ”ÔV¶      .   vendor/phpunit/phpunit/src/Util/Fileloader.phpv  MDÐ\v  ÷Ô(E¶      5   vendor/phpunit/phpunit/src/Util/TestSuiteIterator.phpÊ  MDÐ\Ê  ÔØø¶      .   vendor/phpunit/phpunit/src/Util/Filesystem.php   MDÐ\   ™þÓ¶      :   vendor/phpunit/phpunit/src/Util/ConfigurationGenerator.phpU  MDÐ\U  V·ëø¶      -   vendor/phpunit/phpunit/src/Util/Blacklist.phpr  MDÐ\r  a¸1ã¶      *   vendor/phpunit/phpunit/src/Util/Getopt.php#  MDÐ\#  ,„·¶      '   vendor/phpunit/phpunit/src/Util/XML.php¥  MDÐ\¥  Ífý¶      9   vendor/phpunit/phpunit/src/Util/InvalidArgumentHelper.php"  MDÐ\"  ‚ƒ¥À¶      1   vendor/phpunit/phpunit/src/Util/Configuration.phpIˆ  MDÐ\Iˆ  Z7"¶      2   vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php  MDÐ\  ™ 3í¶      /   vendor/phpunit/phpunit/src/Util/PHP/Windows.phpõ  MDÐ\õ  Ä¿î¶      /   vendor/phpunit/phpunit/src/Util/PHP/Default.php°  MDÐ\°  ­·nƒ¶      (   vendor/phpunit/phpunit/src/Util/Test.phpZŠ  MDÐ\ZŠ  ¥"B¶      /   vendor/phpunit/phpunit/src/Util/GlobalState.php:  MDÐ\:  9"™¶      9   vendor/phpunit/phpunit/src/Util/TestDox/ResultPrinter.phpf%  MDÐ\f%  “>'¶      :   vendor/phpunit/phpunit/src/Util/TestDox/NamePrettifier.phpC  MDÐ\C  Æk·¶      =   vendor/phpunit/phpunit/src/Util/TestDox/ResultPrinter/XML.php¼  MDÐ\¼  ×Ç[û¶      >   vendor/phpunit/phpunit/src/Util/TestDox/ResultPrinter/HTML.php¸
  MDÐ\¸
  Ý
ì¶      >   vendor/phpunit/phpunit/src/Util/TestDox/ResultPrinter/Text.phpF  MDÐ\F   !kT¶      *   vendor/phpunit/phpunit/src/Util/Filter.php[  MDÐ\[  ÞŠÓ,¶      *   vendor/phpunit/phpunit/src/Util/String.phpm  MDÐ\m  þî©¶      0   vendor/phpunit/phpunit/src/Util/Log/TeamCity.phpT+  MDÐ\T+  Z×¯¶      ,   vendor/phpunit/phpunit/src/Util/Log/JSON.php¼  MDÐ\¼  •gÀ¶      +   vendor/phpunit/phpunit/src/Util/Log/TAP.phpr  MDÐ\r  ·nöß¶      -   vendor/phpunit/phpunit/src/Util/Log/JUnit.php®2  MDÐ\®2  �óÚ)¶      0   vendor/phpunit/phpunit/src/Util/ErrorHandler.phpL  MDÐ\L  Ó±s¶      '   vendor/phpunit/phpunit/src/Util/PHP.php¾)  MDÐ\¾)  O‘P¶      +   vendor/phpunit/phpunit/src/Util/Printer.phpJ  MDÐ\J  LŸ•z¶      5   vendor/phpunit/phpunit/src/Framework/TestListener.php  MDÐ\  ò®~"¶      0   vendor/phpunit/phpunit/src/Framework/Warning.php<  MDÐ\<  ‘§Ï‰¶      H   vendor/phpunit/phpunit/src/Framework/UnintentionallyCoveredCodeError.phpÑ  MDÐ\Ñ  	Q¶      C   vendor/phpunit/phpunit/src/Framework/ExpectationFailedException.phph  MDÐ\h  =ØÜž¶      3   vendor/phpunit/phpunit/src/Framework/TestResult.php£ƒ  MDÐ\£ƒ  ú\©¶      >   vendor/phpunit/phpunit/src/Framework/SkippedTestSuiteError.phpã  MDÐ\ã  ²ŽÙ¶      9   vendor/phpunit/phpunit/src/Framework/BaseTestListener.php±  MDÐ\±  Aû%r¶      2   vendor/phpunit/phpunit/src/Framework/TestSuite.php?o  MDÐ\?o  ×^C?¶      4   vendor/phpunit/phpunit/src/Framework/OutputError.php¶  MDÐ\¶  Rá�B¶      <   vendor/phpunit/phpunit/src/Framework/IncompleteTestError.phpâ  MDÐ\â  ß¬T¶      7   vendor/phpunit/phpunit/src/Framework/SelfDescribing.phpá  MDÐ\á   zÑl¶      ?   vendor/phpunit/phpunit/src/Framework/TestSuite/DataProvider.phpM  MDÐ\M  ¬!_0¶      7   vendor/phpunit/phpunit/src/Framework/RiskyTestError.phpÒ  MDÐ\Ò  ý›é¶      7   vendor/phpunit/phpunit/src/Framework/IncompleteTest.phpª  MDÐ\ª  ÎÍy¶      9   vendor/phpunit/phpunit/src/Framework/SkippedTestError.phpØ  MDÐ\Ø  Øà¿¶      7   vendor/phpunit/phpunit/src/Framework/SyntheticError.phpØ  MDÐ\Ø  ¹Ö7E¶      >   vendor/phpunit/phpunit/src/Framework/CodeCoverageException.php;  MDÐ\;  CzøÑ¶      9   vendor/phpunit/phpunit/src/Framework/ExceptionWrapper.phpg  MDÐ\g  ú‚Ž¶      H   vendor/phpunit/phpunit/src/Framework/CoveredCodeNotExecutedException.phpá  MDÐ\á  Fuw¶      9   vendor/phpunit/phpunit/src/Framework/Assert/Functions.phpÇÊ  MDÐ\ÇÊ  žŠ£‡¶      /   vendor/phpunit/phpunit/src/Framework/Assert.php3^ MDÐ\3^ ƒý|Ã¶      4   vendor/phpunit/phpunit/src/Framework/SkippedTest.phpY  MDÐ\Y  Îë!l¶      -   vendor/phpunit/phpunit/src/Framework/Test.phpZ  MDÐ\Z  –@kD¶      2   vendor/phpunit/phpunit/src/Framework/RiskyTest.phpt  MDÐ\t  j~Hô¶      .   vendor/phpunit/phpunit/src/Framework/Error.phpó  MDÐ\ó  x)¶      3   vendor/phpunit/phpunit/src/Framework/Constraint.phpÆ  MDÐ\Æ  E"9¶      E   vendor/phpunit/phpunit/src/Framework/InvalidCoversTargetException.phpN  MDÐ\N  i ¡G¶      I   vendor/phpunit/phpunit/src/Framework/MissingCoversAnnotationException.phpí  MDÐ\í  �}¼,¶      2   vendor/phpunit/phpunit/src/Framework/Exception.phpï  MDÐ\ï  ÆLÉ¶      6   vendor/phpunit/phpunit/src/Framework/Error/Warning.php  MDÐ\  bc“:¶      5   vendor/phpunit/phpunit/src/Framework/Error/Notice.phpÿ  MDÐ\ÿ  +I�¶      9   vendor/phpunit/phpunit/src/Framework/Error/Deprecated.php  MDÐ\  ,Ç/‚¶      1   vendor/phpunit/phpunit/src/Framework/TestCase.phpÚ MDÐ\Ú ÓCî@¶      8   vendor/phpunit/phpunit/src/Framework/WarningTestCase.php:  MDÐ\:  ô/šñ¶      @   vendor/phpunit/phpunit/src/Framework/Constraint/IsInstanceOf.php�  MDÐ\�  -åYÿ¶      >   vendor/phpunit/phpunit/src/Framework/Constraint/IsReadable.php‹  MDÐ\‹  ÊÍM¶      ;   vendor/phpunit/phpunit/src/Framework/Constraint/IsFalse.phpD  MDÐ\D  -™ø�¶      <   vendor/phpunit/phpunit/src/Framework/Constraint/LessThan.php�  MDÐ\�  6îÀó¶      C   vendor/phpunit/phpunit/src/Framework/Constraint/DirectoryExists.php‘  MDÐ\‘   «Ú¶      >   vendor/phpunit/phpunit/src/Framework/Constraint/IsAnything.php  MDÐ\  B	¡¶      D   vendor/phpunit/phpunit/src/Framework/Constraint/ExceptionMessage.php   MDÐ\   }¦ú¶      7   vendor/phpunit/phpunit/src/Framework/Constraint/And.phpÀ  MDÐ\À  *}¶      K   vendor/phpunit/phpunit/src/Framework/Constraint/TraversableContainsOnly.php|	  MDÐ\|	  XºÄª¶      K   vendor/phpunit/phpunit/src/Framework/Constraint/ClassHasStaticAttribute.php  MDÐ\  çU·¶      :   vendor/phpunit/phpunit/src/Framework/Constraint/IsTrue.php@  MDÐ\@  dX¿Ø¶      ?   vendor/phpunit/phpunit/src/Framework/Constraint/ArrayHasKey.php‡  MDÐ\‡  ´*öQ¶      =   vendor/phpunit/phpunit/src/Framework/Constraint/Composite.phpŽ  MDÐ\Ž  Rª‰&¶      D   vendor/phpunit/phpunit/src/Framework/Constraint/StringStartsWith.php•  MDÐ\•  :÷ÊN¶      T   vendor/phpunit/phpunit/src/Framework/Constraint/JsonMatches/ErrorMessageProvider.php~  MDÐ\~  dõ–b¶      :   vendor/phpunit/phpunit/src/Framework/Constraint/IsJson.php  MDÐ\  KÊF¶      ?   vendor/phpunit/phpunit/src/Framework/Constraint/JsonMatches.phpï  MDÐ\ï  Ù b%¶      =   vendor/phpunit/phpunit/src/Framework/Constraint/Attribute.phpd	  MDÐ\d	  Ý»¦¶      A   vendor/phpunit/phpunit/src/Framework/Constraint/StringMatches.phpä  MDÐ\ä  h–z¶      7   vendor/phpunit/phpunit/src/Framework/Constraint/Xor.phpc  MDÐ\c  œ/-¶      :   vendor/phpunit/phpunit/src/Framework/Constraint/IsType.php:  MDÐ\:  ñ�Î¶      ;   vendor/phpunit/phpunit/src/Framework/Constraint/IsEmpty.phpõ  MDÐ\õ  Û’o¶      G   vendor/phpunit/phpunit/src/Framework/Constraint/TraversableContains.phpl  MDÐ\l  '€:Œ¶      ;   vendor/phpunit/phpunit/src/Framework/Constraint/IsEqual.phpð  MDÐ\ð  ;ðß¯¶      9   vendor/phpunit/phpunit/src/Framework/Constraint/IsNan.php<  MDÐ\<  ×¾Ë¶      E   vendor/phpunit/phpunit/src/Framework/Constraint/ClassHasAttribute.php’  MDÐ\’  >Œåæ¶      9   vendor/phpunit/phpunit/src/Framework/Constraint/Count.php¿  MDÐ\¿  lÙìh¶      F   vendor/phpunit/phpunit/src/Framework/Constraint/ObjectHasAttribute.phpl  MDÐ\l  ™È¶¶      =   vendor/phpunit/phpunit/src/Framework/Constraint/PCREMatch.phpŒ  MDÐ\Œ  £˜Â¶      >   vendor/phpunit/phpunit/src/Framework/Constraint/IsInfinite.phpP  MDÐ\P  ÈÖos¶      J   vendor/phpunit/phpunit/src/Framework/Constraint/ExceptionMessageRegExp.php*  MDÐ\*  ~2H¶      B   vendor/phpunit/phpunit/src/Framework/Constraint/StringContains.php  MDÐ\  õˆVÜ¶      ?   vendor/phpunit/phpunit/src/Framework/Constraint/IsIdentical.php¢  MDÐ\¢  q¡S.¶      :   vendor/phpunit/phpunit/src/Framework/Constraint/IsNull.php@  MDÐ\@  þ¨Û4¶      6   vendor/phpunit/phpunit/src/Framework/Constraint/Or.phpð
  MDÐ\ð
  þih¶      ?   vendor/phpunit/phpunit/src/Framework/Constraint/ArraySubset.phpä
  MDÐ\ä
  oá%Ò¶      <   vendor/phpunit/phpunit/src/Framework/Constraint/SameSize.php  MDÐ\  iòÍ°¶      7   vendor/phpunit/phpunit/src/Framework/Constraint/Not.php#  MDÐ\#  çŠ,¶      =   vendor/phpunit/phpunit/src/Framework/Constraint/Exception.phpd  MDÐ\d  ­ð¶      >   vendor/phpunit/phpunit/src/Framework/Constraint/IsWritable.php‹  MDÐ\‹  R­ˆ›¶      A   vendor/phpunit/phpunit/src/Framework/Constraint/ExceptionCode.php%  MDÐ\%  F7Ú¶      <   vendor/phpunit/phpunit/src/Framework/Constraint/Callback.php3  MDÐ\3  ¥ãí¶      ?   vendor/phpunit/phpunit/src/Framework/Constraint/GreaterThan.php–  MDÐ\–  àÒÇy¶      B   vendor/phpunit/phpunit/src/Framework/Constraint/StringEndsWith.php¦  MDÐ\¦  éÇ¦—¶      <   vendor/phpunit/phpunit/src/Framework/Constraint/IsFinite.phpH  MDÐ\H  ç™dà¶      >   vendor/phpunit/phpunit/src/Framework/Constraint/FileExists.php‚  MDÐ\‚  ßz‡¼¶      ;   vendor/phpunit/phpunit/src/Framework/IncompleteTestCase.php  MDÐ\  ‰=¶      4   vendor/phpunit/phpunit/src/Framework/TestFailure.php  MDÐ\  yˆj"¶      8   vendor/phpunit/phpunit/src/Framework/SkippedTestCase.php¹  MDÐ\¹  ä FÕ¶      =   vendor/phpunit/phpunit/src/Framework/AssertionFailedError.phpJ  MDÐ\J  Ê‹tG¶      4   vendor/phpunit/phpunit/src/Runner/BaseTestRunner.phpJ  MDÐ\J  (õÜ¶      =   vendor/phpunit/phpunit/src/Runner/StandardTestSuiteLoader.php›  MDÐ\›  ãŸ¶      -   vendor/phpunit/phpunit/src/Runner/Version.php$  MDÐ\$  eÛm6¶      5   vendor/phpunit/phpunit/src/Runner/TestSuiteLoader.phpª  MDÐ\ª  \fÕb¶      4   vendor/phpunit/phpunit/src/Runner/Filter/Factory.phpœ  MDÐ\œ  ^I À¶      2   vendor/phpunit/phpunit/src/Runner/Filter/Group.phpÅ  MDÐ\Å  7û_w¶      :   vendor/phpunit/phpunit/src/Runner/Filter/Group/Include.phpñ  MDÐ\ñ  }f¾¶      :   vendor/phpunit/phpunit/src/Runner/Filter/Group/Exclude.phpò  MDÐ\ò  ø­ÓT¶      1   vendor/phpunit/phpunit/src/Runner/Filter/Test.phpq  MDÐ\q  |Oèú¶      /   vendor/phpunit/phpunit/src/Runner/Exception.php>  MDÐ\>  +ÿPÝ¶      7   vendor/phpunit/phpunit/src/Extensions/PhptTestSuite.phpÞ  MDÐ\Þ  Mï,n¶      6   vendor/phpunit/phpunit/src/Extensions/RepeatedTest.phpÂ  MDÐ\Â  ÞÑOæ¶      6   vendor/phpunit/phpunit/src/Extensions/PhptTestCase.php8-  MDÐ\8-  B™ï¶      7   vendor/phpunit/phpunit/src/Extensions/TestDecorator.php	  MDÐ\	  8<{·¶      8   vendor/phpunit/phpunit/src/Extensions/TicketListener.php	  MDÐ\	  NeÑ¶      8   vendor/phpunit/phpunit/src/Extensions/GroupTestSuite.phpQ  MDÐ\Q  2ˆÐ¶      @   vendor/phpunit/phpunit/src/ForwardCompatibility/TestListener.phpj  MDÐ\j  Þý”¶      D   vendor/phpunit/phpunit/src/ForwardCompatibility/BaseTestListener.php{  MDÐ\{  ðÉÚZ¶      =   vendor/phpunit/phpunit/src/ForwardCompatibility/TestSuite.php]  MDÐ\]  Ø‰Ê¶      :   vendor/phpunit/phpunit/src/ForwardCompatibility/Assert.php]  MDÐ\]  ì®_È¶      8   vendor/phpunit/phpunit/src/ForwardCompatibility/Test.phpR  MDÐ\R  ÞQÆt¶      <   vendor/phpunit/phpunit/src/ForwardCompatibility/TestCase.phpc  MDÐ\c  ÷;Â¶      H   vendor/phpunit/phpunit/src/ForwardCompatibility/AssertionFailedError.php~  MDÐ\~  2¶      (   vendor/phpunit/phpunit/src/Exception.php9  MDÐ\9  Æ,Ð¶      .   vendor/phpunit/php-text-template/composer.json�  MDÐ\�  &à{Ö¶      1   vendor/phpunit/php-text-template/src/Template.php”  MDÐ\”  ¯w4¯¶      .   vendor/phpunit/php-code-coverage/composer.json�  MDÐ\�  ½hÝµ¶      5   vendor/phpunit/php-code-coverage/src/CodeCoverage.phpXt  MDÐ\Xt  í[f¶      4   vendor/phpunit/php-code-coverage/src/Driver/HHVM.phpƒ  MDÐ\ƒ  Õ†7¶      6   vendor/phpunit/php-code-coverage/src/Driver/Driver.php  MDÐ\  Ž �î¶      6   vendor/phpunit/php-code-coverage/src/Driver/PHPDBG.php]  MDÐ\]  Ó”Ó¶      6   vendor/phpunit/php-code-coverage/src/Driver/Xdebug.phpÙ
  MDÐ\Ù
  ÿò‡¶      /   vendor/phpunit/php-code-coverage/src/Filter.phpÛ  MDÐ\Û  ÁÖÊ¶      6   vendor/phpunit/php-code-coverage/src/Report/Clover.phpè&  MDÐ\è&  ’$ì´¶      G   vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Dashboard.phpm&  MDÐ\m&  !mu¶      B   vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/File.phpàJ  MDÐ\àJ  ¥Ìµ¶      G   vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Directory.php÷  MDÐ\÷  ;$­¶      =   vendor/phpunit/php-code-coverage/src/Report/Html/Renderer.php¹"  MDÐ\¹"  d]“™¶      ;   vendor/phpunit/php-code-coverage/src/Report/Html/Facade.php-  MDÐ\-  {$H¶      :   vendor/phpunit/php-code-coverage/src/Report/Xml/Totals.phpˆ  MDÐ\ˆ  é…®©¶      8   vendor/phpunit/php-code-coverage/src/Report/Xml/File.phpÛ  MDÐ\Û  e�5j¶      :   vendor/phpunit/php-code-coverage/src/Report/Xml/Method.phpW  MDÐ\W  �	\¶      8   vendor/phpunit/php-code-coverage/src/Report/Xml/Unit.php
  MDÐ\
  -5µ^¶      9   vendor/phpunit/php-code-coverage/src/Report/Xml/Tests.php  MDÐ\  ÇÒM•¶      ;   vendor/phpunit/php-code-coverage/src/Report/Xml/Project.php  MDÐ\  Äƒ¸J¶      8   vendor/phpunit/php-code-coverage/src/Report/Xml/Node.phpó  MDÐ\ó  ;‡#P¶      =   vendor/phpunit/php-code-coverage/src/Report/Xml/Directory.phpR  MDÐ\R  &ì·a¶      <   vendor/phpunit/php-code-coverage/src/Report/Xml/Coverage.phpC  MDÐ\C  dÈ¶      :   vendor/phpunit/php-code-coverage/src/Report/Xml/Report.phpÆ  MDÐ\Æ  @ÉwS¶      :   vendor/phpunit/php-code-coverage/src/Report/Xml/Facade.phpÛ  MDÐ\Û  ÝP,¶      3   vendor/phpunit/php-code-coverage/src/Report/PHP.phpï  MDÐ\ï  DPÍë¶      4   vendor/phpunit/php-code-coverage/src/Report/Text.php"  MDÐ\"  ÞjÚ¶      6   vendor/phpunit/php-code-coverage/src/Report/Crap4j.phpˆ  MDÐ\ˆ  qro‚¶      -   vendor/phpunit/php-code-coverage/src/Util.phpõ  MDÐ\õ  ±¼¶      2   vendor/phpunit/php-code-coverage/src/Node/File.phpuL  MDÐ\uL  �:›¶      5   vendor/phpunit/php-code-coverage/src/Node/Builder.phpb  MDÐ\b  “*ÜÌ¶      :   vendor/phpunit/php-code-coverage/src/Node/AbstractNode.phpŸ  MDÐ\Ÿ  Œ_Å¶      7   vendor/phpunit/php-code-coverage/src/Node/Directory.phpå&  MDÐ\å&  Ókê¶      6   vendor/phpunit/php-code-coverage/src/Node/Iterator.php¬  MDÐ\¬  9–ÜQ¶      V   vendor/phpunit/php-code-coverage/src/Exception/UnintentionallyCoveredCodeException.php¬  MDÐ\¬  ýF–+¶      C   vendor/phpunit/php-code-coverage/src/Exception/RuntimeException.phpp  MDÐ\p  w©C¶      R   vendor/phpunit/php-code-coverage/src/Exception/CoveredCodeNotExecutedException.php°  MDÐ\°  /Ý/ ¶      K   vendor/phpunit/php-code-coverage/src/Exception/InvalidArgumentException.phpç  MDÐ\ç  —NË¶      S   vendor/phpunit/php-code-coverage/src/Exception/MissingCoversAnnotationException.php´  MDÐ\´  ¼fM¶      <   vendor/phpunit/php-code-coverage/src/Exception/Exception.php~  MDÐ\~  ›üu¶      .   vendor/phpunit/php-file-iterator/composer.jsonn  MDÐ\n  ;·åp¶      0   vendor/phpunit/php-file-iterator/src/Factory.php§	  MDÐ\§	  ùÙl�¶      1   vendor/phpunit/php-file-iterator/src/Iterator.phpb  MDÐ\b  ‚ ¶      /   vendor/phpunit/php-file-iterator/src/Facade.php»  MDÐ\»  ËÏ0Ž¶      /   vendor/psr/log/Psr/Log/LoggerAwareInterface.php)  MDÐ\)  Èj±¶      #   vendor/psr/log/Psr/Log/LogLevel.phpP  MDÐ\P  �òº¶      3   vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php^  MDÐ\^  œêzÊ¶      *   vendor/psr/log/Psr/Log/Test/TestLogger.php‹  MDÐ\‹  €Q™™¶      +   vendor/psr/log/Psr/Log/LoggerAwareTrait.php�  MDÐ\�  z%Ô¶      3   vendor/psr/log/Psr/Log/InvalidArgumentException.php`   MDÐ\`    ˆX1¶      %   vendor/psr/log/Psr/Log/NullLogger.php‹  MDÐ\‹  šöZf¶      *   vendor/psr/log/Psr/Log/LoggerInterface.phpß  MDÐ\ß  ?}¶      &   vendor/psr/log/Psr/Log/LoggerTrait.php  MDÐ\  ›Ã½õ¶      )   vendor/psr/log/Psr/Log/AbstractLogger.php  MDÐ\  šGl¶         vendor/psr/log/composer.json1  MDÐ\1  Â—²¶      $   vendor/monolog/monolog/composer.jsonç	  MDÐ\ç	  ½ö³‹¶      @   vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.phpR  MDÐ\R  B]“˜¶      :   vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php  MDÐ\  ôIG¶      ;   vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.phpB  MDÐ\B  8÷¶      <   vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.phpT&  MDÐ\T&  k°‘¾¶      >   vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.phpí  MDÐ\í  ot¶	¶      <   vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.phpU  MDÐ\U  ªƒB¥¶      <   vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php3  MDÐ\3  £·VÆ¶      =   vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.phpf  MDÐ\f  …Œ¶      D   vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php  MDÐ\  L9˜¶      =   vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php>	  MDÐ\>	  ŠH¶      Y   vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.phpˆ  MDÐ\ˆ  —Äm{¶      Z   vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php÷  MDÐ\÷  V	vÜ¶      \   vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php�  MDÐ\�  oœV­¶      ;   vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.phpÆ
  MDÐ\Æ
  Ï}×¶      C   vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.phpU  MDÐ\U  Óú9¶      >   vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.phpr
  MDÐ\r
  °öY‹¶      >   vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php  MDÐ\  �ß¶      A   vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.phpl  MDÐ\l  ¬Æ¶      >   vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php  MDÐ\  má¶      >   vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.phpâ  MDÐ\â  EUÍú¶      H   vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.phpÂ  MDÐ\Â  ðÆ]•¶      D   vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php+  MDÐ\+  ¥¹Ñ¶      ;   vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.phpÔ  MDÐ\Ô  Ž—é*¶      ?   vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php
  MDÐ\
  çVcÅ¶      D   vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.phpÒ  MDÐ\Ò  C³‘Ú¶      <   vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.phpF  MDÐ\F  ‚âtÓ¶      B   vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php  MDÐ\  ÿ×&Š¶      B   vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.phpV  MDÐ\V  «»ªÅ¶      =   vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.phpF  MDÐ\F  ›/g¶      :   vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php¥  MDÐ\¥  M·¤M¶      A   vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.phpÀ  MDÐ\À  RÞ½¦¶      @   vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php'  MDÐ\'  ns|Ê¶      >   vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php@	  MDÐ\@	  û„}O¶      @   vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.phpO  MDÐ\O  ™V„¶      C   vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.phpg  MDÐ\g  Êš0¶      =   vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.phpW  MDÐ\W  tÝe›¶      8   vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.phpÙ  MDÐ\Ù  áÿµ¸¶      E   vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.phpè  MDÐ\è  ~ºÕ¶      >   vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.phpˆ	  MDÐ\ˆ	  bôiå¶      :   vendor/monolog/monolog/src/Monolog/Handler/MailHandler.phpV  MDÐ\V  YÐyW¶      <   vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.phpd  MDÐ\d  ò‡ê+¶      <   vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php;
  MDÐ\;
  Wß¶      B   vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.phpì  MDÐ\ì  �ïº¶      F   vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php  MDÐ\  Š‘fû¶      ?   vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.phpç  MDÐ\ç  CZE‡¶      @   vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php)   MDÐ\)   “ÒÊj¶      9   vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php™  MDÐ\™  Ú(r¶      >   vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.phpB  MDÐ\B  •V&¶      ?   vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php
  MDÐ\
  $ÊsÕ¶      ;   vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php6  MDÐ\6  •-*¶      =   vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php*  MDÐ\*  ÕæÂö¶      ?   vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php#  MDÐ\#  3–‡Ö¶      :   vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php¹  MDÐ\¹  åuàf¶      :   vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php  MDÐ\  zYÐM¶      >   vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.phpl  MDÐ\l  "Ç3Ö¶      H   vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.phpü  MDÐ\ü  ‹å”˜¶      =   vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php¡  MDÐ\¡  cŒï#¶      ;   vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.phpI  MDÐ\I   n“L¶      :   vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php  MDÐ\  À9 ë¶      B   vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.phpy  MDÐ\y  kƒMâ¶      -   vendor/monolog/monolog/src/Monolog/Logger.php5V  MDÐ\5V  y¶Å¶      /   vendor/monolog/monolog/src/Monolog/Registry.php¸  MDÐ\¸  ¼û½Õ¶      G   vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php0  MDÐ\0  ÿ¶¶      =   vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.phpª  MDÐ\ª  xÃOj¶      I   vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php  MDÐ\  ·¼¬@¶      C   vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php\  MDÐ\\  ¦×�¶      C   vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.phpƒ  MDÐ\ƒ  u›_¶      =   vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.phpR  MDÐ\R  £dd¶      =   vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.phpœ  MDÐ\œ  �6 A¶      @   vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php)  MDÐ\)  ñÇy¶      G   vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.phpí  MDÐ\í  ÷˜p+¶      =   vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.phpÏ  MDÐ\Ï  ý)èï¶      C   vendor/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php	  MDÐ\	  \e3ÿ¶      E   vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php  MDÐ\  aoOD¶      :   vendor/monolog/monolog/src/Monolog/ResettableInterface.php¯  MDÐ\¯  û4n¶      4   vendor/monolog/monolog/src/Monolog/SignalHandler.phpa  MDÐ\a  ·M`¶      B   vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php-  MDÐ\-  ø™™‚¶      >   vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php¾  MDÐ\¾  <¶–ï¶      >   vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php  MDÐ\  J3Ž¶      B   vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.phpô	  MDÐ\ô	  }*Ô3¶      C   vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php  MDÐ\  ÇWÜ‘¶      B   vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php»  MDÐ\»  .ßhÝ¶      @   vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php*  MDÐ\*  wâ™¶      @   vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php  MDÐ\  ¥^Êñ¶      A   vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.phpÜ  MDÐ\Ü  ¯iHò¶      C   vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.phpû  MDÐ\û  ;`ai¶      A   vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.phps  MDÐ\s  ÜæÆ-¶      E   vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php8  MDÐ\8  �1lá¶      B   vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php¶  MDÐ\¶  ©à(ï¶      D   vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php¤'  MDÐ\¤'  œø  ¶      >   vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.phpÛ  MDÐ\Û  
ÆŠ‹¶      3   vendor/monolog/monolog/src/Monolog/ErrorHandler.php­!  MDÐ\­!  šá±¶      ,   vendor/monolog/monolog/src/Monolog/Utils.php  MDÐ\  ¥þ¶      #   vendor/voku/php-hooks/composer.json`  MDÐ\`  âö¸�¶      /   vendor/voku/php-hooks/src/voku/helper/Hooks.php  MDÐ\  =\JG¶          vendor/symfony/dotenv/Dotenv.phpŽ8  MDÐ\Ž8  æ¶og¶      3   vendor/symfony/dotenv/Exception/FormatException.php†  MDÐ\†  å®í¶      1   vendor/symfony/dotenv/Exception/PathException.php¯  MDÐ\¯  ÂÑy$¶      6   vendor/symfony/dotenv/Exception/ExceptionInterface.php—  MDÐ\—  Ðà=¶      :   vendor/symfony/dotenv/Exception/FormatExceptionContext.php¡  MDÐ\¡  PMm¶      #   vendor/symfony/dotenv/composer.jsont  MDÐ\t  !F�¿¶      +   vendor/symfony/polyfill-ctype/bootstrap.phpS  MDÐ\S  ½ñ7¶      '   vendor/symfony/polyfill-ctype/Ctype.php}  MDÐ\}  \Šè°¶      +   vendor/symfony/polyfill-ctype/composer.json\  MDÐ\\  ¢âšÕ¶      4   vendor/symfony/config/ResourceCheckerConfigCache.php„  MDÐ\„  }g¶      '   vendor/symfony/config/Util/XmlUtils.php›$  MDÐ\›$  ¨»Ü¶      <   vendor/symfony/config/Util/Exception/XmlParsingException.phpÒ  MDÐ\Ò  ñï_‘¶      <   vendor/symfony/config/Util/Exception/InvalidXmlException.php%  MDÐ\%   A¬i¶      .   vendor/symfony/config/ConfigCacheInterface.php0  MDÐ\0  úed¶      .   vendor/symfony/config/FileLocatorInterface.php  MDÐ\  âXE¶      %   vendor/symfony/config/ConfigCache.php0  MDÐ\0  SþO¶      5   vendor/symfony/config/ConfigCacheFactoryInterface.phpÁ  MDÐ\Á  &é	+¶      %   vendor/symfony/config/FileLocator.php
  MDÐ\
  _÷€Û¶      2   vendor/symfony/config/ResourceCheckerInterface.php  MDÐ\  œR¶      '   vendor/symfony/config/Loader/Loader.php>  MDÐ\>  ÉK%v¶      +   vendor/symfony/config/Loader/FileLoader.phpŸ  MDÐ\Ÿ  ¯L¡¶      0   vendor/symfony/config/Loader/LoaderInterface.phpN  MDÐ\N  7‹
�¶      8   vendor/symfony/config/Loader/LoaderResolverInterface.php  MDÐ\  !ŠKÄ¶      /   vendor/symfony/config/Loader/LoaderResolver.php/  MDÐ\/  *žÑ7¶      1   vendor/symfony/config/Loader/DelegatingLoader.phpõ  MDÐ\õ  ¦Æø¶      /   vendor/symfony/config/Loader/GlobFileLoader.phpÊ  MDÐ\Ê  ïÑQ¶      .   vendor/symfony/config/Definition/FloatNode.phpØ  MDÐ\Ø  ]g'K¶      -   vendor/symfony/config/Definition/BaseNode.phpÏ8  MDÐ\Ï8  /Qû¶¶      0   vendor/symfony/config/Definition/NumericNode.php  MDÐ\  g8<¶      0   vendor/symfony/config/Definition/IntegerNode.phpO  MDÐ\O  X~¶      ;   vendor/symfony/config/Definition/PrototypeNodeInterface.phpw  MDÐ\w  ÿ€e¶      8   vendor/symfony/config/Definition/PrototypedArrayNode.php±/  MDÐ\±/  �ñjÙ¶      .   vendor/symfony/config/Definition/Processor.php9  MDÐ\9  À|ø}¶      2   vendor/symfony/config/Definition/NodeInterface.phpý	  MDÐ\ý	  ý'YV¶      -   vendor/symfony/config/Definition/EnumNode.phpƒ  MDÐ\ƒ  pjÓ¶      0   vendor/symfony/config/Definition/BooleanNode.phpñ  MDÐ\ñ  t·cü¶      .   vendor/symfony/config/Definition/ArrayNode.php÷.  MDÐ\÷.  î+gÑ¶      /   vendor/symfony/config/Definition/ScalarNode.phpÝ  MDÐ\Ý  gÄí¶      ;   vendor/symfony/config/Definition/ConfigurationInterface.phpe  MDÐ\e  \Û –¶      8   vendor/symfony/config/Definition/Builder/ExprBuilder.php  MDÐ\  Ÿë¥¹¶      ?   vendor/symfony/config/Definition/Builder/EnumNodeDefinition.phpä  MDÐ\ä  ô”·/¶      9   vendor/symfony/config/Definition/Builder/MergeBuilder.php4  MDÐ\4  Ž¥Ä¶      B   vendor/symfony/config/Definition/Builder/NumericNodeDefinition.php  MDÐ\  ó^Ó¶      8   vendor/symfony/config/Definition/Builder/NodeBuilder.php³  MDÐ\³  ^ïK¶      C   vendor/symfony/config/Definition/Builder/VariableNodeDefinition.phpÏ  MDÐ\Ï  i|7¶      @   vendor/symfony/config/Definition/Builder/ArrayNodeDefinition.php#;  MDÐ\#;  ±$E¶      B   vendor/symfony/config/Definition/Builder/BuilderAwareInterface.phpC  MDÐ\C  B¿�}¶      >   vendor/symfony/config/Definition/Builder/ValidationBuilder.phpÅ  MDÐ\Å  †iÖË¶      8   vendor/symfony/config/Definition/Builder/TreeBuilder.phpÅ  MDÐ\Å  Ï=¶      B   vendor/symfony/config/Definition/Builder/IntegerNodeDefinition.php  MDÐ\  [$Èö¶      @   vendor/symfony/config/Definition/Builder/NodeParentInterface.php¿  MDÐ\¿  YîÇÓ¶      A   vendor/symfony/config/Definition/Builder/NormalizationBuilder.php‚  MDÐ\‚  ª&¹<¶      B   vendor/symfony/config/Definition/Builder/BooleanNodeDefinition.php  MDÐ\  ‰y[¶      J   vendor/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php˜  MDÐ\˜  Ñwbš¶      A   vendor/symfony/config/Definition/Builder/ScalarNodeDefinition.phpô  MDÐ\ô   P¶      @   vendor/symfony/config/Definition/Builder/FloatNodeDefinition.php  MDÐ\  ?,·¶      ;   vendor/symfony/config/Definition/Builder/NodeDefinition.phpã   MDÐ\ã   =¾¢¶      D   vendor/symfony/config/Definition/Exception/DuplicateKeyException.phpE  MDÐ\E  ø|÷¶      I   vendor/symfony/config/Definition/Exception/InvalidDefinitionException.phpÚ  MDÐ\Ú  ]F4ç¶      C   vendor/symfony/config/Definition/Exception/InvalidTypeException.phpí  MDÐ\í  ¾Ä@¶      @   vendor/symfony/config/Definition/Exception/UnsetKeyException.php  MDÐ\  þ¤½¿¶      L   vendor/symfony/config/Definition/Exception/InvalidConfigurationException.phpR  MDÐ\R  h'Nñ¶      J   vendor/symfony/config/Definition/Exception/ForbiddenOverwriteException.phpQ  MDÐ\Q  ¨:2&¶      K   vendor/symfony/config/Definition/Exception/TreeWithoutRootNodeException.php¨  MDÐ\¨  9;š¶      8   vendor/symfony/config/Definition/Exception/Exception.phpË  MDÐ\Ë  ¹po:¶      1   vendor/symfony/config/Definition/VariableNode.php;  MDÐ\;  Â>RÎ¶      ?   vendor/symfony/config/Definition/Dumper/YamlReferenceDumper.phpð  MDÐ\ð  ]]M¶      >   vendor/symfony/config/Definition/Dumper/XmlReferenceDumper.php{'  MDÐ\{'  Ý`üÅ¶      @   vendor/symfony/config/Resource/SelfCheckingResourceInterface.phpB  MDÐ\B  Ñé£¶      /   vendor/symfony/config/Resource/FileResource.phpé  MDÐ\é  º`õO¶      :   vendor/symfony/config/Resource/ReflectionClassResource.php>  MDÐ\>  Øë�)¶      >   vendor/symfony/config/Resource/SelfCheckingResourceChecker.phpâ  MDÐ\â  ÿ¬f¶      3   vendor/symfony/config/Resource/ComposerResource.php0  MDÐ\0  Þ©p
¶      8   vendor/symfony/config/Resource/FileExistenceResource.php\  MDÐ\\  ¤…¾�¶      4   vendor/symfony/config/Resource/ResourceInterface.php  MDÐ\  ý?Š‰¶      9   vendor/symfony/config/Resource/ClassExistenceResource.php  MDÐ\  À‰1@¶      4   vendor/symfony/config/Resource/DirectoryResource.phpv  MDÐ\v  Ì€©·¶      /   vendor/symfony/config/Resource/GlobResource.phpy  MDÐ\y  4Lø’¶      7   vendor/symfony/config/Exception/LoaderLoadException.phpÚ  MDÐ\Ú  .En¶      ;   vendor/symfony/config/Exception/FileLoaderLoadException.php   MDÐ\   f®Ä#¶      N   vendor/symfony/config/Exception/FileLoaderImportCircularReferenceException.phpN  MDÐ\N  H—´¶      D   vendor/symfony/config/Exception/FileLocatorFileNotFoundException.php  MDÐ\  Gm+¶      #   vendor/symfony/config/composer.json¡  MDÐ\¡  �FšÝ¶      ,   vendor/symfony/config/ConfigCacheFactory.php  MDÐ\  Á{ƒ¶      ;   vendor/symfony/config/ResourceCheckerConfigCacheFactory.phpå  MDÐ\å  úLÑ¶      (   vendor/symfony/filesystem/Filesystem.phpÃr  MDÐ\Ãr  7TGz¶      <   vendor/symfony/filesystem/Exception/IOExceptionInterface.php¦  MDÐ\¦  ¨™i”¶      :   vendor/symfony/filesystem/Exception/ExceptionInterface.phpÐ  MDÐ\Ð  nßj¶      3   vendor/symfony/filesystem/Exception/IOException.php¼  MDÐ\¼  ãS¦K¶      @   vendor/symfony/filesystem/Exception/InvalidArgumentException.phpÏ  MDÐ\Ï  Ð*“Š¶      =   vendor/symfony/filesystem/Exception/FileNotFoundException.phpÉ  MDÐ\É  Økf¶      '   vendor/symfony/filesystem/composer.json(  MDÐ\(  �<¤¶         vendor/symfony/yaml/Parser.phpß°  MDÐ\ß°  SçI¤¶         vendor/symfony/yaml/Escaper.phpY  MDÐ\Y  ÜÈ­£¶      !   vendor/symfony/yaml/Unescaper.php[  MDÐ\[  b‘ØH¶         vendor/symfony/yaml/Dumper.php  MDÐ\  o1fß¶         vendor/symfony/yaml/Inline.php¤“  MDÐ\¤“  Áôn¶         vendor/symfony/yaml/Yaml.phpR  MDÐ\R  ÃeRƒ¶      +   vendor/symfony/yaml/Command/LintCommand.php¼  MDÐ\¼  ~Þfo¶      /   vendor/symfony/yaml/Exception/DumpException.phpÇ  MDÐ\Ç  Œ¶      4   vendor/symfony/yaml/Exception/ExceptionInterface.php»  MDÐ\»  Þ^KA¶      2   vendor/symfony/yaml/Exception/RuntimeException.phpå  MDÐ\å  ô_q¦¶      0   vendor/symfony/yaml/Exception/ParseException.php[  MDÐ\[  6�
Ì¶      '   vendor/symfony/yaml/Tag/TaggedValue.phpG  MDÐ\G  ÊiY¶      !   vendor/symfony/yaml/composer.json  MDÐ\  IìpÎ¶         vendor/symfony/debug/Debug.phpË  MDÐ\Ë  ›Ê= ¶      M   vendor/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.phpó  MDÐ\ó  Ê/.2¶      E   vendor/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php¿  MDÐ\¿  ÇiA¼¶      K   vendor/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.phpV  MDÐ\V  “feÿ¶      I   vendor/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.phpË  MDÐ\Ë  ,	ú™¶      )   vendor/symfony/debug/ExceptionHandler.phpga  MDÐ\ga  ö³L¶      )   vendor/symfony/debug/DebugClassLoader.phpöF  MDÐ\öF  §7%ð¶      (   vendor/symfony/debug/BufferingLogger.phpÜ  MDÐ\Ü  Ã'…¶      %   vendor/symfony/debug/ErrorHandler.php'h  MDÐ\'h  öQ¶      6   vendor/symfony/debug/Exception/FatalThrowableError.php  MDÐ\  ›’ª¶      7   vendor/symfony/debug/Exception/OutOfMemoryException.php¨  MDÐ\¨  ªøh¶      3   vendor/symfony/debug/Exception/FlattenException.php¦!  MDÐ\¦!  WÁÝ¶      ;   vendor/symfony/debug/Exception/UndefinedMethodException.phpc  MDÐ\c  ..ñ ¶      =   vendor/symfony/debug/Exception/UndefinedFunctionException.phph  MDÐ\h  êT|¶      6   vendor/symfony/debug/Exception/FatalErrorException.phpr
  MDÐ\r
  ¸í8Î¶      7   vendor/symfony/debug/Exception/SilencedErrorContext.phpq  MDÐ\q  ³^Ž¥¶      9   vendor/symfony/debug/Exception/ClassNotFoundException.phpy  MDÐ\y  ·<z¶      "   vendor/symfony/debug/composer.json�  MDÐ\�  # *�¶      (   vendor/symfony/console/ConsoleEvents.php!  MDÐ\!  Ÿ“Î©¶      &   vendor/symfony/console/Application.php1©  MDÐ\1©  ü„l¶      /   vendor/symfony/console/Logger/ConsoleLogger.php÷  MDÐ\÷  õd½¶      9   vendor/symfony/console/Input/StreamableInputInterface.phpi  MDÐ\i  õ™í¶      +   vendor/symfony/console/Input/ArrayInput.phpà  MDÐ\à  !š›‡¶      *   vendor/symfony/console/Input/ArgvInput.php/  MDÐ\/  Xåïx¶      .   vendor/symfony/console/Input/InputArgument.phpO  MDÐ\O  (ÐÀâ¶      4   vendor/symfony/console/Input/InputAwareInterface.php:  MDÐ\:  ± '´¶      ,   vendor/symfony/console/Input/InputOption.php¢  MDÐ\¢  œ«6]¶      0   vendor/symfony/console/Input/InputDefinition.phpÒ+  MDÐ\Ò+  º�‰Ì¶      ,   vendor/symfony/console/Input/StringInput.phpE	  MDÐ\E	  7zCÁ¶      &   vendor/symfony/console/Input/Input.phpr  MDÐ\r  U‚jx¶      /   vendor/symfony/console/Input/InputInterface.php±  MDÐ\±  õÎˆÚ¶      #   vendor/symfony/console/Terminal.php|  MDÐ\|  «Ã¸‡¶      8   vendor/symfony/console/Output/ConsoleOutputInterface.phpÞ  MDÐ\Þ  ¿ ƒ¶      0   vendor/symfony/console/Output/BufferedOutput.phpH  MDÐ\H   9¿Ô¶      1   vendor/symfony/console/Output/OutputInterface.php  MDÐ\  ’³é&¶      (   vendor/symfony/console/Output/Output.php{  MDÐ\{  îžø7¶      /   vendor/symfony/console/Output/ConsoleOutput.php¦  MDÐ\¦  À3q¶      ,   vendor/symfony/console/Output/NullOutput.phpn  MDÐ\n  t�D»¶      .   vendor/symfony/console/Output/StreamOutput.php(  MDÐ\(  Ý‹ô¶      ,   vendor/symfony/console/Style/OutputStyle.phpý  MDÐ\ý  1kz¶      -   vendor/symfony/console/Style/SymfonyStyle.php/  MDÐ\/  ìÜ^¶      /   vendor/symfony/console/Style/StyleInterface.phpO  MDÐ\O  üë®Q¶      2   vendor/symfony/console/Question/ChoiceQuestion.php²  MDÐ\²  ±a•|¶      ,   vendor/symfony/console/Question/Question.phpé  MDÐ\é  5ïÇ¶      8   vendor/symfony/console/Question/ConfirmationQuestion.php#  MDÐ\#  Ï�û¶      9   vendor/symfony/console/Formatter/OutputFormatterStyle.phpö  MDÐ\ö  ‡ý¶      B   vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php7  MDÐ\7  –.@Y¶      =   vendor/symfony/console/Formatter/OutputFormatterInterface.php¦  MDÐ\¦  #7êu¶      4   vendor/symfony/console/Formatter/OutputFormatter.php›  MDÐ\›  ,¶      >   vendor/symfony/console/Formatter/OutputFormatterStyleStack.phpµ	  MDÐ\µ	  —¿‹Ò¶      ?   vendor/symfony/console/CommandLoader/CommandLoaderInterface.php¹  MDÐ\¹  ¼NEe¶      ?   vendor/symfony/console/CommandLoader/ContainerCommandLoader.phpW  MDÐ\W  ÿŒÈž¶      =   vendor/symfony/console/CommandLoader/FactoryCommandLoader.php7  MDÐ\7  ‚m_ò¶      4   vendor/symfony/console/Descriptor/TextDescriptor.phpU0  MDÐ\U0  ÈŠµ.¶      <   vendor/symfony/console/Descriptor/ApplicationDescription.php  MDÐ\  ô5´Œ¶      3   vendor/symfony/console/Descriptor/XmlDescriptor.phpB$  MDÐ\B$  4Ñæ¶      9   vendor/symfony/console/Descriptor/DescriptorInterface.phpÙ  MDÐ\Ù  ×\“¶      0   vendor/symfony/console/Descriptor/Descriptor.phpd  MDÐ\d  ¡ÖåT¶      4   vendor/symfony/console/Descriptor/JsonDescriptor.php  MDÐ\  ¾ax¶      8   vendor/symfony/console/Descriptor/MarkdownDescriptor.php½  MDÐ\½  f 0>¶      /   vendor/symfony/console/Tester/CommandTester.phpë  MDÐ\ë  ¨Ðc}¶      3   vendor/symfony/console/Tester/ApplicationTester.php  MDÐ\  Â*¼[¶      0   vendor/symfony/console/Command/LockableTrait.phpä  MDÐ\ä  ŠîO¶      .   vendor/symfony/console/Command/HelpCommand.php?	  MDÐ\?	  ø�ä'¶      .   vendor/symfony/console/Command/ListCommand.phpÿ	  MDÐ\ÿ	  ñ›#¶      *   vendor/symfony/console/Command/Command.phpóK  MDÐ\óK  ÍÙtä¶      ,   vendor/symfony/console/Helper/TableStyle.phpv  MDÐ\v  ž�_Ó¶      7   vendor/symfony/console/Helper/SymfonyQuestionHelper.phpê  MDÐ\ê  /a¹¶      1   vendor/symfony/console/Helper/HelperInterface.phpp  MDÐ\p  ‘àñn¶      +   vendor/symfony/console/Helper/TableCell.phpp  MDÐ\p  Ë�¥À¶      /   vendor/symfony/console/Helper/ProcessHelper.phpó  MDÐ\ó  °|_¶      0   vendor/symfony/console/Helper/TableSeparator.php  MDÐ\  &Šª¶      1   vendor/symfony/console/Helper/FormatterHelper.php  MDÐ\  *+Ãç¶      -   vendor/symfony/console/Helper/ProgressBar.php=C  MDÐ\=C  ‘Û£¶      6   vendor/symfony/console/Helper/DebugFormatterHelper.phpH  MDÐ\H  ]R`A¶      +   vendor/symfony/console/Helper/HelperSet.phpÁ	  MDÐ\Á	  Ã3C¶      2   vendor/symfony/console/Helper/InputAwareHelper.phpë  MDÐ\ë  ¼�¶      0   vendor/symfony/console/Helper/QuestionHelper.php/=  MDÐ\/=  æ¤9`¶      (   vendor/symfony/console/Helper/Helper.php°  MDÐ\°  }Ku·¶      '   vendor/symfony/console/Helper/Table.php\N  MDÐ\\N  gåø	¶      3   vendor/symfony/console/Helper/ProgressIndicator.phpØ  MDÐ\Ø  VßÆï¶      2   vendor/symfony/console/Helper/DescriptorHelper.php"
  MDÐ\"
  Ý`Èj¶      D   vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.phpš  MDÐ\š  A|\¶      3   vendor/symfony/console/Exception/LogicException.phpª  MDÐ\ª  SML�¶      ;   vendor/symfony/console/Exception/InvalidOptionException.phpü  MDÐ\ü  í“;¶      7   vendor/symfony/console/Exception/ExceptionInterface.php–  MDÐ\–  ãëÚU¶      5   vendor/symfony/console/Exception/RuntimeException.php®  MDÐ\®  ¶*b¶      =   vendor/symfony/console/Exception/InvalidArgumentException.php¾  MDÐ\¾  îu i¶      =   vendor/symfony/console/Exception/CommandNotFoundException.php½  MDÐ\½  .—o£¶      6   vendor/symfony/console/Event/ConsoleTerminateEvent.php  MDÐ\  {eð¶      2   vendor/symfony/console/Event/ConsoleErrorEvent.phpÅ  MDÐ\Å  l›�¶      4   vendor/symfony/console/Event/ConsoleCommandEvent.php%  MDÐ\%  {Ë¾à¶      -   vendor/symfony/console/Event/ConsoleEvent.phpÄ  MDÐ\Ä  xS*¶      6   vendor/symfony/console/Event/ConsoleExceptionEvent.phpS  MDÐ\S  ¼ˆÃÖ¶      $   vendor/symfony/console/composer.jsonÂ  MDÐ\Â  fÝ_¶      6   vendor/symfony/console/EventListener/ErrorListener.phpØ
  MDÐ\Ø
  ¥2¶      @   vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.phpI  MDÐ\I  æ(~¶      F   vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php9  MDÐ\9  >|zK¶      @   vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.phpþI  MDÐ\þI  ÒÖë¶      -   vendor/symfony/polyfill-mbstring/Mbstring.phpüg  MDÐ\üg  ÏÅàn¶      .   vendor/symfony/polyfill-mbstring/bootstrap.phpI  MDÐ\I  @ï¶      .   vendor/symfony/polyfill-mbstring/composer.jsonp  MDÐ\p  ËTv+¶      8   vendor/linways/linways-base/test/testMysqlRealEscape.phpf  MDÐ\f  «ëÙ¶      8   vendor/linways/linways-base/test/testTaskQueueWorker.phpÒ  MDÐ\Ò  ÛøYf¶      /   vendor/linways/linways-base/test/testLogger.phpn  MDÐ\n  ÿzQã¶      6   vendor/linways/linways-base/test/FeatureToggleTest.phpæ  MDÐ\æ  |&M¶      -   vendor/linways/linways-base/test/testHook.phpr   MDÐ\r   ‚"Ñ�¶      =   vendor/linways/linways-base/test/toggles/ExperimentToggle.php‚  MDÐ\‚  Ô”Sö¶      :   vendor/linways/linways-base/test/toggles/ReleaseToggle.php­  MDÐ\­  }F¹Ž¶      5   vendor/linways/linways-base/test/testLiwnaysHooks.phpÅ   MDÐ\Å   ­F×ú¶      (   vendor/linways/linways-base/test/web.php–   MDÐ\–   	¡Ï¶      5   vendor/linways/linways-base/test/testemailservice.php<  MDÐ\<  õÿÑf¶      2   vendor/linways/linways-base/test/testTaskQueue.phpÚ  MDÐ\Ú  ¢ß½¶      )   vendor/linways/linways-base/composer.json#  MDÐ\#  ÜîAË¶      >   vendor/linways/linways-base/src/com/linways/base/dto/Email.phpÏ  MDÐ\Ï  �a¶      @   vendor/linways/linways-base/src/com/linways/base/dto/EmailCC.php  MDÐ\  Ñ�UÊ¶      H   vendor/linways/linways-base/src/com/linways/base/dto/EmailAttachment.phpÈ   MDÐ\È   Aƒà—¶      @   vendor/linways/linways-base/src/com/linways/base/dto/EmailTo.php  MDÐ\  –³¶      @   vendor/linways/linways-base/src/com/linways/base/dto/sms/SMS.phpÝ  MDÐ\Ý   ØªD¶      V   vendor/linways/linways-base/src/com/linways/base/dto/sms/SMSProviderAttributeValue.phpŸ  MDÐ\Ÿ  »‰¶      H   vendor/linways/linways-base/src/com/linways/base/dto/sms/SMSResponse.php‹   MDÐ\‹   Ìzç¶      N   vendor/linways/linways-base/src/com/linways/base/dto/sms/SMSProviderModule.php   MDÐ\   �–{¶      Q   vendor/linways/linways-base/src/com/linways/base/dto/sms/SMSProviderAttribute.phpÕ  MDÐ\Õ  ¬ö†¶      H   vendor/linways/linways-base/src/com/linways/base/dto/sms/SMSProvider.php|  MDÐ\|  ¤¢¶      D   vendor/linways/linways-base/src/com/linways/base/dto/PageSupport.phpk  MDÐ\k  vÞŒ±¶      @   vendor/linways/linways-base/src/com/linways/base/dto/BaseDTO.php¸  MDÐ\¸  }¨ÙÖ¶      B   vendor/linways/linways-base/src/com/linways/base/dto/EmailFrom.php  MDÐ\  h`¾¶      F   vendor/linways/linways-base/src/com/linways/base/dto/AjaxPaginator.php‰	  MDÐ\‰	  Uqö—¶      D   vendor/linways/linways-base/src/com/linways/base/dto/MySqlResult.phpe   MDÐ\e   |Ñ¼l¶      ?   vendor/linways/linways-base/src/com/linways/base/dto/Toggle.phpÎ  MDÐ\Î  Í•CÍ¶      J   vendor/linways/linways-base/src/com/linways/base/testcase/BaseTestCase.php›  MDÐ\›  MÕßŸ¶      L   vendor/linways/linways-base/src/com/linways/base/messages/CommonMessages.php  MDÐ\  T‰’µ¶      J   vendor/linways/linways-base/src/com/linways/base/response/BaseResponse.phpº   MDÐ\º   S€�®¶      C   vendor/linways/linways-base/src/com/linways/base/cache/LinCache.phpG  MDÐ\G  ä�Ÿ¶      N   vendor/linways/linways-base/src/com/linways/base/connection/MySqlConnector.php4  MDÐ\4  ´ñãˆ¶      O   vendor/linways/linways-base/src/com/linways/base/connection/IMySqlConnector.php1  MDÐ\1  ,gK–¶      J   vendor/linways/linways-base/src/com/linways/base/connection/MySqlQuery.phpö  MDÐ\ö  kYÏe¶      D   vendor/linways/linways-base/src/com/linways/base/util/StringUtil.php  MDÐ\  çùU¶      F   vendor/linways/linways-base/src/com/linways/base/util/CalendarUtil.phpf  MDÐ\f  ?è#Ì¶      D   vendor/linways/linways-base/src/com/linways/base/util/CommonUtil.php¤  MDÐ\¤  ›Õå�¶      E   vendor/linways/linways-base/src/com/linways/base/util/RequestUtil.phpÌ  MDÐ\Ì  ª¢ô>¶      D   vendor/linways/linways-base/src/com/linways/base/util/MapperUtil.php-$  MDÐ\-$  ë¶      L   vendor/linways/linways-base/src/com/linways/base/util/MakeSingletonTrait.phpå  MDÐ\å  �n¯²¶      D   vendor/linways/linways-base/src/com/linways/base/util/ObjectUtil.php›  MDÐ\›  šŽ,·¶      C   vendor/linways/linways-base/src/com/linways/base/util/PhoneUtil.phpG  MDÐ\G  çž'"¶      G   vendor/linways/linways-base/src/com/linways/base/util/SecurityUtils.php 
  MDÐ\ 
  KÖMì¶      C   vendor/linways/linways-base/src/com/linways/base/util/MySqlUtil.phpÎ
  MDÐ\Î
  ~¶;¶      B   vendor/linways/linways-base/src/com/linways/base/mapper/Result.phpÀ  MDÐ\À  «Ïü¥¶      E   vendor/linways/linways-base/src/com/linways/base/mapper/ResultMap.php0  MDÐ\0  òa›<¶      C   vendor/linways/linways-base/src/com/linways/base/mapper/IMapper.php²   MDÐ\²   Ú<PL¶      M   vendor/linways/linways-base/src/com/linways/base/soap/soap_transport_http.php…é  MDÐ\…é  G¼F¶      G   vendor/linways/linways-base/src/com/linways/base/soap/nusoap_client.php¥˜  MDÐ\¥˜  "‚/¶      J   vendor/linways/linways-base/src/com/linways/base/soap/nusoap_xmlschema.phpk‰  MDÐ\k‰  ‡øl¶      E   vendor/linways/linways-base/src/com/linways/base/soap/soap_server.phpu   MDÐ\u   ‡ÞÂ1¶      E   vendor/linways/linways-base/src/com/linways/base/soap/nusoap_base.php”Š  MDÐ\”Š  )?¶      E   vendor/linways/linways-base/src/com/linways/base/soap/soap_parser.phpt   MDÐ\t   ‰»^ò¶      D   vendor/linways/linways-base/src/com/linways/base/soap/soap_fault.phpv   MDÐ\v   tb~¶      A   vendor/linways/linways-base/src/com/linways/base/soap/soapval.php?
  MDÐ\?
  Ÿàç©¶      >   vendor/linways/linways-base/src/com/linways/base/soap/wsdl.php²… MDÐ\²… :¶      F   vendor/linways/linways-base/src/com/linways/base/soap/nusoap_fault.phpk	  MDÐ\k	  Š/[¶      G   vendor/linways/linways-base/src/com/linways/base/soap/nusoap_server.phpïº  MDÐ\ïº  Z[ûç¶      G   vendor/linways/linways-base/src/com/linways/base/soap/nusoap_parser.php`u  MDÐ\`u  ýª™Þ¶      C   vendor/linways/linways-base/src/com/linways/base/soap/XMLSchema.phpw   MDÐ\w   _¯	¶      D   vendor/linways/linways-base/src/com/linways/base/soap/soapclient.phpT   MDÐ\T    · q¶      F   vendor/linways/linways-base/src/com/linways/base/constant/UserType.phpy  MDÐ\y  É,’#¶      J   vendor/linways/linways-base/src/com/linways/base/toggles/FeatureToggle.php,  MDÐ\,  ^úh7¶      T   vendor/linways/linways-base/src/com/linways/base/toggles/AbstractToggleMigration.php4  MDÐ\4  õ;:¶      S   vendor/linways/linways-base/src/com/linways/base/toggles/FeatureToggleInterface.php—   MDÐ\—   L2#Ž¶      G   vendor/linways/linways-base/src/com/linways/base/hooks/LinwaysHooks.php5  MDÐ\5  }ö�ô¶      D   vendor/linways/linways-base/src/com/linways/base/mcrypt/CryptAES.php  MDÐ\  Â‚«¶      @   vendor/linways/linways-base/src/com/linways/base/queue/Queue.php×  MDÐ\×  …÷œ¶      H   vendor/linways/linways-base/src/com/linways/base/request/BaseRequest.phpC  MDÐ\C  šåYÍ¶      I   vendor/linways/linways-base/src/com/linways/base/service/EmailService.phpØ  MDÐ\Ø  ±Xo¶      I   vendor/linways/linways-base/src/com/linways/base/helper/ResultHandler.phpS  MDÐ\S  *�³÷¶      K   vendor/linways/linways-base/src/com/linways/base/exception/SMSException.phpì  MDÐ\ì  ÖlMí¶      K   vendor/linways/linways-base/src/com/linways/base/exception/SQLException.php^   MDÐ\^   @ç…a¶      Q   vendor/linways/linways-base/src/com/linways/base/exception/TaskQueueException.phpË  MDÐ\Ë  ÓTçç¶      M   vendor/linways/linways-base/src/com/linways/base/exception/EmailException.phpm  MDÐ\m  ¶SÅ¶      N   vendor/linways/linways-base/src/com/linways/base/exception/MapperException.phpg  MDÐ\g  3ÞÑ:¶      L   vendor/linways/linways-base/src/com/linways/base/exception/CoreException.php»  MDÐ\»  Éùs¶      K   vendor/linways/linways-base/src/com/linways/base/logging/ElasticHandler.phpÁ  MDÐ\Á  €† ¶      C   vendor/linways/linways-base/src/com/linways/base/logging/Logger.phpõ  MDÐ\õ  Ä5y†¶      G   vendor/linways/linways-base/src/com/linways/base/logging/BaseEntity.phpn  MDÐ\n  ¢r“?¶      D   vendor/linways/linways-base/src/com/linways/base/email/PHPMailer.phpO×  MDÐ\O×  ÝJŽ¶      ?   vendor/linways/linways-base/src/com/linways/base/email/SMTP.php›ˆ  MDÐ\›ˆ  iz-¶      8   vendor/linways/nucleus-core/test/db_conf/ams_db_conf.phpe   MDÐ\e   xº9C¶      <   vendor/linways/nucleus-core/test/db_conf/nucleus_db_conf.phpˆ   MDÐ\ˆ   8ÑÞ¶      ?   vendor/linways/nucleus-core/test/utils/UserServiceUtilsTest.php—  MDÐ\—  ©ƒ¶      7   vendor/linways/nucleus-core/test/scripts/basic_task.phpø  MDÐ\ø  tÁûˆ¶      =   vendor/linways/nucleus-core/test/scripts/testServiceClass.phpÅ  MDÐ\Å  îæ¶      @   vendor/linways/nucleus-core/test/scripts/basic_schedule_task.phpD   MDÐ\D   9Pý¶      B   vendor/linways/nucleus-core/test/service/PermissionServiceTest.phpó  MDÐ\ó  _l€¶      =   vendor/linways/nucleus-core/test/service/AddonServiceTest.php¥*  MDÐ\¥*  ôëžÏ¶      H   vendor/linways/nucleus-core/test/service/RecurringExpenseServiceTest.phpæ  MDÐ\æ  œ¦˜¶      E   vendor/linways/nucleus-core/test/service/ExpenseReportServiceTest.php"	  MDÐ\"	  Â.f«¶      A   vendor/linways/nucleus-core/test/service/user/UserServiceTest.phpí  MDÐ\í  XûY·¶      D   vendor/linways/nucleus-core/test/service/SingleSignonServiceTest.phpŠ  MDÐ\Š  },’þ¶      E   vendor/linways/nucleus-core/test/service/ConfigurationServiceTest.php4  MDÐ\4  \õñ¶      F   vendor/linways/nucleus-core/test/service/APIAccessTokenServiceTest.php  MDÐ\  Q'Ta¶      F   vendor/linways/nucleus-core/test/service/AddonDataStoreServiceTest.php›  MDÐ\›  Š‚2µ¶      =   vendor/linways/nucleus-core/test/service/AddonTestHelpers.php(  MDÐ\(  VF2±¶      H   vendor/linways/nucleus-core/test/service/PermissionToggleServiceTest.php·  MDÐ\·  J�@Ñ¶      ?   vendor/linways/nucleus-core/test/service/CollegeServiceTest.phpÿ  MDÐ\ÿ  †dr‰¶      ?   vendor/linways/nucleus-core/test/service/ExpenseServiceTest.php  MDÐ\  ¶•À¶      G   vendor/linways/nucleus-core/test/service/ExpenseCategoryServiceTest.phpŸ  MDÐ\Ÿ  ~‡¶      A   vendor/linways/nucleus-core/test/service/SchedulerServiceTest.php   MDÐ\   m�ú,¶      >   vendor/linways/nucleus-core/test/service/ClientServiceTest.php 	  MDÐ\ 	  ¨h8Œ¶      )   vendor/linways/nucleus-core/composer.json’  MDÐ\’  ’HF¶      F   vendor/linways/nucleus-core/src/com/linways/nucleus/core/dto/Addon.phpâ  MDÐ\â  ~[ª¶      T   vendor/linways/nucleus-core/src/com/linways/nucleus/core/dto/AddonAttributeValue.phpð  MDÐ\ð  œÃùE¶      O   vendor/linways/nucleus-core/src/com/linways/nucleus/core/dto/AddonAttribute.phpÁ  MDÐ\Á  Ž’¿™¶      W   vendor/linways/nucleus-core/src/com/linways/nucleus/core/dto/ApiOrganizationDetails.php#  MDÐ\#  1£ö¶      M   vendor/linways/nucleus-core/src/com/linways/nucleus/core/dto/AddonEnabled.phpå  MDÐ\å  Kå´¶      J   vendor/linways/nucleus-core/src/com/linways/nucleus/core/dto/user/User.phpí   MDÐ\í   6öi)¶      O   vendor/linways/nucleus-core/src/com/linways/nucleus/core/dto/AddonDataStore.php±  MDÐ\±  À,.¦¶      W   vendor/linways/nucleus-core/src/com/linways/nucleus/core/dto/ExpenseRecurringAmount.phpc  MDÐ\c  ‰\éÕ¶      H   vendor/linways/nucleus-core/src/com/linways/nucleus/core/dto/Expense.php  MDÐ\  ‰ý
¶      N   vendor/linways/nucleus-core/src/com/linways/nucleus/core/dto/ScheduledTask.phpA  MDÐ\A  I5þD¶      H   vendor/linways/nucleus-core/src/com/linways/nucleus/core/dto/College.phpŒ  MDÐ\Œ  ßm‹¶      O   vendor/linways/nucleus-core/src/com/linways/nucleus/core/dto/ProductVariant.phpß   MDÐ\ß   Oä¶      Q   vendor/linways/nucleus-core/src/com/linways/nucleus/core/dto/ExpenseRecurring.php'  MDÐ\'  r(K¶      P   vendor/linways/nucleus-core/src/com/linways/nucleus/core/dto/ExpenseCategory.phpñ   MDÐ\ñ   ì—b¶      P   vendor/linways/nucleus-core/src/com/linways/nucleus/core/dto/SingleSignonLog.phpæ   MDÐ\æ   Ùì0¶      G   vendor/linways/nucleus-core/src/com/linways/nucleus/core/dto/Client.php@  MDÐ\@  €ÁJH¶      Z   vendor/linways/nucleus-core/src/com/linways/nucleus/core/constants/PermissionConstants.phpâ   MDÐ\â   ýûN‘¶      L   vendor/linways/nucleus-core/src/com/linways/nucleus/core/constants/Scope.phpm   MDÐ\m   \™�Ê¶      [   vendor/linways/nucleus-core/src/com/linways/nucleus/core/constants/SingleSignonConstant.phpX  MDÐ\X  pàRÁ¶      W   vendor/linways/nucleus-core/src/com/linways/nucleus/core/mapper/ClientServiceMapper.phpÞ  MDÐ\Þ  šÙ‚¶      Y   vendor/linways/nucleus-core/src/com/linways/nucleus/core/mapper/ScheduleServiceMapper.php£  MDÐ\£  §0ä¶      X   vendor/linways/nucleus-core/src/com/linways/nucleus/core/mapper/CollegeServiceMapper.phpÖ  MDÐ\Ö  é¿ž¶      X   vendor/linways/nucleus-core/src/com/linways/nucleus/core/mapper/AddonDataStoreMapper.php  MDÐ\  ¬NìÓ¶      _   vendor/linways/nucleus-core/src/com/linways/nucleus/core/mapper/ProductVariantServiceMapper.php¼  MDÐ\¼  +\fù¶      X   vendor/linways/nucleus-core/src/com/linways/nucleus/core/mapper/ExpenseServiceMapper.phpe  MDÐ\e  >²ÞÒ¶      V   vendor/linways/nucleus-core/src/com/linways/nucleus/core/mapper/AddonServiceMapper.php-  MDÐ\-  àir*¶      a   vendor/linways/nucleus-core/src/com/linways/nucleus/core/mapper/RecurringExpenseServiceMapper.phpH	  MDÐ\H	  �§yŠ¶      X   vendor/linways/nucleus-core/src/com/linways/nucleus/core/utils/user/UserServiceUtils.php¡  MDÐ\¡  Vaò
¶      L   vendor/linways/nucleus-core/src/com/linways/nucleus/core/utils/DateUtils.phpx  MDÐ\x  û÷—T¶      N   vendor/linways/nucleus-core/src/com/linways/nucleus/core/utils/StringUtils.php  MDÐ\  $˜›¶      K   vendor/linways/nucleus-core/src/com/linways/nucleus/core/utils/JWTUtils.php¡  MDÐ\¡  zFF€¶      T   vendor/linways/nucleus-core/src/com/linways/nucleus/core/request/ScheduleRequest.phpx  MDÐ\x  #¥´Ô¶      Y   vendor/linways/nucleus-core/src/com/linways/nucleus/core/service/ConfigurationService.phpo  MDÐ\o  _¯ÿ¶      V   vendor/linways/nucleus-core/src/com/linways/nucleus/core/service/PermissionService.phpJ  MDÐ\J  ÖC£*¶      \   vendor/linways/nucleus-core/src/com/linways/nucleus/core/service/RecurringExpenseService.php/!  MDÐ\/!  wJÂ2¶      Q   vendor/linways/nucleus-core/src/com/linways/nucleus/core/service/AddonService.php³:  MDÐ\³:  ¨ôp¶      U   vendor/linways/nucleus-core/src/com/linways/nucleus/core/service/user/UserService.php™  MDÐ\™  yüîþ¶      [   vendor/linways/nucleus-core/src/com/linways/nucleus/core/service/ExpenseCategoryService.phpF	  MDÐ\F	  j¿À�¶      X   vendor/linways/nucleus-core/src/com/linways/nucleus/core/service/SingleSignonService.phpâ	  MDÐ\â	  ©Ð¶      Z   vendor/linways/nucleus-core/src/com/linways/nucleus/core/service/ProductVariantService.php'  MDÐ\'  eUˆc¶      Z   vendor/linways/nucleus-core/src/com/linways/nucleus/core/service/AddonAttributeService.php  MDÐ\  òt@•¶      \   vendor/linways/nucleus-core/src/com/linways/nucleus/core/service/PermissionToggleService.php  MDÐ\  Ó€¶      S   vendor/linways/nucleus-core/src/com/linways/nucleus/core/service/ExpenseService.phpâ  MDÐ\â  ¨É·ù¶      Y   vendor/linways/nucleus-core/src/com/linways/nucleus/core/service/AddonDataStoreSevice.php[  MDÐ\[  Ê{¶      Z   vendor/linways/nucleus-core/src/com/linways/nucleus/core/service/APIAccessTokenService.php£  MDÐ\£  6Ÿ„¶      P   vendor/linways/nucleus-core/src/com/linways/nucleus/core/service/BaseService.php�
  MDÐ\�
  ¼Drç¶      R   vendor/linways/nucleus-core/src/com/linways/nucleus/core/service/ClientService.php
  MDÐ\
  CÆ6ð¶      R   vendor/linways/nucleus-core/src/com/linways/nucleus/core/service/ReportService.php  MDÐ\  lý§¶      U   vendor/linways/nucleus-core/src/com/linways/nucleus/core/service/SchedulerService.phpø  MDÐ\ø  ¾™ÁS¶      S   vendor/linways/nucleus-core/src/com/linways/nucleus/core/service/CollegeService.phpÎ(  MDÐ\Î(  ;§’¶      _   vendor/linways/nucleus-core/src/com/linways/nucleus/core/exception/TaskQueueWorkerException.php  MDÐ\  òwŠ¶      Y   vendor/linways/nucleus-core/src/com/linways/nucleus/core/exception/SchedulerException.php  MDÐ\  …CúÕ¶      W   vendor/linways/nucleus-core/src/com/linways/nucleus/core/exception/NucleusException.php  MDÐ\  …À*¶      2   vendor/dragonmantank/cron-expression/composer.json·  MDÐ\·  =�Ý¿¶      >   vendor/dragonmantank/cron-expression/src/Cron/FieldFactory.phpU  MDÐ\U  d‘"«¶      A   vendor/dragonmantank/cron-expression/src/Cron/DayOfMonthField.php  MDÐ\  SðD¶      ;   vendor/dragonmantank/cron-expression/src/Cron/YearField.php  MDÐ\  ÌÈ‡¶      @   vendor/dragonmantank/cron-expression/src/Cron/FieldInterface.php?  MDÐ\?  šYäÎ¶      ?   vendor/dragonmantank/cron-expression/src/Cron/AbstractField.phpF  MDÐ\F  Ô×,²¶      >   vendor/dragonmantank/cron-expression/src/Cron/MinutesField.php2  MDÐ\2  UîuQ¶      <   vendor/dragonmantank/cron-expression/src/Cron/MonthField.phpÜ  MDÐ\Ü  »Š¥O¶      <   vendor/dragonmantank/cron-expression/src/Cron/HoursField.php¤  MDÐ\¤  œ	‡Ñ¶      @   vendor/dragonmantank/cron-expression/src/Cron/CronExpression.php47  MDÐ\47  é¶      @   vendor/dragonmantank/cron-expression/src/Cron/DayOfWeekField.php.  MDÐ\.  ñ-D©¶      %   vendor/webmozart/assert/composer.jsonU  MDÐ\U  oÖU¡¶      &   vendor/webmozart/assert/src/Assert.phpÀ¡  MDÐ\À¡  Þ€ò¶      %   vendor/phpspec/prophecy/composer.jsonT  MDÐ\T  ÈHï‰¶      ;   vendor/phpspec/prophecy/src/Prophecy/Comparator/Factory.phpÔ  MDÐ\Ô  Öˆi§¶      E   vendor/phpspec/prophecy/src/Prophecy/Comparator/ClosureComparator.phpK  MDÐ\K  )RQã¶      F   vendor/phpspec/prophecy/src/Prophecy/Comparator/ProphecyComparator.phps  MDÐ\s  �¤hÇ¶      1   vendor/phpspec/prophecy/src/Prophecy/Argument.phpÇ  MDÐ\Ç  AT¶      @   vendor/phpspec/prophecy/src/Prophecy/Doubler/DoubleInterface.phpâ  MDÐ\â  8d�j¶      ;   vendor/phpspec/prophecy/src/Prophecy/Doubler/LazyDouble.phpF  MDÐ\F  ¼ël¦¶      >   vendor/phpspec/prophecy/src/Prophecy/Doubler/NameGenerator.phpŠ  MDÐ\Š  õÑ7¶      8   vendor/phpspec/prophecy/src/Prophecy/Doubler/Doubler.php�  MDÐ\�  8]Õ^¶      G   vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassCreator.phpÔ  MDÐ\Ô  ä?Br¶      N   vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ReflectionInterface.phpò  MDÐ\ò  çûªå¶      M   vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassCodeGenerator.php  MDÐ\  TºH¶      L   vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/TypeHintReference.phpí  MDÐ\í  tí>Ë¶      F   vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassMirror.phpf  MDÐ\f  ei¶      L   vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ArgumentNode.phpÀ  MDÐ\À  |«¶      I   vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ClassNode.php¬  MDÐ\¬  <ü[¶      J   vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/MethodNode.php  MDÐ\  ó5ÍH¶      >   vendor/phpspec/prophecy/src/Prophecy/Doubler/CachedDoubler.phpƒ  MDÐ\ƒ  Ì‡gè¶      L   vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/TraversablePatch.php	  MDÐ\	  §€jN¶      H   vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/KeywordPatch.php$  MDÐ\$  «AA‰¶      O   vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ClassPatchInterface.phpl  MDÐ\l  )Š5:¶      [   vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ReflectionClassNewInstancePatch.phpp  MDÐ\p  x¤¿ˆ¶      N   vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/HhvmExceptionPatch.phpÑ  MDÐ\Ñ  x“Â^¶      L   vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/SplFileInfoPatch.php~  MDÐ\~  ÅÏT�¶      J   vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ThrowablePatch.phpa	  MDÐ\a	  ÿÓ÷‚¶      P   vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ProphecySubjectPatch.php  MDÐ\  öÏâZ¶      S   vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/DisableConstructorPatch.php’  MDÐ\’  :0`ò¶      J   vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/MagicCallPatch.php
  MDÐ\
  _aGk¶      8   vendor/phpspec/prophecy/src/Prophecy/Util/StringUtil.php£
  MDÐ\£
  ¼Z~š¶      8   vendor/phpspec/prophecy/src/Prophecy/Util/ExportUtil.phpP  MDÐ\P  2¼qÆ¶      G   vendor/phpspec/prophecy/src/Prophecy/Argument/Token/ArrayEntryToken.php”  MDÐ\”  ÞJú:¶      G   vendor/phpspec/prophecy/src/Prophecy/Argument/Token/ExactValueToken.php¡  MDÐ\¡   …3�¶      G   vendor/phpspec/prophecy/src/Prophecy/Argument/Token/LogicalAndToken.phpø  MDÐ\ø   Nãv¶      F   vendor/phpspec/prophecy/src/Prophecy/Argument/Token/AnyValuesToken.phpÚ  MDÐ\Ú  ÖbN/¶      E   vendor/phpspec/prophecy/src/Prophecy/Argument/Token/CallbackToken.php,  MDÐ\,  cRÊÌ¶      K   vendor/phpspec/prophecy/src/Prophecy/Argument/Token/IdenticalValueToken.phpæ  MDÐ\æ  �¯Êý¶      A   vendor/phpspec/prophecy/src/Prophecy/Argument/Token/TypeToken.php¥  MDÐ\¥  ®næ\¶      H   vendor/phpspec/prophecy/src/Prophecy/Argument/Token/ObjectStateToken.php9
  MDÐ\9
  E²ë.¶      E   vendor/phpspec/prophecy/src/Prophecy/Argument/Token/AnyValueToken.php«  MDÐ\«  F�h¶      F   vendor/phpspec/prophecy/src/Prophecy/Argument/Token/TokenInterface.php�  MDÐ\�  Ù°Š¼¶      G   vendor/phpspec/prophecy/src/Prophecy/Argument/Token/LogicalNotToken.php  MDÐ\  Ü¼r¶      L   vendor/phpspec/prophecy/src/Prophecy/Argument/Token/ArrayEveryEntryToken.php’  MDÐ\’  pbø�¶      K   vendor/phpspec/prophecy/src/Prophecy/Argument/Token/StringContainsToken.php  MDÐ\  ¯ô|¶      G   vendor/phpspec/prophecy/src/Prophecy/Argument/Token/ArrayCountToken.phpá  MDÐ\á  ‚4®Ì¶      M   vendor/phpspec/prophecy/src/Prophecy/Argument/Token/ApproximateValueToken.php‡  MDÐ\‡  #Iú¶      C   vendor/phpspec/prophecy/src/Prophecy/Argument/ArgumentsWildcard.php4	  MDÐ\4	  A;K2¶      0   vendor/phpspec/prophecy/src/Prophecy/Prophet.phpb  MDÐ\b  ¹Z`¶      R   vendor/phpspec/prophecy/src/Prophecy/PhpDocumentor/MethodTagRetrieverInterface.phpß  MDÐ\ß  ˜;‰¶      H   vendor/phpspec/prophecy/src/Prophecy/PhpDocumentor/ClassTagRetriever.phpD  MDÐ\D  d9ÁÏ¶      T   vendor/phpspec/prophecy/src/Prophecy/PhpDocumentor/ClassAndInterfaceTagRetriever.phpx  MDÐ\x  rÐ¬¶      N   vendor/phpspec/prophecy/src/Prophecy/PhpDocumentor/LegacyClassTagRetriever.phpo  MDÐ\o  u9‡¶      E   vendor/phpspec/prophecy/src/Prophecy/Prediction/NoCallsPrediction.phpå  MDÐ\å  L9%¶      F   vendor/phpspec/prophecy/src/Prophecy/Prediction/CallbackPrediction.php“  MDÐ\“  Vb{Î¶      B   vendor/phpspec/prophecy/src/Prophecy/Prediction/CallPrediction.phpQ	  MDÐ\Q	  Iæ“é¶      G   vendor/phpspec/prophecy/src/Prophecy/Prediction/CallTimesPrediction.php“  MDÐ\“  XŠü¶      G   vendor/phpspec/prophecy/src/Prophecy/Prediction/PredictionInterface.phpŸ  MDÐ\Ÿ  `IE¶      8   vendor/phpspec/prophecy/src/Prophecy/Call/CallCenter.php*  MDÐ\*  ˆ.wã¶      2   vendor/phpspec/prophecy/src/Prophecy/Call/Call.php›  MDÐ\›  8VuÙ¶      C   vendor/phpspec/prophecy/src/Prophecy/Prophecy/RevealerInterface.phpH  MDÐ\H  ‡gZ¶¶      @   vendor/phpspec/prophecy/src/Prophecy/Prophecy/ObjectProphecy.php‡  MDÐ\‡  ß5D¶      J   vendor/phpspec/prophecy/src/Prophecy/Prophecy/ProphecySubjectInterface.phpß  MDÐ\ß  i²¶      :   vendor/phpspec/prophecy/src/Prophecy/Prophecy/Revealer.php�  MDÐ\�  jÉÉ¸¶      @   vendor/phpspec/prophecy/src/Prophecy/Prophecy/MethodProphecy.phpq1  MDÐ\q1  †?¶      C   vendor/phpspec/prophecy/src/Prophecy/Prophecy/ProphecyInterface.php,  MDÐ\,  ¡W¶      A   vendor/phpspec/prophecy/src/Prophecy/Promise/PromiseInterface.phpK  MDÐ\K  ¾¬…ë¶      F   vendor/phpspec/prophecy/src/Prophecy/Promise/ReturnArgumentPromise.php'  MDÐ\'  â(«³¶      @   vendor/phpspec/prophecy/src/Prophecy/Promise/CallbackPromise.php�  MDÐ\�  [žÜ¶      >   vendor/phpspec/prophecy/src/Prophecy/Promise/ReturnPromise.php  MDÐ\  çØ�ä¶      =   vendor/phpspec/prophecy/src/Prophecy/Promise/ThrowPromise.php]  MDÐ\]  £Î	~¶      R   vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/MethodNotFoundException.phpä  MDÐ\ä  èX¶      U   vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/InterfaceNotFoundException.phpà  MDÐ\à  õ¡…ý¶      U   vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/ReturnByReferenceException.php�  MDÐ\�  ýª¶      K   vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/DoublerException.php—  MDÐ\—  ÃZ^¶      P   vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/ClassCreatorException.phpµ  MDÐ\µ  77/%¶      J   vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/DoubleException.php©  MDÐ\©  zéFƒ¶      Q   vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/ClassNotFoundException.phpÅ  MDÐ\Å  h+î¤¶      O   vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/ClassMirrorException.phpª  MDÐ\ª  Û‰?¶      W   vendor/phpspec/prophecy/src/Prophecy/Exception/Doubler/MethodNotExtendableException.phpD  MDÐ\D  ÷pæ¶      V   vendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/UnexpectedCallsException.php,  MDÐ\,  ��õa¶      [   vendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/UnexpectedCallsCountException.php  MDÐ\  æ	Æ¶      Q   vendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/PredictionException.php�  MDÐ\�  2T¢Ñ¶      W   vendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/FailedPredictionException.phpJ  MDÐ\J  ~ÐãD¶      N   vendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/NoCallsException.phpÚ  MDÐ\Ú  Ál<�¶      P   vendor/phpspec/prophecy/src/Prophecy/Exception/Prediction/AggregateException.php   MDÐ\   ;¼ð¶      K   vendor/phpspec/prophecy/src/Prophecy/Exception/InvalidArgumentException.php•  MDÐ\•  ¨gì¶      O   vendor/phpspec/prophecy/src/Prophecy/Exception/Call/UnexpectedCallException.phpÖ  MDÐ\Ö  £Áó¸¶      S   vendor/phpspec/prophecy/src/Prophecy/Exception/Prophecy/MethodProphecyException.php)  MDÐ\)  Fù¢4¶      M   vendor/phpspec/prophecy/src/Prophecy/Exception/Prophecy/ProphecyException.php™  MDÐ\™  Üê$Ï¶      S   vendor/phpspec/prophecy/src/Prophecy/Exception/Prophecy/ObjectProphecyException.php  MDÐ\  †:‚F¶      <   vendor/phpspec/prophecy/src/Prophecy/Exception/Exception.php+  MDÐ\+  ¸µ‘¶      "   vendor/react/promise/composer.json‡  MDÐ\‡  T Üu¶      -   vendor/react/promise/src/PromiseInterface.php@  MDÐ\@  ¢c¶      &   vendor/react/promise/src/functions.phpª   MDÐ\ª   /à:¶      .   vendor/react/promise/src/functions_include.phpa   MDÐ\a   …|â¶      8   vendor/react/promise/src/CancellablePromiseInterface.php¦   MDÐ\¦   ¬c¨Œ¶      $   vendor/react/promise/src/Promise.phpŒ"  MDÐ\Œ"  Éàm¶      %   vendor/react/promise/src/Deferred.php¼  MDÐ\¼  
Ì¶      .   vendor/react/promise/src/PromisorInterface.php�   MDÐ\�   ºÿ\º¶      ,   vendor/react/promise/src/RejectedPromise.php;  MDÐ\;  ó½x ¶      8   vendor/react/promise/src/UnhandledRejectionException.phpe  MDÐ\e  g4ß¶      (   vendor/react/promise/src/LazyPromise.phpx  MDÐ\x  „ýÿ¶      -   vendor/react/promise/src/FulfilledPromise.phpö  MDÐ\ö  ü�A¶      .   vendor/react/promise/src/CancellationQueue.phpX  MDÐ\X  ž!¶      6   vendor/react/promise/src/Exception/LengthException.php^   MDÐ\^   ?q¶      5   vendor/react/promise/src/ExtendedPromiseInterface.phpþ  MDÐ\þ  $£3"¶      )   vendor/sebastian/comparator/composer.json:  MDÐ\:  v¶      3   vendor/sebastian/comparator/src/ArrayComparator.php`  MDÐ\`  Þæ¼¶      2   vendor/sebastian/comparator/src/TypeComparator.phpà  MDÐ\à  G—ŠÏ¶      +   vendor/sebastian/comparator/src/Factory.phpd  MDÐ\d  Ù‡1¶      6   vendor/sebastian/comparator/src/ResourceComparator.php*  MDÐ\*  Ù«ÁÄ¶      8   vendor/sebastian/comparator/src/MockObjectComparator.php´  MDÐ\´  –�îO¶      4   vendor/sebastian/comparator/src/ObjectComparator.php  MDÐ\  ˜¥26¶      5   vendor/sebastian/comparator/src/NumericComparator.php·  MDÐ\·  _ÐnÑ¶      6   vendor/sebastian/comparator/src/DateTimeComparator.phpP
  MDÐ\P
  RO^¶      4   vendor/sebastian/comparator/src/ScalarComparator.phpw  MDÐ\w  É§×¶      7   vendor/sebastian/comparator/src/ExceptionComparator.php×  MDÐ\×   kf ¶      .   vendor/sebastian/comparator/src/Comparator.php‰  MDÐ\‰  Å`…¶      4   vendor/sebastian/comparator/src/DoubleComparator.phpp  MDÐ\p  vz‘å¶      5   vendor/sebastian/comparator/src/DOMNodeComparator.phpS  MDÐ\S  ÄWbö¶      5   vendor/sebastian/comparator/src/ComparisonFailure.php€  MDÐ\€  �V¶      >   vendor/sebastian/comparator/src/SplObjectStorageComparator.phpÖ  MDÐ\Ö  ‹Wê¶      +   vendor/sebastian/global-state/composer.json  MDÐ\  D•c¶      .   vendor/sebastian/global-state/src/Restorer.php§  MDÐ\§  Ó“;\¶      2   vendor/sebastian/global-state/src/CodeExporter.php›  MDÐ\›  `Ö(C¶      .   vendor/sebastian/global-state/src/Snapshot.phpå%  MDÐ\å%  Ã–SÝ¶      6   vendor/sebastian/global-state/src/RuntimeException.phpq  MDÐ\q  ¿~]!¶      /   vendor/sebastian/global-state/src/Blacklist.php[  MDÐ\[  :®¶      /   vendor/sebastian/global-state/src/Exception.php?  MDÐ\?  É¶      0   vendor/sebastian/object-enumerator/composer.json  MDÐ\  õœµ)¶      C   vendor/sebastian/object-enumerator/src/InvalidArgumentException.phpx  MDÐ\x  �û'í¶      5   vendor/sebastian/object-enumerator/src/Enumerator.phpk	  MDÐ\k	  QÔ	$¶      4   vendor/sebastian/object-enumerator/src/Exception.php6  MDÐ\6  n$*a¶      7   vendor/sebastian/code-unit-reverse-lookup/composer.json¢  MDÐ\¢  SS	T¶      8   vendor/sebastian/code-unit-reverse-lookup/src/Wizard.phpe  MDÐ\e  þíŽþ¶      #   vendor/sebastian/diff/composer.jsonÝ  MDÐ\Ý  K^ ü¶      "   vendor/sebastian/diff/src/Diff.php§  MDÐ\§  Sdh¶      $   vendor/sebastian/diff/src/Parser.php¸  MDÐ\¸  &ÇÇ¶      U   vendor/sebastian/diff/src/LCS/TimeEfficientLongestCommonSubsequenceImplementation.php¥  MDÐ\¥  ¾ò¯¶      :   vendor/sebastian/diff/src/LCS/LongestCommonSubsequence.phpj  MDÐ\j  ·\/¶      W   vendor/sebastian/diff/src/LCS/MemoryEfficientLongestCommonSubsequenceImplementation.phpR	  MDÐ\R	  œoƒó¶      #   vendor/sebastian/diff/src/Chunk.phpÅ  MDÐ\Å  vÇ|¼¶      $   vendor/sebastian/diff/src/Differ.php)  MDÐ\)  S„9R¶      "   vendor/sebastian/diff/src/Line.php•  MDÐ\•  e’Ü¶      0   vendor/sebastian/recursion-context/composer.jsonL  MDÐ\L  6GÔ¶      C   vendor/sebastian/recursion-context/src/InvalidArgumentException.php’  MDÐ\’  mH¶      2   vendor/sebastian/recursion-context/src/Context.php{  MDÐ\{  ‰¤�¶      4   vendor/sebastian/recursion-context/src/Exception.phpJ  MDÐ\J  Èô³ñ¶      &   vendor/sebastian/version/composer.jsonÁ  MDÐ\Á  a§Ê¶      (   vendor/sebastian/version/src/Version.php±  MDÐ\±  N\Ç¶      *   vendor/sebastian/environment/composer.jsonÉ  MDÐ\É  Âœ¯¶      ,   vendor/sebastian/environment/src/Runtime.phpz  MDÐ\z  O<�¶      ,   vendor/sebastian/environment/src/Console.phpÌ  MDÐ\Ì  ôF¶      '   vendor/sebastian/exporter/composer.jsonŠ  MDÐ\Š  …uÉG¶      *   vendor/sebastian/exporter/src/Exporter.phpE#  MDÐ\E#  Ÿü¶      7   vendor/sebastian/resource-operations/build/generate.php  MDÐ\  Þx4½¶      2   vendor/sebastian/resource-operations/composer.jsonY  MDÐ\Y  KÈ¶      ?   vendor/sebastian/resource-operations/src/ResourceOperations.php’U  MDÐ\’U  ØhÕ¶      >   vendor/elasticsearch/elasticsearch/util/EnsureClusterAlive.php_  MDÐ\_  rÏBí¶      :   vendor/elasticsearch/elasticsearch/util/RestSpecRunner.php-  MDÐ\-  	°Þ¶      6   vendor/elasticsearch/elasticsearch/util/docsConfig.phpµ  MDÐ\µ  ÛòŠ¶      6   vendor/elasticsearch/elasticsearch/util/SpecParser.php‡  MDÐ\‡  á<m–¶      0   vendor/elasticsearch/elasticsearch/composer.json�  MDÐ\�  _ñÑ¶      W   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/RenderSearchTemplate.php¤  MDÐ\¤  8^€÷¶      O   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Recovery.php  MDÐ\  ‹Oes¶      S   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/PendingTasks.php‚  MDÐ\‚  7[�¶      Q   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/ThreadPool.phpÌ  MDÐ\Ì  N,¶      P   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Templates.php  MDÐ\  Ë˜cÔ¶      M   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Shards.phpþ  MDÐ\þ  æ.F¶      M   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Health.php�  MDÐ\�  º�–µ¶      M   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Master.phpo  MDÐ\o  œB¶      L   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Tasks.phpœ  MDÐ\œ  	îu¶      P   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Snapshots.phpW  MDÐ\W  ÷ÎŸ¶      P   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/NodeAttrs.phpx  MDÐ\x  +¦o¶      N   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Plugins.phpq  MDÐ\q  w¨Ìª¶      Q   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Allocation.php�  MDÐ\�  ’ìš¶      K   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Help.php  MDÐ\  ÌÓ Ï¶      L   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Count.phpå  MDÐ\å  ·.�é¶      N   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Aliases.php8  MDÐ\8  .É|¶      O   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Segments.php$  MDÐ\$  …©Ã€¶      N   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Indices.php,  MDÐ\,  ˆŒ4ö¶      P   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Fielddata.php	  MDÐ\	  –Vx%¶      L   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Nodes.phpƒ  MDÐ\ƒ  ñá–Ó¶      S   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Repositories.php�  MDÐ\�  Kê§Ø¶      G   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Info.php™  MDÐ\™  Çn3¶      J   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Explain.phpÑ  MDÐ\Ñ  bŠ)ì¶      O   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/SearchShards.phpÆ  MDÐ\Æ  €òÕ¶      Z   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Repository/Get.php  MDÐ\  òÎD¶      ]   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Repository/Create.php£  MDÐ\£  À`*Ô¶      ]   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Repository/Delete.phpB  MDÐ\B  áy¾¶      ]   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Repository/Verify.phpä  MDÐ\ä  Q‚ãl¶      O   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Get.phpx  MDÐ\x  þŒV¶      R   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Create.php‘	  MDÐ\‘	  fÎG·¶      R   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Status.php{  MDÐ\{  Ãž¶      S   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Restore.php¨	  MDÐ\¨	  Pé?¶      R   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Delete.php4  MDÐ\4  «yÎ!¶      I   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Update.php”  MDÐ\”  +B%¶      W   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Settings/Get.phpL  MDÐ\L  ·F¶      W   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Settings/Put.phpx  MDÐ\x  ü¯À�¶      W   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/PendingTasks.php1  MDÐ\1  ä­Z¶      R   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Reroute.phpè  MDÐ\è  (ìiÙ¶      Q   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Health.php   MDÐ\   èÔ�°¶      U   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/Info.php#  MDÐ\#  psï¶      [   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/HotThreads.phpÊ  MDÐ\Ê  Ïú0n¶      V   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/Stats.phpm
  MDÐ\m
  ÌÏ¶      Y   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/Shutdown.phpƒ  MDÐ\ƒ  ðK‡-¶      f   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/AbstractNodesEndpoint.php*  MDÐ\*  É"w¶      \   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/AllocationExplain.phpy  MDÐ\y  ˆ?¶      P   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/State.php…  MDÐ\…  ¦Î¶      P   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Stats.phpˆ  MDÐ\ˆ  ª°‚¡¶      P   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/UpdateByQuery.phpw
  MDÐ\w
  ib}¶      N   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/TermVectors.php)  MDÐ\)  ”|#¶      X   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/BulkEndpointInterface.php@  MDÐ\@  l§¶      L   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Tasks/Get.php  MDÐ\  ™ŠL¶      R   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Tasks/TasksList.php  MDÐ\  ßYKõ¶      O   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Tasks/Cancel.phpc  MDÐ\c  ù‰¶      V   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/ShardStores.phpq  MDÐ\q  Ã�KÁ¶      S   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Recovery.php¨  MDÐ\¨  äÇ¶      [   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Mapping/GetField.phpk  MDÐ\k  —Oé`¶      V   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Mapping/Get.phpÞ  MDÐ\Þ  Nêh­¶      V   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Mapping/Put.phpM  MDÐ\M  �üjq¶      Y   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Mapping/Delete.php‡  MDÐ\‡  “º†z¶      W   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Settings/Get.php8  MDÐ\8  T¾¶      W   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Settings/Put.phpÃ  MDÐ\Ã  A7XÁ¶      Y   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Validate/Query.php‚  MDÐ\‚  ”]ˆ3¶      f   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/AbstractAliasEndpoint.php�  MDÐ\�  )E»d¶      T   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/Get.phpé  MDÐ\é  æÄÓ¶      T   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/Put.php†  MDÐ\†  WÛ#%¶      W   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/Delete.php  MDÐ\  ä¢˜z¶      W   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/Exists.phpö  MDÐ\ö  ïMcœ¶      V   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Upgrade/Get.phpp  MDÐ\p  6C”¶      W   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Upgrade/Post.phpr  MDÐ\r  ˜.V´¶      V   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Cache/Clear.phpð  MDÐ\ð  žp(¶      R   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Analyze.php×  MDÐ\×  añ^L¶      N   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Get.phpF  MDÐ\F  ÚÓÆë¶      W   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Exists/Types.phpS  MDÐ\S  ånú¶      U   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/ClearCache.phpº  MDÐ\º  *ˆ¹¶      Q   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Shrink.php  MDÐ\  éc`}¶      W   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/Get.php<  MDÐ\<  7–er¶      W   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/Put.php‰  MDÐ\‰  ¨ÏZ{¶      Z   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/Delete.phpÖ  MDÐ\Ö  1jÎ¬¶      l   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/AbstractTemplateEndpoint.phpŠ  MDÐ\Š  Eîm�¶      Z   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/Exists.phpÑ  MDÐ\Ñ  
Êo{¶      Q   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Create.php  MDÐ\  ãÈ†Ö¶      T   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Field/Get.php  MDÐ\  Ã²‹_¶      P   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Stats.phpÍ  MDÐ\Í  ]Ñ°ü¶      V   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Type/Exists.php5  MDÐ\5  �ÿôA¶      U   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/ForceMerge.phpf  MDÐ\f  ‹[ó@¶      Q   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Status.php#  MDÐ\#  ¦!Êñ¶      X   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/ValidateQuery.php2  MDÐ\2  ú©ÿa¶      S   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Rollover.phpB  MDÐ\B  R5ê~¶      P   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Flush.phpÃ  MDÐ\Ã  \BÃ¶      Q   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Delete.phpˆ  MDÐ\ˆ  ãË=-¶      Q   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Exists.phpÕ  MDÐ\Õ  tZ„S¶      O   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Seal.php¯  MDÐ\¯  WÀŒ"¶      O   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Open.phpû  MDÐ\û  ÒxšÆ¶      S   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Segments.phpû  MDÐ\û  €ep¶      Y   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Aliases/Update.phpÉ  MDÐ\É  Â¿Ã¶      V   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Aliases/Get.phpª  MDÐ\ª  pÐ‹Ï¶      X   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Snapshotindex.phpÞ  MDÐ\Þ  ”D´·¶      R   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Refresh.php÷  MDÐ\÷  ×Ëƒç¶      [   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Gateway/Snapshot.phpã  MDÐ\ã  |/ˆ~¶      P   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Close.php   MDÐ\   �|ÑÈ¶      O   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/MTermVectors.php&  MDÐ\&  ™ñ�À¶      L   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Percolate.php9  MDÐ\9  ˜^H¶      P   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/DeleteByQuery.phpÝ  MDÐ\Ý  _~L¶      H   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Index.php
  MDÐ\
  á$1×¶      F   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Get.phpy	  MDÐ\y	  ö1½¶      J   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Suggest.phpa  MDÐ\a  @u–¶      R   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ingest/Simulate.php  MDÐ\  Àg¶      V   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ingest/Pipeline/Get.phpÔ  MDÐ\Ô  ¦^Ðô¶      V   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ingest/Pipeline/Put.php…  MDÐ\…  Ø©Vµ¶      Y   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ingest/Pipeline/Delete.phpT  MDÐ\T  ±´ ¶      M   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Source/Get.php  MDÐ\  —jÌµ¶      O   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Template/Get.php  MDÐ\  vFý¶      O   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Template/Put.php  MDÐ\  ¤Ã2H¶      R   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Template/Delete.php  MDÐ\  z½P[¶      I   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Create.php	  MDÐ\	  +SO`¶      I   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Search.phpÊ	  MDÐ\Ê	  ×ÈZ8¶      M   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Script/Get.php  MDÐ\  	d=¶      M   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Script/Put.php  MDÐ\  5�[«¶      P   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Script/Delete.php  MDÐ\  ãñºÉ¶      M   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/MPercolate.php  MDÐ\  ÛS‚¶      S   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/AbstractEndpoint.phpü  MDÐ\ü  w›ª¶¶      L   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/FieldCaps.php=  MDÐ\=  «Ÿ¢‘¶      Q   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/CountPercolate.phpŽ  MDÐ\Ž  —’µ”¶      H   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Count.php  MDÐ\  asø¶      N   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/ClearScroll.phpÐ  MDÐ\Ð  *!´P¶      G   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ping.phpš  MDÐ\š  ò¤N¶      J   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Reindex.phpˆ  MDÐ\ˆ  «Ì÷*¶      I   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Delete.php¦  MDÐ\¦  ]8†·¶      G   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Bulk.php¦  MDÐ\¦  �û„¶      Q   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/SearchTemplate.php•  MDÐ\•  ìÅÒ7¶      I   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Exists.php‰  MDÐ\‰  ß!e®¶      R   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/MsearchTemplate.php!	  MDÐ\!	  <ƒS¶      M   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/FieldStats.php2  MDÐ\2  ½ºZ¶      J   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Msearch.phpÇ  MDÐ\Ç  @Ûl^¶      I   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Scroll.php¥  MDÐ\¥  ~*™¥¶      G   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Mget.phpo  MDÐ\o  Æ%Oð¶      N   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Remote/Info.phpÇ  MDÐ\Ç  ¥]k ¶      Y   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/BooleanRequestWrapper.phpò  MDÐ\ò  ‰Ö–Ü¶      U   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/SnapshotNamespace.phpR  MDÐ\R  ;Ç<”¶      P   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/CatNamespace.php<S  MDÐ\<S  Fo¡¶      R   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/TasksNamespace.phpk  MDÐ\k  3ú0è¶      R   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/NodesNamespace.php„  MDÐ\„  T­¶      S   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/RemoteNamespace.php6  MDÐ\6  �x²†¶      S   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/IngestNamespace.php†  MDÐ\†  å\f¶      T   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/ClusterNamespace.php  MDÐ\  ì?Á"¶      ]   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/NamespaceBuilderInterface.php   MDÐ\   ¾.gÛ¶      T   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/IndicesNamespace.php¦¾  MDÐ\¦¾  Ÿ°MÑ¶      U   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/AbstractNamespace.phpî  MDÐ\î  çpš¶      O   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Connections/Connection.phpí_  MDÐ\í_  o6¢â¶      _   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Connections/ConnectionFactoryInterface.php¢  MDÐ\¢  ¬÷,¶      X   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Connections/ConnectionInterface.phpa	  MDÐ\a	   ýú¶      V   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Connections/ConnectionFactory.phpÇ  MDÐ\Ç  û”ˆN¶      K   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/EmptyLogger.php!  MDÐ\!  @= à¶      g   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/ClientErrorResponseException.php›  MDÐ\›  ¬Š’`¶      h   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Serializer/JsonErrorException.phpj  MDÐ\j  	ç¶      `   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Forbidden403Exception.php  MDÐ\  ="³¶      ^   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Missing404Exception.php{  MDÐ\{  Tf>¶      c   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/UnexpectedValueException.phpÖ  MDÐ\Ö  ˆ>Žµ¶      [   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/RuntimeException.php|  MDÐ\|  †Þï:¶      g   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/ServerErrorResponseException.php•  MDÐ\•  e,^5¶      b   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/AlreadyExpiredException.php¯  MDÐ\¯  Óot¶      c   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/InvalidArgumentException.phpÆ  MDÐ\Æ  pz…¶      a   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/BadMethodCallException.phpÜ  MDÐ\Ü  ¥F‘y¶      d   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/NoDocumentsToGetException.php™  MDÐ\™  ÌØÜä¶      i   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Curl/OperationTimeoutException.php  MDÐ\  …Å¢€¶      e   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Curl/CouldNotConnectToHost.php  MDÐ\  X¹è;¶      l   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Curl/CouldNotResolveHostException.php  MDÐ\  Î”‚Ë¶      e   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/RequestTimeout408Exception.php•  MDÐ\•  å�Õ¶      b   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/RoutingMissingException.phpÕ  MDÐ\Õ  #	¬¶      ]   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/TransportException.phpy  MDÐ\y  °6 ¶      ^   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/MaxRetriesException.phpƒ  MDÐ\ƒ  Öu²i¶      a   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/BadRequest400Exception.php¤  MDÐ\¤  Ü5ø	¶      j   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/ScriptLangNotSupportedException.phpŸ  MDÐ\Ÿ  †3é¶      d   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/NoNodesAvailableException.php‡  MDÐ\‡  /‡¶      d   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/NoShardAvailableException.php™  MDÐ\™  ÿÉvÑ¶      _   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Conflict409Exception.php   MDÐ\   «áqí¶      a   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/ElasticsearchException.phpU  MDÐ\U  Twœ¬¶      X   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/SerializerInterface.php  MDÐ\  ;¤§¶      _   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/EverythingToJSONSerializer.php  MDÐ\  ¶Ø¡¶      Z   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/ArrayToJSONSerializer.php†  MDÐ\†  vµT²¶      T   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/SmartSerializer.php™
  MDÐ\™
  ü�1k¶      B   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Transport.phpÕ  MDÐ\Õ  ­YÞš¶      b   vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/StaticNoPingConnectionPool.php×  MDÐ\×  ßW‚¶      ^   vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/AbstractConnectionPool.php˜
  MDÐ\˜
  ìHz¶      ^   vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/SniffingConnectionPool.php  MDÐ\  HûRØ¶      \   vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/StaticConnectionPool.php»	  MDÐ\»	  -u‡K¶      _   vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/ConnectionPoolInterface.phpR  MDÐ\R  GÙÿ¶      \   vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/SimpleConnectionPool.php†  MDÐ\†  ä5Ü¶      c   vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/SelectorInterface.php¢  MDÐ\¢  Ã�¶      `   vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/RandomSelector.php  MDÐ\  î¥D¶      d   vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/RoundRobinSelector.php‚  MDÐ\‚  ÇÚ–.¶      j   vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/StickyRoundRobinSelector.php•  MDÐ\•  ‡IZú¶      F   vendor/elasticsearch/elasticsearch/src/Elasticsearch/ClientBuilder.php”M  MDÐ\”M  }_½H¶      `   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Helper/Iterators/SearchResponseIterator.phpÒ  MDÐ\Ò  Iž>˜¶      [   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Helper/Iterators/SearchHitIterator.php  MDÐ\  †¦ðæ¶      ?   vendor/elasticsearch/elasticsearch/src/Elasticsearch/Client.php[ MDÐ\[ <ŽJ÷¶         src/utils/FileNameUtils.phpú  MDÐ\ú  S1	¶¶         src/utils/MigrationUtils.php‘  MDÐ\‘  I¼Æú¶      6   src/commands/toggles/CreatePermissionToggleCommand.php2  MDÐ\2  opÙ¶      .   src/commands/toggles/MigrateTogglesCommand.php€
  MDÐ\€
  ]1ˆ¬¶      "   src/commands/db/MigrateCommand.phpŠ  MDÐ\Š  ëjt¶      &   src/commands/db/MigrateFakeCommand.phpþ  MDÐ\þ  V"€¶      *   src/commands/db/CreateMigrationCommand.phpP  MDÐ\P  v–j¶      ,   src/commands/git/UpdateStyleGuideCommand.php  MDÐ\  zA4x¶      ,   src/service/toggles/ToggleManagerService.phpV  MDÐ\V  © L|¶      %   src/service/db/MigrateFakeService.php  MDÐ\  á<%¶      !   src/service/db/MigrateService.phpj  MDÐ\j  ’ÆÄî¶         cli3  MDÐ\3  0¬¶      !   MigrationCustom.template.php.dist[  MDÐ\[  _¡¶         Toggle.php.template  MDÐ\  rÐºÅ¶      <?php

namespace DeepCopy\f005;

class Foo
{
    public $cloned = false;

    public function __clone()
    {
        $this->cloned = true;
    }
}
<?php

namespace DeepCopy\f002;

class A
{
    private $prop1;
    private $prop2;

    public function getProp1()
    {
        return $this->prop1;
    }

    public function setProp1($prop)
    {
        $this->prop1 = $prop;

        return $this;
    }

    public function getProp2()
    {
        return $this->prop2;
    }

    public function setProp2($prop)
    {
        $this->prop2 = $prop;

        return $this;
    }
}
<?php

namespace DeepCopy\f003;

class Foo
{
    private $name;
    private $prop;

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

    public function getProp()
    {
        return $this->prop;
    }

    public function setProp($prop)
    {
        $this->prop = $prop;

        return $this;
    }
}<?php

namespace DeepCopy\f004;

use BadMethodCallException;

class UnclonableItem
{
    private function __clone()
    {
        throw new BadMethodCallException('Unsupported call.');
    }
}
<?php

namespace DeepCopy\f008;

class B extends A
{
}
<?php

namespace DeepCopy\f008;

class A
{
    private $foo;

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

    public function getFoo()
    {
        return $this->foo;
    }
}
<?php

namespace DeepCopy\f001;

class B extends A
{
    private $bProp;

    public function getBProp()
    {
        return $this->bProp;
    }

    public function setBProp($prop)
    {
        $this->bProp = $prop;

        return $this;
    }
}
<?php

namespace DeepCopy\f001;

class A
{
    private $aProp;

    public function getAProp()
    {
        return $this->aProp;
    }

    public function setAProp($prop)
    {
        $this->aProp = $prop;

        return $this;
    }
}
<?php

namespace DeepCopy\f006;

class B
{
    public $cloned = false;
    private $bProp;

    public function getBProp()
    {
        return $this->bProp;
    }

    public function setBProp($prop)
    {
        $this->bProp = $prop;

        return $this;
    }

    public function __clone()
    {
        $this->cloned = true;
    }
}
<?php

namespace DeepCopy\f006;

class A
{
    public $cloned = false;
    private $aProp;

    public function getAProp()
    {
        return $this->aProp;
    }

    public function setAProp($prop)
    {
        $this->aProp = $prop;

        return $this;
    }

    public function __clone()
    {
        $this->cloned = true;
    }
}
<?php

namespace DeepCopy\f007;

use DateInterval;

class FooDateInterval extends DateInterval
{
    public $cloned = false;

    public function __clone()
    {
        $this->cloned = true;
    }
}
<?php

namespace DeepCopy\f007;

use DateTimeZone;

class FooDateTimeZone extends DateTimeZone
{
    public $cloned = false;

    public function __clone()
    {
        $this->cloned = true;
    }
}
{
    "name": "myclabs/deep-copy",
    "type": "library",
    "description": "Create deep copies (clones) of your objects",
    "keywords": ["clone", "copy", "duplicate", "object", "object graph"],
    "license": "MIT",

    "autoload": {
        "psr-4": {
            "DeepCopy\\": "src/DeepCopy/"
        },
        "files": [
            "src/DeepCopy/deep_copy.php"
        ]
    },
    "autoload-dev": {
        "psr-4": {
            "DeepCopy\\": "fixtures/",
            "DeepCopyTest\\": "tests/DeepCopyTest/"
        }
    },

    "require": {
        "php": "^7.1"
    },
    "require-dev": {
        "doctrine/collections": "^1.0",
        "doctrine/common": "^2.6",
        "phpunit/phpunit": "^7.1"
    },
    "replace": {
        "myclabs/deep-copy": "self.version"
    },

    "config": {
        "sort-packages": true
    }
}
<?php

namespace DeepCopy;

use function function_exists;

if (false === function_exists('DeepCopy\deep_copy')) {
    /**
     * Deep copies the given value.
     *
     * @param mixed $value
     * @param bool  $useCloneMethod
     *
     * @return mixed
     */
    function deep_copy($value, $useCloneMethod = false)
    {
        return (new DeepCopy($useCloneMethod))->copy($value);
    }
}
<?php

namespace DeepCopy\Matcher;

/**
 * @final
 */
class PropertyMatcher implements Matcher
{
    /**
     * @var string
     */
    private $class;

    /**
     * @var string
     */
    private $property;

    /**
     * @param string $class    Class name
     * @param string $property Property name
     */
    public function __construct($class, $property)
    {
        $this->class = $class;
        $this->property = $property;
    }

    /**
     * Matches a specific property of a specific class.
     *
     * {@inheritdoc}
     */
    public function matches($object, $property)
    {
        return ($object instanceof $this->class) && $property == $this->property;
    }
}
<?php

namespace DeepCopy\Matcher\Doctrine;

use DeepCopy\Matcher\Matcher;
use Doctrine\Common\Persistence\Proxy;

/**
 * @final
 */
class DoctrineProxyMatcher implements Matcher
{
    /**
     * Matches a Doctrine Proxy class.
     *
     * {@inheritdoc}
     */
    public function matches($object, $property)
    {
        return $object instanceof Proxy;
    }
}
<?php

namespace DeepCopy\Matcher;

interface Matcher
{
    /**
     * @param object $object
     * @param string $property
     *
     * @return boolean
     */
    public function matches($object, $property);
}
<?php

namespace DeepCopy\Matcher;

/**
 * @final
 */
class PropertyNameMatcher implements Matcher
{
    /**
     * @var string
     */
    private $property;

    /**
     * @param string $property Property name
     */
    public function __construct($property)
    {
        $this->property = $property;
    }

    /**
     * Matches a property by its name.
     *
     * {@inheritdoc}
     */
    public function matches($object, $property)
    {
        return $property == $this->property;
    }
}
<?php

namespace DeepCopy\Matcher;

use DeepCopy\Reflection\ReflectionHelper;
use ReflectionException;

/**
 * Matches a property by its type.
 *
 * It is recommended to use {@see DeepCopy\TypeFilter\TypeFilter} instead, as it applies on all occurrences
 * of given type in copied context (eg. array elements), not just on object properties.
 *
 * @final
 */
class PropertyTypeMatcher implements Matcher
{
    /**
     * @var string
     */
    private $propertyType;

    /**
     * @param string $propertyType Property type
     */
    public function __construct($propertyType)
    {
        $this->propertyType = $propertyType;
    }

    /**
     * {@inheritdoc}
     */
    public function matches($object, $property)
    {
        try {
            $reflectionProperty = ReflectionHelper::getProperty($object, $property);
        } catch (ReflectionException $exception) {
            return false;
        }

        $reflectionProperty->setAccessible(true);

        return $reflectionProperty->getValue($object) instanceof $this->propertyType;
    }
}
<?php

namespace DeepCopy;

use DateInterval;
use DateTimeInterface;
use DateTimeZone;
use DeepCopy\Exception\CloneException;
use DeepCopy\Filter\Filter;
use DeepCopy\Matcher\Matcher;
use DeepCopy\TypeFilter\Date\DateIntervalFilter;
use DeepCopy\TypeFilter\Spl\SplDoublyLinkedListFilter;
use DeepCopy\TypeFilter\TypeFilter;
use DeepCopy\TypeMatcher\TypeMatcher;
use ReflectionObject;
use ReflectionProperty;
use DeepCopy\Reflection\ReflectionHelper;
use SplDoublyLinkedList;

/**
 * @final
 */
class DeepCopy
{
    /**
     * @var object[] List of objects copied.
     */
    private $hashMap = [];

    /**
     * Filters to apply.
     *
     * @var array Array of ['filter' => Filter, 'matcher' => Matcher] pairs.
     */
    private $filters = [];

    /**
     * Type Filters to apply.
     *
     * @var array Array of ['filter' => Filter, 'matcher' => Matcher] pairs.
     */
    private $typeFilters = [];

    /**
     * @var bool
     */
    private $skipUncloneable = false;

    /**
     * @var bool
     */
    private $useCloneMethod;

    /**
     * @param bool $useCloneMethod   If set to true, when an object implements the __clone() function, it will be used
     *                               instead of the regular deep cloning.
     */
    public function __construct($useCloneMethod = false)
    {
        $this->useCloneMethod = $useCloneMethod;

        $this->addTypeFilter(new DateIntervalFilter(), new TypeMatcher(DateInterval::class));
        $this->addTypeFilter(new SplDoublyLinkedListFilter($this), new TypeMatcher(SplDoublyLinkedList::class));
    }

    /**
     * If enabled, will not throw an exception when coming across an uncloneable property.
     *
     * @param $skipUncloneable
     *
     * @return $this
     */
    public function skipUncloneable($skipUncloneable = true)
    {
        $this->skipUncloneable = $skipUncloneable;

        return $this;
    }

    /**
     * Deep copies the given object.
     *
     * @param mixed $object
     *
     * @return mixed
     */
    public function copy($object)
    {
        $this->hashMap = [];

        return $this->recursiveCopy($object);
    }

    public function addFilter(Filter $filter, Matcher $matcher)
    {
        $this->filters[] = [
            'matcher' => $matcher,
            'filter'  => $filter,
        ];
    }

    public function prependFilter(Filter $filter, Matcher $matcher)
    {
        array_unshift($this->filters, [
            'matcher' => $matcher,
            'filter'  => $filter,
        ]);
    }

    public function addTypeFilter(TypeFilter $filter, TypeMatcher $matcher)
    {
        $this->typeFilters[] = [
            'matcher' => $matcher,
            'filter'  => $filter,
        ];
    }

    private function recursiveCopy($var)
    {
        // Matches Type Filter
        if ($filter = $this->getFirstMatchedTypeFilter($this->typeFilters, $var)) {
            return $filter->apply($var);
        }

        // Resource
        if (is_resource($var)) {
            return $var;
        }

        // Array
        if (is_array($var)) {
            return $this->copyArray($var);
        }

        // Scalar
        if (! is_object($var)) {
            return $var;
        }

        // Object
        return $this->copyObject($var);
    }

    /**
     * Copy an array
     * @param array $array
     * @return array
     */
    private function copyArray(array $array)
    {
        foreach ($array as $key => $value) {
            $array[$key] = $this->recursiveCopy($value);
        }

        return $array;
    }

    /**
     * Copies an object.
     *
     * @param object $object
     *
     * @throws CloneException
     *
     * @return object
     */
    private function copyObject($object)
    {
        $objectHash = spl_object_hash($object);

        if (isset($this->hashMap[$objectHash])) {
            return $this->hashMap[$objectHash];
        }

        $reflectedObject = new ReflectionObject($object);
        $isCloneable = $reflectedObject->isCloneable();

        if (false === $isCloneable) {
            if ($this->skipUncloneable) {
                $this->hashMap[$objectHash] = $object;

                return $object;
            }

            throw new CloneException(
                sprintf(
                    'The class "%s" is not cloneable.',
                    $reflectedObject->getName()
                )
            );
        }

        $newObject = clone $object;
        $this->hashMap[$objectHash] = $newObject;

        if ($this->useCloneMethod && $reflectedObject->hasMethod('__clone')) {
            return $newObject;
        }

        if ($newObject instanceof DateTimeInterface || $newObject instanceof DateTimeZone) {
            return $newObject;
        }

        foreach (ReflectionHelper::getProperties($reflectedObject) as $property) {
            $this->copyObjectProperty($newObject, $property);
        }

        return $newObject;
    }

    private function copyObjectProperty($object, ReflectionProperty $property)
    {
        // Ignore static properties
        if ($property->isStatic()) {
            return;
        }

        // Apply the filters
        foreach ($this->filters as $item) {
            /** @var Matcher $matcher */
            $matcher = $item['matcher'];
            /** @var Filter $filter */
            $filter = $item['filter'];

            if ($matcher->matches($object, $property->getName())) {
                $filter->apply(
                    $object,
                    $property->getName(),
                    function ($object) {
                        return $this->recursiveCopy($object);
                    }
                );

                // If a filter matches, we stop processing this property
                return;
            }
        }

        $property->setAccessible(true);
        $propertyValue = $property->getValue($object);

        // Copy the property
        $property->setValue($object, $this->recursiveCopy($propertyValue));
    }

    /**
     * Returns first filter that matches variable, `null` if no such filter found.
     *
     * @param array $filterRecords Associative array with 2 members: 'filter' with value of type {@see TypeFilter} and
     *                             'matcher' with value of type {@see TypeMatcher}
     * @param mixed $var
     *
     * @return TypeFilter|null
     */
    private function getFirstMatchedTypeFilter(array $filterRecords, $var)
    {
        $matched = $this->first(
            $filterRecords,
            function (array $record) use ($var) {
                /* @var TypeMatcher $matcher */
                $matcher = $record['matcher'];

                return $matcher->matches($var);
            }
        );

        return isset($matched) ? $matched['filter'] : null;
    }

    /**
     * Returns first element that matches predicate, `null` if no such element found.
     *
     * @param array    $elements Array of ['filter' => Filter, 'matcher' => Matcher] pairs.
     * @param callable $predicate Predicate arguments are: element.
     *
     * @return array|null Associative array with 2 members: 'filter' with value of type {@see TypeFilter} and 'matcher'
     *                    with value of type {@see TypeMatcher} or `null`.
     */
    private function first(array $elements, callable $predicate)
    {
        foreach ($elements as $element) {
            if (call_user_func($predicate, $element)) {
                return $element;
            }
        }

        return null;
    }
}
<?php

namespace DeepCopy\Filter;

use DeepCopy\Reflection\ReflectionHelper;

/**
 * @final
 */
class ReplaceFilter implements Filter
{
    /**
     * @var callable
     */
    protected $callback;

    /**
     * @param callable $callable Will be called to get the new value for each property to replace
     */
    public function __construct(callable $callable)
    {
        $this->callback = $callable;
    }

    /**
     * Replaces the object property by the result of the callback called with the object property.
     *
     * {@inheritdoc}
     */
    public function apply($object, $property, $objectCopier)
    {
        $reflectionProperty = ReflectionHelper::getProperty($object, $property);
        $reflectionProperty->setAccessible(true);

        $value = call_user_func($this->callback, $reflectionProperty->getValue($object));

        $reflectionProperty->setValue($object, $value);
    }
}
<?php

namespace DeepCopy\Filter;

class KeepFilter implements Filter
{
    /**
     * Keeps the value of the object property.
     *
     * {@inheritdoc}
     */
    public function apply($object, $property, $objectCopier)
    {
        // Nothing to do
    }
}
<?php

namespace DeepCopy\Filter\Doctrine;

use DeepCopy\Filter\Filter;
use DeepCopy\Reflection\ReflectionHelper;
use Doctrine\Common\Collections\ArrayCollection;

/**
 * @final
 */
class DoctrineEmptyCollectionFilter implements Filter
{
    /**
     * Sets the object property to an empty doctrine collection.
     *
     * @param object   $object
     * @param string   $property
     * @param callable $objectCopier
     */
    public function apply($object, $property, $objectCopier)
    {
        $reflectionProperty = ReflectionHelper::getProperty($object, $property);
        $reflectionProperty->setAccessible(true);

        $reflectionProperty->setValue($object, new ArrayCollection());
    }
} <?php

namespace DeepCopy\Filter\Doctrine;

use DeepCopy\Filter\Filter;
use DeepCopy\Reflection\ReflectionHelper;

/**
 * @final
 */
class DoctrineCollectionFilter implements Filter
{
    /**
     * Copies the object property doctrine collection.
     *
     * {@inheritdoc}
     */
    public function apply($object, $property, $objectCopier)
    {
        $reflectionProperty = ReflectionHelper::getProperty($object, $property);

        $reflectionProperty->setAccessible(true);
        $oldCollection = $reflectionProperty->getValue($object);

        $newCollection = $oldCollection->map(
            function ($item) use ($objectCopier) {
                return $objectCopier($item);
            }
        );

        $reflectionProperty->setValue($object, $newCollection);
    }
}
<?php

namespace DeepCopy\Filter\Doctrine;

use DeepCopy\Filter\Filter;

/**
 * @final
 */
class DoctrineProxyFilter implements Filter
{
    /**
     * Triggers the magic method __load() on a Doctrine Proxy class to load the
     * actual entity from the database.
     *
     * {@inheritdoc}
     */
    public function apply($object, $property, $objectCopier)
    {
        $object->__load();
    }
}
<?php

namespace DeepCopy\Filter;

/**
 * Filter to apply to a property while copying an object
 */
interface Filter
{
    /**
     * Applies the filter to the object.
     *
     * @param object   $object
     * @param string   $property
     * @param callable $objectCopier
     */
    public function apply($object, $property, $objectCopier);
}
<?php

namespace DeepCopy\Filter;

use DeepCopy\Reflection\ReflectionHelper;

/**
 * @final
 */
class SetNullFilter implements Filter
{
    /**
     * Sets the object property to null.
     *
     * {@inheritdoc}
     */
    public function apply($object, $property, $objectCopier)
    {
        $reflectionProperty = ReflectionHelper::getProperty($object, $property);

        $reflectionProperty->setAccessible(true);
        $reflectionProperty->setValue($object, null);
    }
}
<?php

namespace DeepCopy\TypeMatcher;

class TypeMatcher
{
    /**
     * @var string
     */
    private $type;

    /**
     * @param string $type
     */
    public function __construct($type)
    {
        $this->type = $type;
    }

    /**
     * @param mixed $element
     *
     * @return boolean
     */
    public function matches($element)
    {
        return is_object($element) ? is_a($element, $this->type) : gettype($element) === $this->type;
    }
}
<?php

namespace DeepCopy\Exception;

use UnexpectedValueException;

class CloneException extends UnexpectedValueException
{
} <?php

namespace DeepCopy\Exception;

use ReflectionException;

class PropertyException extends ReflectionException
{
}
<?php

namespace DeepCopy\Reflection;

use DeepCopy\Exception\PropertyException;
use ReflectionClass;
use ReflectionException;
use ReflectionObject;
use ReflectionProperty;

class ReflectionHelper
{
    /**
     * Retrieves all properties (including private ones), from object and all its ancestors.
     *
     * Standard \ReflectionClass->getProperties() does not return private properties from ancestor classes.
     *
     * @author muratyaman@gmail.com
     * @see http://php.net/manual/en/reflectionclass.getproperties.php
     *
     * @param ReflectionClass $ref
     *
     * @return ReflectionProperty[]
     */
    public static function getProperties(ReflectionClass $ref)
    {
        $props = $ref->getProperties();
        $propsArr = array();

        foreach ($props as $prop) {
            $propertyName = $prop->getName();
            $propsArr[$propertyName] = $prop;
        }

        if ($parentClass = $ref->getParentClass()) {
            $parentPropsArr = self::getProperties($parentClass);
            foreach ($propsArr as $key => $property) {
                $parentPropsArr[$key] = $property;
            }

            return $parentPropsArr;
        }

        return $propsArr;
    }

    /**
     * Retrieves property by name from object and all its ancestors.
     *
     * @param object|string $object
     * @param string $name
     *
     * @throws PropertyException
     * @throws ReflectionException
     *
     * @return ReflectionProperty
     */
    public static function getProperty($object, $name)
    {
        $reflection = is_object($object) ? new ReflectionObject($object) : new ReflectionClass($object);

        if ($reflection->hasProperty($name)) {
            return $reflection->getProperty($name);
        }

        if ($parentClass = $reflection->getParentClass()) {
            return self::getProperty($parentClass->getName(), $name);
        }

        throw new PropertyException(
            sprintf(
                'The class "%s" doesn\'t have a property with the given name: "%s".',
                is_object($object) ? get_class($object) : $object,
                $name
            )
        );
    }
}
<?php

namespace DeepCopy\TypeFilter;

/**
 * @final
 */
class ReplaceFilter implements TypeFilter
{
    /**
     * @var callable
     */
    protected $callback;

    /**
     * @param callable $callable Will be called to get the new value for each element to replace
     */
    public function __construct(callable $callable)
    {
        $this->callback = $callable;
    }

    /**
     * {@inheritdoc}
     */
    public function apply($element)
    {
        return call_user_func($this->callback, $element);
    }
}
<?php

namespace DeepCopy\TypeFilter\Spl;

use Closure;
use DeepCopy\DeepCopy;
use DeepCopy\TypeFilter\TypeFilter;
use SplDoublyLinkedList;

/**
 * @final
 */
class SplDoublyLinkedListFilter implements TypeFilter
{
    private $copier;

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

    /**
     * {@inheritdoc}
     */
    public function apply($element)
    {
        $newElement = clone $element;

        $copy = $this->createCopyClosure();

        return $copy($newElement);
    }

    private function createCopyClosure()
    {
        $copier = $this->copier;

        $copy = function (SplDoublyLinkedList $list) use ($copier) {
            // Replace each element in the list with a deep copy of itself
            for ($i = 1; $i <= $list->count(); $i++) {
                $copy = $copier->recursiveCopy($list->shift());

                $list->push($copy);
            }

            return $list;
        };

        return Closure::bind($copy, null, DeepCopy::class);
    }
}
<?php

namespace DeepCopy\TypeFilter\Spl;

/**
 * @deprecated Use {@see SplDoublyLinkedListFilter} instead.
 */
class SplDoublyLinkedList extends SplDoublyLinkedListFilter
{
}
<?php

namespace DeepCopy\TypeFilter\Date;

use DateInterval;
use DeepCopy\TypeFilter\TypeFilter;

/**
 * @final
 *
 * @deprecated Will be removed in 2.0. This filter will no longer be necessary in PHP 7.1+.
 */
class DateIntervalFilter implements TypeFilter
{

    /**
     * {@inheritdoc}
     *
     * @param DateInterval $element
     *
     * @see http://news.php.net/php.bugs/205076
     */
    public function apply($element)
    {
        $copy = new DateInterval('P0D');

        foreach ($element as $propertyName => $propertyValue) {
            $copy->{$propertyName} = $propertyValue;
        }

        return $copy;
    }
}
<?php

namespace DeepCopy\TypeFilter;

/**
 * @final
 */
class ShallowCopyFilter implements TypeFilter
{
    /**
     * {@inheritdoc}
     */
    public function apply($element)
    {
        return clone $element;
    }
}
<?php

namespace DeepCopy\TypeFilter;

interface TypeFilter
{
    /**
     * Applies the filter to the object.
     *
     * @param mixed $element
     */
    public function apply($element);
}
<?php

// autoload.php @generated by Composer

require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInit555f246cb7db6869b6fb9f48d5e5f3d2::getLoader();
<?php

/**
 * Usage:
 * php spec/parser.php path.to.spec.json:
 *   - php spec/parser.php amqp-rabbitmq-0.8.json
 *   - php spec/parser.php amqp-rabbitmq-0.9.1.json
 */

if (empty($argv[1])) {
    echo "ERROR: You must provide a protocol file to parse.\n";
    echo "Usage: php spec/parser.php amqp-rabbitmq-0.9.1.json.\n";
    die(1);
}

$spec = file_get_contents(__DIR__ . "/" . $argv[1]);

$json_spec = json_decode($spec, true);

function to_camel_case($amqp_method)
{
    $words = explode('-', $amqp_method);
    $ret = array();
    foreach ($words as $w) {
        $ret[] = ucfirst($w);
    }

    return implode('', $ret);
}

function method_name($amqp_class, $amqp_method)
{
    return $amqp_class . to_camel_case($amqp_method);
}

function to_snake_case($arg)
{
    return str_replace('-', '_', $arg);
}

function addPhpDocParams($arguments)
{
    $ret = array();
    foreach ($arguments as $arg) {
        $ret[] = ' * @param ' . translateType($arg) . ' $' . to_snake_case($arg['name']);
    }

    return implode("\n", $ret);
}

function translateType($argument)
{
    $type = null;
    if (array_key_exists('default-value', $argument)) {
        $type = gettype($argument['default-value']);
    } elseif (array_key_exists('type', $argument)) {
        $type = $argument['type'];
    }

    switch ($type) {
        case 'longstr':
        case 'shortstr':
        case 'string':
            return 'string';
        case 'short':
        case 'long':
        case 'longlong':
        case 'integer':
        case 'int':
            return 'int';
        case 'bit':
        case 'boolean':
        case 'bool':
            return 'bool';
        case 'array':
            return 'array';
        default:
            return 'mixed';
    }
}

function argument_default_val($arg)
{
    return isset($arg['default-value']) ? " = " . default_value_to_string($arg['default-value']) : "";
}

function default_value_to_string($value)
{
    if (is_array($value)) {
        return 'array(' . implode(', ', array_map('default_value_to_string', $value)) . ')';
    }

    return var_export($value, true);
}

function indent($s, $level = 1, $chars = "    ")
{
    if ($level > 0) {
        $s = preg_replace('#(?:^|[\r\n]+)(?=[^\r\n])#', '$0' . str_repeat($chars, $level), $s);
    }

    return $s;
}

function add_method_arguments($arguments)
{
    $ret = array();
    foreach ($arguments as $arg) {
        $ret[] = '$' . to_snake_case($arg['name']) . argument_default_val($arg);
    }

    return implode(", ", $ret);
}

/**
 * @param array $domains
 * @param string $domain
 * @return string
 * @throws Exception
 */
function domain_to_type($domains, $domain)
{
    foreach ($domains as $d) {
        if ($d[0] == $domain) {
            return $d[1];
        }
    }
    throw new \Exception("Invalid domain: " . $domain);
}

/**
 * @param array $domains
 * @param array $arg
 * @return string
 * @throws Exception
 */
function argument_type($domains, $arg)
{
    return isset($arg['type']) ? $arg['type'] : domain_to_type($domains, $arg['domain']);
}

class ArgumentWriter
{
    protected $bit_args = array();

    public function call_write_argument($domains, $arg)
    {
        $a_type = argument_type($domains, $arg);
        if ($a_type == 'bit') {
            $this->bit_args[] = '$' . to_snake_case($arg['name']);
            $ret = '';

        } else {
            $ret = $this->write_bits();

            $a_name = '$' . to_snake_case($arg['name']);
            $ret .= '$writer->write_' . $a_type . '(' . ($a_type === 'table' ? 'empty(' . $a_name . ') ? array() : ' : '') . $a_name . ");\n";
        }

        return $ret;
    }


    public function write_bits()
    {
        if (empty($this->bit_args)) {
            return '';
        }

        $ret = '$writer->write_bits(array(' . implode(', ', $this->bit_args) . "));\n";
        $this->bit_args = array();

        return $ret;
    }
}

function call_read_argument($domains, $arg)
{
    return '$reader->read_' . argument_type($domains, $arg) . "();\n";
}

function protocol_version($json_spec)
{
    if (isset($json_spec['revision'])) {
        return $json_spec['major-version'] . $json_spec['minor-version'] . $json_spec['revision'];
    } else {
        return '0' . $json_spec['major-version'] . $json_spec['minor-version'];
    }
}

function protocol_header($json_spec)
{
    if (isset($json_spec['revision'])) {
        $args = array(0, $json_spec['major-version'], $json_spec['minor-version'], $json_spec['revision']);

    } else {
        $args = array(1, 1, $json_spec['major-version'], $json_spec['minor-version']);
    }

    array_unshift($args, 'AMQP\x%02x\x%02x\x%02x\x%02x');

    return '"' . call_user_func_array('sprintf', $args) . '"';
}

$argumentWriter = new ArgumentWriter();

$out = '<?php' . "\n\n";
$out .= '/* This file was autogenerated by spec/parser.php - Do not modify */' . "\n\n";
$out .= 'namespace PhpAmqpLib\Helper\Protocol;' . "\n\n";
$out .= 'use PhpAmqpLib\Wire\AMQPWriter;' . "\n";
$out .= 'use PhpAmqpLib\Wire\AMQPReader;' . "\n\n";
$out .= 'class Protocol' . protocol_version($json_spec) . "\n";
$out .= "{";

$methods = '';

foreach ($json_spec['classes'] as $c) {
    foreach ($c['methods'] as $m) {
        $methods .= "\n";

        if ($m['id'] % 10 == 0) {
            $methodBody = '$writer = new AMQPWriter();' . "\n";
            foreach ($m['arguments'] as $arg) {
                $methodBody .= $argumentWriter->call_write_argument($json_spec['domains'], $arg);
            }
            $methodBody .= $argumentWriter->write_bits();
            $methodBody .= 'return array(' . $c['id'] . ", " . $m['id'] . ', $writer);';

            $methods .= '/**' . "\n";
            $methods .= addPhpDocParams($m['arguments']) . "\n";
            $methods .= ' * @return array' . "\n";
            $methods .= ' */' . "\n";
            $methods .= 'public function ' . method_name($c['name'], $m['name']) . "(";
            $methods .= add_method_arguments($m['arguments']);
            $methods .= ")\n{\n";
            $methods .= indent($methodBody) . "\n";
            $methods .= "}\n";

        } else {
            $methodBody = '$response = array();' . "\n";
            foreach ($m['arguments'] as $arg) {
                $methodBody .= '$response[] = ' . call_read_argument($json_spec['domains'], $arg);
            }
            $methodBody .= 'return $response;';

            $methods .= '/**' . "\n";
            $methods .= ' * @param AMQPReader $reader' . "\n";
            $methods .= ' * @return array' . "\n";
            $methods .= ' */' . "\n";
            $methods .= 'public static function ' . method_name($c['name'], $m['name'])
                . '(AMQPReader $reader)' . "\n{\n";
            $methods .= indent($methodBody) . "\n";
            $methods .= "}\n";
        }
    }
}

$out .= indent(rtrim($methods)) . "\n";
$out .= "}\n";

file_put_contents(__DIR__ . '/../PhpAmqpLib/Helper/Protocol/Protocol' . protocol_version($json_spec) . '.php', $out);

function export_property($ret)
{
    if (!is_array($ret)) {
        return var_export($ret, true);
    }

    $code = '';
    foreach ($ret as $key => $value) {
        $code .= var_export($key, true) . ' => ' . export_property($value) . ",\n";
    }

    return "array(\n" . indent($code) . ")";
}

function frame_types($json_spec)
{
    $ret = array();
    foreach ($json_spec['constants'] as $c) {
        if (mb_substr($c['name'], 0, 5, 'ASCII') == "FRAME") {
            $ret[$c['value']] = $c['name'];
        }
    }

    return export_property($ret);
}

function content_methods($json_spec)
{
    $ret = array();
    foreach ($json_spec['classes'] as $c) {
        foreach ($c['methods'] as $m) {
            if (isset($m['content']) && $m['content']) {
                $ret[] = $c['id'] . "," . $m['id'];
            }
        }
    }

    return export_property($ret);
}

function close_methods($json_spec)
{
    $ret = array();
    foreach ($json_spec['classes'] as $c) {
        foreach ($c['methods'] as $m) {
            if ($m['name'] == 'close') {
                $ret[] = $c['id'] . "," . $m['id'];
            }
        }
    }

    return export_property($ret);
}

function global_method_names($json_spec)
{
    $ret = array();
    foreach ($json_spec['classes'] as $c) {
        foreach ($c['methods'] as $m) {
            $ret[$c['id'] . "," . $m['id']] = ucfirst($c['name']) . '.' . to_snake_case($m['name']);
        }
    }

    return export_property($ret);
}

/**
 * @param string $type
 * @param string $variableName
 * @param string $returnType (optional)
 * @return string
 */
function get_type_phpdoc($type, $variableName = null, $returnType = null)
{
    $properties = "/**\n";
    $properties .= " * @var " . $type;

    if ($variableName != null) {
        $properties .= " " . $variableName;
    }
    $properties .= "\n";

    if ($returnType != null) {
        $properties .= " * @return " . $returnType . "\n";
    }

    $properties .= " */\n";

    return $properties;
}

$properties = get_type_phpdoc('string');
$properties .= 'public static $AMQP_PROTOCOL_HEADER = ' . protocol_header($json_spec) . ";\n\n";
$properties .= get_type_phpdoc('array');
$properties .= 'public static $FRAME_TYPES = ' . frame_types($json_spec) . ";\n\n";
$properties .= get_type_phpdoc('array');
$properties .= 'public static $CONTENT_METHODS = ' . content_methods($json_spec) . ";\n\n";
$properties .= get_type_phpdoc('array');
$properties .= 'public static $CLOSE_METHODS = ' . close_methods($json_spec) . ";\n\n";
$properties .= get_type_phpdoc('array');
$properties .= 'public static $GLOBAL_METHOD_NAMES = ' . global_method_names($json_spec) . ";\n";

$out = '<?php' . "\n\n";
$out .= '/* This file was autogenerated by spec/parser.php - Do not modify */' . "\n\n";
$out .= 'namespace PhpAmqpLib\Wire;' . "\n\n";
$out .= "class Constants" . protocol_version($json_spec) . "\n";
$out .= "{\n";
$out .= indent($properties);
$out .= "}\n";

file_put_contents(__DIR__ . '/../PhpAmqpLib/Wire/Constants' . protocol_version($json_spec) . '.php', $out);

function method_waits($json_spec)
{
    $ret = array();
    foreach ($json_spec['classes'] as $c) {
        foreach ($c['methods'] as $m) {
            $ret[$c['name'] . '.' . to_snake_case($m['name'])] = $c['id'] . "," . $m['id'];
        }
    }

    return export_property($ret);
}

$classBody = '';
$classBody .= get_type_phpdoc('array');
$classBody .= "protected \$wait = " . method_waits($json_spec) . ";\n\n";
$classBody .= get_type_phpdoc('string', '$method', 'string');
$classBody .= 'public function get_wait($method)' . "\n{\n";
$classBody .= indent('return $this->wait[$method];') . "\n";
$classBody .= "}";

$out = '<?php' . "\n\n";
$out .= '/* This file was autogenerated by spec/parser.php - Do not modify */' . "\n\n";
$out .= 'namespace PhpAmqpLib\Helper\Protocol;' . "\n\n";
$out .= "class Wait" . protocol_version($json_spec) . "\n";
$out .= "{\n";
$out .= indent($classBody) . "\n";
$out .= "}\n";

file_put_contents(__DIR__ . '/../PhpAmqpLib/Helper/Protocol/Wait' . protocol_version($json_spec) . '.php', $out);

function method_map($json_spec)
{
    $ret = array();

    $special_map = array(
        '60,30' => 'basic_cancel_from_server',
        '60,80' => 'basic_ack_from_server',
        '60,120' => 'basic_nack_from_server'
    );

    foreach ($json_spec['classes'] as $c) {
        foreach ($c['methods'] as $m) {
            if (isset($special_map[$c['id'] . ',' . $m['id']]) && protocol_version($json_spec) == '091') {
                $ret[$c['id'] . "," . $m['id']] = $special_map[$c['id'] . ',' . $m['id']];
            } else {
                $ret[$c['id'] . "," . $m['id']] = $c['name'] . '_' . to_snake_case($m['name']);
            }
        }
    }

    return export_property($ret);
}

$classBody = '';
$classBody .= get_type_phpdoc('array');
$classBody .= 'protected $method_map = ' . method_map($json_spec) . ";\n\n";
$classBody .= get_type_phpdoc('string', '$method_sig', 'string');
$classBody .= 'public function get_method($method_sig)' . "\n{\n";
$classBody .= indent('return $this->method_map[$method_sig];') . "\n";
$classBody .= "}\n\n";
$classBody .= get_type_phpdoc('string', '$method_sig', 'bool');
$classBody .= 'public function valid_method($method_sig)' . "\n{\n";
$classBody .= indent('return array_key_exists($method_sig, $this->method_map);') . "\n";
$classBody .= "}";

$out = '<?php' . "\n\n";
$out .= '/* This file was autogenerated by spec/parser.php - Do not modify */' . "\n\n";
$out .= 'namespace PhpAmqpLib\Helper\Protocol;' . "\n\n";
$out .= "class MethodMap" . protocol_version($json_spec) . "\n";
$out .= "{\n";
$out .= indent($classBody) . "\n";
$out .= "}\n";

file_put_contents(__DIR__ . '/../PhpAmqpLib/Helper/Protocol/MethodMap' . protocol_version($json_spec) . '.php', $out);
{
    "name": "AMQP",
    "major-version": 8,
    "minor-version": 0,
    "port": 5672,
    "copyright": [
        "Copyright (C) 2008-2012 VMware, Inc.\n",
        "\n",
        "Permission is hereby granted, free of charge, to any person\n",
        "obtaining a copy of this file (the \"Software\"), to deal in the\n",
        "Software without restriction, including without limitation the \n",
        "rights to use, copy, modify, merge, publish, distribute, \n",
        "sublicense, and/or sell copies of the Software, and to permit \n",
        "persons to whom the Software is furnished to do so, subject to \n",
        "the following conditions:\n",
        "\n",
        "The above copyright notice and this permission notice shall be\n",
        "included in all copies or substantial portions of the Software.\n",
        "\n",
        "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n",
        "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n",
        "OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n",
        "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n",
        "HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n",
        "WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n",
        "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n",
        "OTHER DEALINGS IN THE SOFTWARE.\n",
        "\n",
        "Class information entered from amqp_xml0-8.pdf and domain types from amqp-xml-doc0-9.pdf\n",
        "\n",
        "b3cb053f15e7b98808c0ccc67f23cb3e  amqp_xml0-8.pdf\n",
        "http://www.twiststandards.org/index.php?option=com_docman&task=cat_view&gid=28&&Itemid=90\n",
        "8444db91e2949dbecfb2585e9eef6d64  amqp-xml-doc0-9.pdf\n",
        "https://jira.amqp.org/confluence/download/attachments/720900/amqp-xml-doc0-9.pdf?version=1\n"],

    "domains": [
        ["access-ticket", "short"],
        ["bit", "bit"],
        ["channel-id", "longstr"],
        ["class-id", "short"],
        ["consumer-tag", "shortstr"],
        ["delivery-tag", "longlong"],
        ["destination", "shortstr"],
        ["duration", "longlong"],
        ["exchange-name", "shortstr"],
        ["known-hosts", "shortstr"],
        ["long", "long"],
        ["longlong", "longlong"],
        ["longstr", "longstr"],
        ["method-id", "short"],
        ["no-ack", "bit"],
        ["no-local", "bit"],
        ["octet", "octet"],
        ["offset", "longlong"],
        ["path", "shortstr"],
        ["peer-properties", "table"],
        ["queue-name", "shortstr"],
        ["redelivered", "bit"],
        ["reference", "longstr"],
        ["reject-code", "short"],
        ["reject-text", "shortstr"],
        ["reply-code", "short"],
        ["reply-text", "shortstr"],
        ["security-token", "longstr"],
        ["short", "short"],
        ["shortstr", "shortstr"],
        ["table", "table"],
        ["timestamp", "timestamp"]
    ],

    "constants": [
        {"name": "FRAME-METHOD", "value": 1},
        {"name": "FRAME-HEADER", "value": 2},
        {"name": "FRAME-BODY", "value": 3},
        {"name": "FRAME-OOB-METHOD", "value": 4},
        {"name": "FRAME-OOB-HEADER", "value": 5},
        {"name": "FRAME-OOB-BODY", "value": 6},
        {"name": "FRAME-TRACE", "value": 7},
        {"name": "FRAME-HEARTBEAT", "value": 8},
        {"name": "FRAME-MIN-SIZE", "value": 4096},
        {"name": "FRAME-END", "value": 206},
        {"name": "REPLY-SUCCESS", "value": 200},
        {"name": "NOT-DELIVERED", "value": 310, "class": "soft-error"},
        {"name": "CONTENT-TOO-LARGE", "value": 311, "class": "soft-error"},
        {"name": "NO-ROUTE", "value": 312, "class": "soft-error"},
        {"name": "NO-CONSUMERS", "value": 313, "class": "soft-error"},
        {"name": "ACCESS-REFUSED", "value": 403, "class": "soft-error"},
        {"name": "NOT-FOUND", "value": 404, "class": "soft-error"},
        {"name": "RESOURCE-LOCKED", "value": 405, "class": "soft-error"},
        {"name": "PRECONDITION-FAILED", "value": 406, "class": "soft-error"},
        {"name": "CONNECTION-FORCED", "value": 320, "class": "hard-error"},
        {"name": "INVALID-PATH", "value": 402, "class": "hard-error"},
        {"name": "FRAME-ERROR", "value": 501, "class": "hard-error"},
        {"name": "SYNTAX-ERROR", "value": 502, "class": "hard-error"},
        {"name": "COMMAND-INVALID", "value": 503, "class": "hard-error"},
        {"name": "CHANNEL-ERROR", "value": 504, "class": "hard-error"},
        {"name": "UNEXPECTED-FRAME", "value": 505, "class": "hard-error"},
        {"name": "RESOURCE-ERROR", "value": 506, "class": "hard-error"},
        {"name": "NOT-ALLOWED", "value": 530, "class": "hard-error"},
        {"name": "NOT-IMPLEMENTED", "value": 540, "class": "hard-error"},
        {"name": "INTERNAL-ERROR", "value": 541, "class": "hard-error"}
    ],

    "classes": [
        {
            "id": 10,
            "methods": [{"id": 10,
                         "arguments": [{"type": "octet", "name": "version-major", "default-value": 0},
                                       {"type": "octet", "name": "version-minor", "default-value": 8},
                                       {"domain": "peer-properties", "name": "server-properties"},
                                       {"type": "longstr", "name": "mechanisms", "default-value": "PLAIN"},
                                       {"type": "longstr", "name": "locales", "default-value": "en_US"}],
                         "name": "start",
                         "synchronous" : true},
                        {"id": 11,
                         "arguments": [{"domain": "peer-properties", "name": "client-properties"},
                                       {"type": "shortstr", "name": "mechanism", "default-value": "PLAIN"},
                                       {"type": "longstr", "name": "response"},
                                       {"type": "shortstr", "name": "locale", "default-value": "en_US"}],
                         "name": "start-ok"},
                        {"id": 20,
                         "arguments": [{"type": "longstr", "name": "challenge"}],
                         "name": "secure",
                         "synchronous" : true},
                        {"id": 21,
                         "arguments": [{"type": "longstr", "name": "response"}],
                         "name": "secure-ok"},
                        {"id": 30,
                         "arguments": [{"type": "short", "name": "channel-max", "default-value": 0},
                                       {"type": "long", "name": "frame-max", "default-value": 0},
                                       {"type": "short", "name": "heartbeat", "default-value": 0}],
                         "name": "tune",
                         "synchronous" : true},
                        {"id": 31,
                         "arguments": [{"type": "short", "name": "channel-max", "default-value": 0},
                                       {"type": "long", "name": "frame-max", "default-value": 0},
                                       {"type": "short", "name": "heartbeat", "default-value": 0}],
                         "name": "tune-ok"},
                        {"id": 40,
                         "arguments": [{"type": "shortstr", "name": "virtual-host", "default-value": "/"},
                                       {"type": "shortstr", "name": "capabilities", "default-value": ""},
                                       {"type": "bit", "name": "insist", "default-value": false}],
                         "name": "open",
                         "synchronous" : true},
                        {"id": 41,
                         "arguments": [{"type": "shortstr", "name": "known-hosts", "default-value": ""}],
                         "name": "open-ok"},
                        {"id": 50,
                         "arguments": [{"type": "shortstr", "name": "host"},
                                       {"type": "shortstr", "name": "known-hosts", "default-value": ""}],
                         "name": "redirect"},
                        {"id": 60,
                         "arguments": [{"type": "short", "name": "reply-code"},
                                       {"type": "shortstr", "name": "reply-text", "default-value": ""},
                                       {"type": "short", "name": "class-id"},
                                       {"type": "short", "name": "method-id"}],
                         "name": "close",
                         "synchronous" : true},
                        {"id": 61,
                         "arguments": [],
                         "name": "close-ok"}],
            "name": "connection",
            "properties": []
        },
        {
            "id": 20,
            "methods": [{"id": 10,
                         "arguments": [{"type": "shortstr", "name": "out-of-band", "default-value": ""}],
                         "name": "open",
                         "synchronous" : true},
                        {"id": 11,
                         "arguments": [],
                         "name": "open-ok"},
                        {"id": 20,
                         "arguments": [{"type": "bit", "name": "active"}],
                         "name": "flow",
                         "synchronous" : true},
                        {"id": 21,
                         "arguments": [{"type": "bit", "name": "active"}],
                         "name": "flow-ok"},
                        {"id": 30,
                         "arguments": [{"type": "short", "name": "reply-code"},
                                       {"type": "shortstr", "name": "reply-text", "default-value": ""},
                                       {"type": "table", "name": "details", "default-value": {}}],
                         "name": "alert"},
                        {"id": 40,
                         "arguments": [{"type": "short", "name": "reply-code"},
                                       {"type": "shortstr", "name": "reply-text", "default-value": ""},
                                       {"type": "short", "name": "class-id"},
                                       {"type": "short", "name": "method-id"}],
                         "name": "close",
                         "synchronous" : true},
                        {"id": 41,
                         "arguments": [],
                         "name": "close-ok"}],
            "name": "channel"
        },
        {
            "id": 30,
            "methods": [{"id": 10,
                         "arguments": [{"type": "shortstr", "name": "realm", "default-value": "/data"},
                                       {"type": "bit", "name": "exclusive", "default-value": false},
                                       {"type": "bit", "name": "passive", "default-value": true},
                                       {"type": "bit", "name": "active", "default-value": true},
                                       {"type": "bit", "name": "write", "default-value": true},
                                       {"type": "bit", "name": "read", "default-value": true}],
                         "name": "request",
                         "synchronous" : true},
                        {"id": 11,
                    "arguments": [{"type": "short", "name": "ticket", "default-value": 1}],
                         "name": "request-ok"}],
            "name": "access"
        },
        {
            "id": 40,
            "methods": [{"id": 10,
                         "arguments": [{"type": "short", "name": "ticket", "default-value": 1},
                                       {"type": "shortstr", "name": "exchange"},
                                       {"type": "shortstr", "name": "type", "default-value": "direct"},
                                       {"type": "bit", "name": "passive", "default-value": false},
                                       {"type": "bit", "name": "durable", "default-value": false},
                                       {"type": "bit", "name": "auto-delete", "default-value": false},
                                       {"type": "bit", "name": "internal", "default-value": false},
                                       {"type": "bit", "name": "nowait", "default-value": false},
                                       {"type": "table", "name": "arguments", "default-value": {}}],
                         "name": "declare",
                         "synchronous" : true},
                        {"id": 11,
                         "arguments": [],
                         "name": "declare-ok"},
                        {"id": 20,
                         "arguments": [{"type": "short", "name": "ticket", "default-value": 1},
                                       {"type": "shortstr", "name": "exchange"},
                                       {"type": "bit", "name": "if-unused", "default-value": false},
                                       {"type": "bit", "name": "nowait", "default-value": false}],
                         "name": "delete",
                         "synchronous" : true},
                        {"id": 21,
                         "arguments": [],
                         "name": "delete-ok"}],
            "name": "exchange"
        },
        {
            "id": 50,
            "methods": [{"id": 10,
                         "arguments": [{"type": "short", "name": "ticket", "default-value": 1},
                           {"type": "shortstr", "name": "queue", "default-value": ""},
                                       {"type": "bit", "name": "passive", "default-value": false},
                                       {"type": "bit", "name": "durable", "default-value": false},
                                       {"type": "bit", "name": "exclusive", "default-value": false},
                                       {"type": "bit", "name": "auto-delete", "default-value": false},
                                       {"type": "bit", "name": "nowait", "default-value": false},
                                       {"type": "table", "name": "arguments", "default-value": {}}],
                         "name": "declare",
                         "synchronous" : true},
                        {"id": 11,
                         "arguments": [{"type": "shortstr", "name": "queue"},
                                       {"type": "long", "name": "message-count"},
                                       {"type": "long", "name": "consumer-count"}],
                         "name": "declare-ok"},
                        {"id": 20,
                         "arguments": [{"type": "short", "name": "ticket", "default-value": 1},
                                       {"type": "shortstr", "name": "queue", "default-value": ""},
                                       {"type": "shortstr", "name": "exchange"},
                                       {"type": "shortstr", "name": "routing-key", "default-value": ""},
                                       {"type": "bit", "name": "nowait", "default-value": false},
                                       {"type": "table", "name": "arguments", "default-value": {}}],
                         "name": "bind",
                         "synchronous" : true},
                        {"id": 21,
                         "arguments": [],
                         "name": "bind-ok"},
                        {"id": 30,
                         "arguments": [{"type": "short", "name": "ticket", "default-value": 1},
                                       {"type": "shortstr", "name": "queue", "default-value": ""},
                                       {"type": "bit", "name": "nowait", "default-value": false}],
                         "name": "purge",
                         "synchronous" : true},
                        {"id": 31,
                         "arguments": [{"type": "long", "name": "message-count"}],
                         "name": "purge-ok"},
                        {"id": 40,
                         "arguments": [{"type": "short", "name": "ticket", "default-value": 1},
                                       {"type": "shortstr", "name": "queue", "default-value": ""},
                                       {"type": "bit", "name": "if-unused", "default-value": false},
                                       {"type": "bit", "name": "if-empty", "default-value": false},
                                       {"type": "bit", "name": "nowait", "default-value": false}],
                         "name": "delete",
                         "synchronous" : true},
                        {"id": 41,
                         "arguments": [{"type": "long", "name": "message-count"}],
                         "name": "delete-ok"},
            {"id": 50,
                         "arguments": [{"type": "short", "name": "ticket", "default-value": 1},
                                       {"type": "shortstr", "name": "queue", "default-value": ""},
                                       {"type": "shortstr", "name": "exchange"},
                                       {"type": "shortstr", "name": "routing-key", "default-value": ""},
                                       {"type": "table", "name": "arguments", "default-value": {}}],
                         "name": "unbind",
                         "synchronous" : true},
                        {"id": 51,
                         "arguments": [],
                         "name": "unbind-ok"}
                        ],
            "name": "queue"
        },
        {
            "id": 60,
            "methods": [{"id": 10,
                         "arguments": [{"type": "long", "name": "prefetch-size", "default-value": 0},
                                       {"type": "short", "name": "prefetch-count", "default-value": 0},
                                       {"type": "bit", "name": "global", "default-value": false}],
                         "name": "qos",
                         "synchronous" : true},
                        {"id": 11,
                         "arguments": [],
                         "name": "qos-ok"},
                        {"id": 20,
                         "arguments": [{"domain": "access-ticket", "name": "ticket", "default-value": 1},
                                       {"type": "shortstr", "name": "queue", "default-value": ""},
                                       {"type": "shortstr", "name": "consumer-tag", "default-value": ""},
                                       {"type": "bit", "name": "no-local", "default-value": false},
                                       {"type": "bit", "name": "no-ack", "default-value": false},
                                       {"type": "bit", "name": "exclusive", "default-value": false},
                                       {"type": "bit", "name": "nowait", "default-value": false}],
                         "name": "consume",
                         "synchronous" : true},
                        {"id": 21,
                         "arguments": [{"type": "shortstr", "name": "consumer-tag"}],
                         "name": "consume-ok"},
                        {"id": 30,
                         "arguments": [{"type": "shortstr", "name": "consumer-tag"},
                                       {"type": "bit", "name": "nowait", "default-value": false}],
                         "name": "cancel",
                         "synchronous" : true},
                        {"id": 31,
                         "arguments": [{"type": "shortstr", "name": "consumer-tag"}],
                         "name": "cancel-ok"},
                        {"content": true,
                         "id": 40,
                         "arguments": [{"type": "short", "name": "ticket", "default-value": 1},
                                       {"type": "shortstr", "name": "exchange", "default-value": ""},
                                       {"type": "shortstr", "name": "routing-key", "default-value": ""},
                                       {"type": "bit", "name": "mandatory", "default-value": false},
                                       {"type": "bit", "name": "immediate", "default-value": false}],
                         "name": "publish"},
                        {"content": true,
                         "id": 50,
                         "arguments": [{"type": "short", "name": "reply-code"},
                                       {"type": "shortstr", "name": "reply-text", "default-value": ""},
                                       {"type": "shortstr", "name": "exchange"},
                                       {"type": "shortstr", "name": "routing-key"}],
                         "name": "return"},
                        {"content": true,
                         "id": 60,
                         "arguments": [{"type": "shortstr", "name": "consumer-tag"},
                                       {"type": "longlong", "name": "delivery-tag"},
                                       {"type": "bit", "name": "redelivered", "default-value": false},
                                       {"type": "shortstr", "name": "exchange"},
                                       {"type": "shortstr", "name": "routing-key"}],
                         "name": "deliver"},
                        {"id": 70,
                         "arguments": [{"type": "short", "name": "ticket", "default-value": 1},
                                       {"type": "shortstr", "name": "queue", "default-value": ""},
                                       {"type": "bit", "name": "no-ack", "default-value": false}],
                         "name": "get",
                         "synchronous" : true},
                        {"content": true,
                         "id": 71,
                         "arguments": [{"type": "longlong", "name": "delivery-tag"},
                                       {"type": "bit", "name": "redelivered", "default-value": false},
                                       {"type": "shortstr", "name": "exchange"},
                                       {"type": "shortstr", "name": "routing-key"},
                                       {"type": "long", "name": "message-count"}],
                         "name": "get-ok"},
                        {"id": 72,
                         "arguments": [{"type": "shortstr", "name": "cluster-id", "default-value": ""}],
                         "name": "get-empty"},
                        {"id": 80,
                         "arguments": [{"type": "longlong", "name": "delivery-tag", "default-value": 0},
                                       {"type": "bit", "name": "multiple", "default-value": false}],
                         "name": "ack"},
                        {"id": 90,
                         "arguments": [{"type": "longlong", "name": "delivery-tag"},
                                       {"type": "bit", "name": "requeue", "default-value": true}],
                         "name": "reject"},
                        {"id": 100,
                         "arguments": [{"type": "bit", "name": "requeue", "default-value": false}],
                         "name": "recover-async"},
            {"id": 110,
                         "arguments": [{"type": "bit", "name": "requeue", "default-value": false}],
                         "name": "recover",
                         "synchronous" : true},
                        {"id": 111,
                         "arguments": [],
                         "name": "recover-ok"}],
            "name": "basic",
            "properties": [{"type": "shortstr", "name": "content-type"},
                           {"type": "shortstr", "name": "content-encoding"},
                           {"type": "table", "name": "headers"},
                           {"type": "octet", "name": "delivery-mode"},
                           {"type": "octet", "name": "priority"},
                           {"type": "shortstr", "name": "correlation-id"},
                           {"type": "shortstr", "name": "reply-to"},
                           {"type": "shortstr", "name": "expiration"},
                           {"type": "shortstr", "name": "message-id"},
                           {"type": "timestamp", "name": "timestamp"},
                           {"type": "shortstr", "name": "type"},
                           {"type": "shortstr", "name": "user-id"},
                           {"type": "shortstr", "name": "app-id"},
                           {"type": "shortstr", "name": "cluster-id"}]
        },
        {
            "id": 70,
            "methods": [{"id": 10,
                         "arguments": [{"type": "long", "name": "prefetch-size", "default-value": 0},
                                       {"type": "short", "name": "prefetch-count", "default-value": 0},
                                       {"type": "bit", "name": "global", "default-value": false}],
                         "name": "qos",
                         "synchronous" : true},
                        {"id": 11,
                         "arguments": [],
                         "name": "qos-ok"},
                        {"id": 20,
                         "arguments": [{"type": "short", "name": "ticket", "default-value": 1},
                                       {"type": "shortstr", "name": "queue", "default-value": ""},
                                       {"type": "shortstr", "name": "consumer-tag", "default-value": ""},
                                       {"type": "bit", "name": "no-local", "default-value": false},
                                       {"type": "bit", "name": "no-ack", "default-value": false},
                                       {"type": "bit", "name": "exclusive", "default-value": false},
                                       {"type": "bit", "name": "nowait", "default-value": false}],
                         "name": "consume",
                         "synchronous" : true},
                        {"id": 21,
                         "arguments": [{"type": "shortstr", "name": "consumer-tag"}],
                         "name": "consume-ok"},
                        {"id": 30,
                         "arguments": [{"type": "shortstr", "name": "consumer-tag"},
                                       {"type": "bit", "name": "nowait", "default-value": false}],
                         "name": "cancel",
                         "synchronous" : true},
                        {"id": 31,
                         "arguments": [{"type": "shortstr", "name": "consumer-tag"}],
                         "name": "cancel-ok"},
                        {"id": 40,
                         "arguments": [{"type": "shortstr", "name": "identifier"},
                                       {"type": "longlong", "name": "content-size"}],
                         "name": "open",
                         "synchronous" : true},
                        {"id": 41,
                         "arguments": [{"type": "longlong", "name": "staged-size"}],
                         "name": "open-ok"},
                        {"content": true,
                         "id": 50,
                         "arguments": [],
                         "name": "stage"},
                        {"id": 60,
                         "arguments": [{"type": "short", "name": "ticket", "default-value": 1},
                                       {"type": "shortstr", "name": "exchange", "default-value": ""},
                                       {"type": "shortstr", "name": "routing-key", "default-value": ""},
                                       {"type": "bit", "name": "mandatory", "default-value": false},
                                       {"type": "bit", "name": "immediate", "default-value": false},
                                       {"type": "shortstr", "name": "identifier"}],
                         "name": "publish"},
                        {"content": true,
                         "id": 70,
                         "arguments": [{"type": "short", "name": "reply-code", "default-value": 200},
                                       {"type": "shortstr", "name": "reply-text", "default-value": ""},
                                       {"type": "shortstr", "name": "exchange"},
                                       {"type": "shortstr", "name": "routing-key"}],
                         "name": "return"},
                        {"id": 80,
                         "arguments": [{"type": "shortstr", "name": "consumer-tag"},
                                       {"type": "longlong", "name": "delivery-tag"},
                                       {"type": "bit", "name": "redelivered", "default-value": false},
                                       {"type": "shortstr", "name": "exchange"},
                                       {"type": "shortstr", "name": "routing-key"},
                                       {"type": "shortstr", "name": "identifier"}],
                         "name": "deliver"},
                        {"id": 90,
                         "arguments": [{"type": "longlong", "name": "delivery-tag", "default-value": 0},
                           {"type": "bit", "name": "multiple", "default-value": false}],
                         "name": "ack"},
                        {"id": 100,
                         "arguments": [{"type": "longlong", "name": "delivery-tag"},
                                       {"type": "bit", "name": "requeue", "default-value": true}],
                         "name": "reject"}],
            "name": "file",
            "properties": [{"type": "shortstr", "name": "content-type"},
                           {"type": "shortstr", "name": "content-encoding"},
                           {"type": "table", "name": "headers"},
                           {"type": "octet", "name": "priority"},
                           {"type": "shortstr", "name": "reply-to"},
                           {"type": "shortstr", "name": "message-id"},
                           {"type": "shortstr", "name": "filename"},
                           {"type": "timestamp", "name": "timestamp"},
                           {"type": "shortstr", "name": "cluster-id"}]
        },
        {
            "id": 80,
            "methods": [{"id": 10,
                         "arguments": [{"type": "long", "name": "prefetch-size", "default-value": 0},
                                       {"type": "short", "name": "prefetch-count", "default-value": 0},
                                       {"type": "long", "name": "consume-rate", "default-value": 0},
                                       {"type": "bit", "name": "global", "default-value": false}],
                         "name": "qos",
                         "synchronous" : true},
                        {"id": 11,
                         "arguments": [],
                         "name": "qos-ok"},
                        {"id": 20,
                         "arguments": [{"type": "short", "name": "ticket", "default-value": 1},
                                       {"type": "shortstr", "name": "queue", "default-value": ""},
                                       {"type": "shortstr", "name": "consumer-tag", "default-value": ""},
                                       {"type": "bit", "name": "no-local", "default-value": false},
                                       {"type": "bit", "name": "exclusive", "default-value": false},
                                       {"type": "bit", "name": "nowait", "default-value": false}],
                         "name": "consume",
                         "synchronous" : true},
                        {"id": 21,
                         "arguments": [{"type": "shortstr", "name": "consumer-tag"}],
                         "name": "consume-ok"},
                        {"id": 30,
                         "arguments": [{"type": "shortstr", "name": "consumer-tag"},
                                       {"type": "bit", "name": "nowait", "default-value": false}],
                         "name": "cancel",
                         "synchronous" : true},
                        {"id": 31,
                         "arguments": [{"type": "shortstr", "name": "consumer-tag"}],
                         "name": "cancel-ok"},
                        {"content": true,
                         "id": 40,
                         "arguments": [{"type": "short", "name": "ticket", "default-value": 1},
                                       {"type": "shortstr", "name": "exchange", "default-value": ""},
                                       {"type": "shortstr", "name": "routing-key", "default-value": ""},
                                       {"type": "bit", "name": "mandatory", "default-value": false},
                                       {"type": "bit", "name": "immediate", "default-value": false}],
                         "name": "publish"},
                        {"content": true,
                         "id": 50,
                         "arguments": [{"type": "short", "name": "reply-code", "default-value": 200},
                                       {"type": "shortstr", "name": "reply-text", "default-value": ""},
                                       {"type": "shortstr", "name": "exchange"},
                                       {"type": "shortstr", "name": "routing-key"}],
                         "name": "return"},
                        {"content": true,
                         "id": 60,
                         "arguments": [{"type": "shortstr", "name": "consumer-tag"},
                                       {"type": "longlong", "name": "delivery-tag"},
                                       {"type": "shortstr", "name": "exchange"},
                                       {"type": "shortstr", "name": "queue"}],
                         "name": "deliver"}],
            "name": "stream",
            "properties": [{"type": "shortstr", "name": "content-type"},
                           {"type": "shortstr", "name": "content-encoding"},
                           {"type": "table", "name": "headers"},
                           {"type": "octet", "name": "priority"},
                           {"type": "timestamp", "name": "timestamp"}]
        },
        {
            "id": 90,
            "methods": [{"id": 10,
                         "arguments": [],
                         "name": "select",
                         "synchronous" : true},
                        {"id": 11,
                         "arguments": [],
                         "name": "select-ok"},
                        {"id": 20,
                         "arguments": [],
                         "name": "commit",
                         "synchronous" : true},
                        {"id": 21,
                         "arguments": [],
                         "name": "commit-ok"},
                        {"id": 30,
                         "arguments": [],
                         "name": "rollback",
                         "synchronous" : true},
                        {"id": 31,
                         "arguments": [],
                         "name": "rollback-ok"}],
            "name": "tx"
        },
        {
            "id": 100,
            "methods": [{"id": 10,
                         "arguments": [],
                         "name": "select",
                         "synchronous" : true},
                        {"id": 11,
                         "arguments": [],
                         "name": "select-ok"},
                        {"id": 20,
                         "arguments": [{"type": "shortstr", "name": "dtx-identifier"}],
                         "name": "start",
                         "synchronous" : true},
                        {"id": 21,
                         "arguments": [], "name": "start-ok"}],
            "name": "dtx"
        },
        {
            "id": 110,
            "methods": [{"content": true,
                         "id": 10,
                         "arguments": [{"type": "table", "name": "meta-data"}],
                         "name": "request"}],
            "name": "tunnel",
            "properties": [{"type": "table", "name": "headers"},
                           {"type": "shortstr", "name": "proxy-name"},
                           {"type": "shortstr", "name": "data-name"},
                           {"type": "octet", "name": "durable"},
                           {"type": "octet", "name": "broadcast"}]
        },
        {
            "id": 120,
            "methods": [{"id": 10,
                         "arguments": [{"type": "octet", "name": "integer-1"},
                                       {"type": "short", "name": "integer-2"},
                                       {"type": "long", "name": "integer-3"},
                                       {"type": "longlong", "name": "integer-4"},
                                       {"type": "octet", "name": "operation"}],
                         "name": "integer",
                         "synchronous" : true},
                        {"id": 11,
                         "arguments": [{"type": "longlong", "name": "result"}],
                         "name": "integer-ok"},
                        {"id": 20,
                         "arguments": [{"type": "shortstr", "name": "string-1"},
                                       {"type": "longstr", "name": "string-2"},
                                       {"type": "octet", "name": "operation"}],
                         "name": "string",
                         "synchronous" : true},
                        {"id": 21,
                         "arguments": [{"type": "longstr", "name": "result"}],
                         "name": "string-ok"},
                        {"id": 30,
                         "arguments": [{"type": "table", "name": "table"},
                                       {"type": "octet", "name": "integer-op"},
                                       {"type": "octet", "name": "string-op"}],
                         "name": "table",
                         "synchronous" : true},
                        {"id": 31,
                         "arguments": [{"type": "longlong", "name": "integer-result"},
                                       {"type": "longstr", "name": "string-result"}],
                         "name": "table-ok"},
                        {"content": true,
                         "id": 40,
                         "arguments": [],
                         "name": "content",
                         "synchronous" : true},
                        {"content": true,
                         "id": 41,
                         "arguments": [{"type": "long", "name": "content-checksum"}],
                         "name": "content-ok"}],
            "name": "test"
        }
    ]
}
{
    "name": "AMQP",
    "major-version": 0,
    "minor-version": 9,
    "revision": 1,
    "port": 5672,
    "copyright": [
        "Copyright (C) 2008-2012 VMware, Inc.\n",
        "\n",
        "Permission is hereby granted, free of charge, to any person\n",
        "obtaining a copy of this file (the \"Software\"), to deal in the\n",
        "Software without restriction, including without limitation the \n",
        "rights to use, copy, modify, merge, publish, distribute, \n",
        "sublicense, and/or sell copies of the Software, and to permit \n",
        "persons to whom the Software is furnished to do so, subject to \n",
        "the following conditions:\n",
        "\n",
        "The above copyright notice and this permission notice shall be\n",
        "included in all copies or substantial portions of the Software.\n",
        "\n",
        "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n",
        "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n",
        "OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n",
        "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n",
        "HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n",
        "WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n",
        "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n",
        "OTHER DEALINGS IN THE SOFTWARE.\n",
        "\n",
        "Class information entered from amqp_xml0-8.pdf and domain types from amqp-xml-doc0-9.pdf\n",
        "Updated for 0-9-1 by Tony Garnock-Jones\n",
        "\n",
        "b3cb053f15e7b98808c0ccc67f23cb3e  amqp_xml0-8.pdf\n",
        "http://www.twiststandards.org/index.php?option=com_docman&task=cat_view&gid=28&&Itemid=90\n",
        "8444db91e2949dbecfb2585e9eef6d64  amqp-xml-doc0-9.pdf\n",
        "https://jira.amqp.org/confluence/download/attachments/720900/amqp-xml-doc0-9.pdf?version=1\n"],

    "domains": [
        ["bit", "bit"],
        ["channel-id", "longstr"],
        ["class-id", "short"],
        ["consumer-tag", "shortstr"],
        ["delivery-tag", "longlong"],
        ["destination", "shortstr"],
        ["duration", "longlong"],
        ["exchange-name", "shortstr"],
        ["long", "long"],
        ["longlong", "longlong"],
        ["longstr", "longstr"],
        ["method-id", "short"],
        ["no-ack", "bit"],
        ["no-local", "bit"],
        ["octet", "octet"],
        ["offset", "longlong"],
        ["path", "shortstr"],
        ["peer-properties", "table"],
        ["queue-name", "shortstr"],
        ["redelivered", "bit"],
        ["reference", "longstr"],
        ["reject-code", "short"],
        ["reject-text", "shortstr"],
        ["reply-code", "short"],
        ["reply-text", "shortstr"],
        ["security-token", "longstr"],
        ["short", "short"],
        ["shortstr", "shortstr"],
        ["table", "table"],
        ["timestamp", "timestamp"]
    ],

    "constants": [
        {"name": "FRAME-METHOD", "value": 1},
        {"name": "FRAME-HEADER", "value": 2},
        {"name": "FRAME-BODY", "value": 3},
        {"name": "FRAME-HEARTBEAT", "value": 8},
        {"name": "FRAME-MIN-SIZE", "value": 4096},
        {"name": "FRAME-END", "value": 206},
        {"name": "REPLY-SUCCESS", "value": 200},
        {"name": "CONTENT-TOO-LARGE", "value": 311, "class": "soft-error"},
        {"name": "NO-ROUTE", "value": 312, "class": "soft-error"},
        {"name": "NO-CONSUMERS", "value": 313, "class": "soft-error"},
        {"name": "ACCESS-REFUSED", "value": 403, "class": "soft-error"},
        {"name": "NOT-FOUND", "value": 404, "class": "soft-error"},
        {"name": "RESOURCE-LOCKED", "value": 405, "class": "soft-error"},
        {"name": "PRECONDITION-FAILED", "value": 406, "class": "soft-error"},
        {"name": "CONNECTION-FORCED", "value": 320, "class": "hard-error"},
        {"name": "INVALID-PATH", "value": 402, "class": "hard-error"},
        {"name": "FRAME-ERROR", "value": 501, "class": "hard-error"},
        {"name": "SYNTAX-ERROR", "value": 502, "class": "hard-error"},
        {"name": "COMMAND-INVALID", "value": 503, "class": "hard-error"},
        {"name": "CHANNEL-ERROR", "value": 504, "class": "hard-error"},
        {"name": "UNEXPECTED-FRAME", "value": 505, "class": "hard-error"},
        {"name": "RESOURCE-ERROR", "value": 506, "class": "hard-error"},
        {"name": "NOT-ALLOWED", "value": 530, "class": "hard-error"},
        {"name": "NOT-IMPLEMENTED", "value": 540, "class": "hard-error"},
        {"name": "INTERNAL-ERROR", "value": 541, "class": "hard-error"}
    ],

    "classes": [
        {
            "id": 10,
            "methods": [{"id": 10,
                         "arguments": [{"type": "octet", "name": "version-major", "default-value": 0},
                                       {"type": "octet", "name": "version-minor", "default-value": 9},
                                       {"domain": "peer-properties", "name": "server-properties"},
                                       {"type": "longstr", "name": "mechanisms", "default-value": "PLAIN"},
                                       {"type": "longstr", "name": "locales", "default-value": "en_US"}],
                         "name": "start",
                         "synchronous" : true},
                        {"id": 11,
                         "arguments": [{"domain": "peer-properties", "name": "client-properties"},
                                       {"type": "shortstr", "name": "mechanism", "default-value": "PLAIN"},
                                       {"type": "longstr", "name": "response"},
                                       {"type": "shortstr", "name": "locale", "default-value": "en_US"}],
                         "name": "start-ok"},
                        {"id": 20,
                         "arguments": [{"type": "longstr", "name": "challenge"}],
                         "name": "secure",
                         "synchronous" : true},
                        {"id": 21,
                         "arguments": [{"type": "longstr", "name": "response"}],
                         "name": "secure-ok"},
                        {"id": 30,
                         "arguments": [{"type": "short", "name": "channel-max", "default-value": 0},
                                       {"type": "long", "name": "frame-max", "default-value": 0},
                                       {"type": "short", "name": "heartbeat", "default-value": 0}],
                         "name": "tune",
                         "synchronous" : true},
                        {"id": 31,
                         "arguments": [{"type": "short", "name": "channel-max", "default-value": 0},
                                       {"type": "long", "name": "frame-max", "default-value": 0},
                                       {"type": "short", "name": "heartbeat", "default-value": 0}],
                         "name": "tune-ok"},
                        {"id": 40,
                         "arguments": [{"type": "shortstr", "name": "virtual-host", "default-value": "/"},
                                       {"type": "shortstr", "name": "capabilities", "default-value": ""},
                                       {"type": "bit", "name": "insist", "default-value": false}],
                         "name": "open",
                         "synchronous" : true},
                        {"id": 41,
                         "arguments": [{"type": "shortstr", "name": "known-hosts", "default-value": ""}],
                         "name": "open-ok"},
                        {"id": 50,
                         "arguments": [{"type": "short", "name": "reply-code"},
                                       {"type": "shortstr", "name": "reply-text", "default-value": ""},
                                       {"type": "short", "name": "class-id"},
                                       {"type": "short", "name": "method-id"}],
                         "name": "close",
                         "synchronous" : true},
                        {"id": 51,
                         "arguments": [],
                         "name": "close-ok"},
                        {"id": 60,
                         "arguments": [{"type": "shortstr", "name": "reason", "default-value": ""}],
                         "name": "blocked"},
                        {"id": 61,
                         "arguments": [],
                         "name": "unblocked"}],
            "name": "connection",
            "properties": []
        },
        {
            "id": 20,
            "methods": [{"id": 10,
                         "arguments": [{"type": "shortstr", "name": "out-of-band", "default-value": ""}],
                         "name": "open",
                         "synchronous" : true},
                        {"id": 11,
                         "arguments": [{"type": "longstr", "name": "channel-id", "default-value": ""}],
                         "name": "open-ok"},
                        {"id": 20,
                         "arguments": [{"type": "bit", "name": "active"}],
                         "name": "flow",
                         "synchronous" : true},
                        {"id": 21,
                         "arguments": [{"type": "bit", "name": "active"}],
                         "name": "flow-ok"},
                        {"id": 40,
                         "arguments": [{"type": "short", "name": "reply-code"},
                                       {"type": "shortstr", "name": "reply-text", "default-value": ""},
                                       {"type": "short", "name": "class-id"},
                                       {"type": "short", "name": "method-id"}],
                         "name": "close",
                         "synchronous" : true},
                        {"id": 41,
                         "arguments": [],
                         "name": "close-ok"}],
            "name": "channel"
        },
        {
            "id": 30,
            "methods": [{"id": 10,
                         "arguments": [{"type": "shortstr", "name": "realm", "default-value": "/data"},
                                       {"type": "bit", "name": "exclusive", "default-value": false},
                                       {"type": "bit", "name": "passive", "default-value": true},
                                       {"type": "bit", "name": "active", "default-value": true},
                                       {"type": "bit", "name": "write", "default-value": true},
                                       {"type": "bit", "name": "read", "default-value": true}],
                         "name": "request",
                         "synchronous" : true},
                        {"id": 11,
                    "arguments": [{"type": "short", "name": "ticket", "default-value": 1}],
                         "name": "request-ok"}],
            "name": "access"
        },
        {
            "id": 40,
            "methods": [{"id": 10,
                         "arguments": [{"type": "short", "name": "ticket", "default-value": 0},
                                       {"type": "shortstr", "name": "exchange"},
                                       {"type": "shortstr", "name": "type", "default-value": "direct"},
                                       {"type": "bit", "name": "passive", "default-value": false},
                                       {"type": "bit", "name": "durable", "default-value": false},
                                       {"type": "bit", "name": "auto-delete", "default-value": false},
                                       {"type": "bit", "name": "internal", "default-value": false},
                                       {"type": "bit", "name": "nowait", "default-value": false},
                                       {"type": "table", "name": "arguments", "default-value": {}}],
                         "name": "declare",
                         "synchronous" : true},
                        {"id": 11,
                         "arguments": [],
                         "name": "declare-ok"},
                        {"id": 20,
                         "arguments": [{"type": "short", "name": "ticket", "default-value": 0},
                                       {"type": "shortstr", "name": "exchange"},
                                       {"type": "bit", "name": "if-unused", "default-value": false},
                                       {"type": "bit", "name": "nowait", "default-value": false}],
                         "name": "delete",
                         "synchronous" : true},
                        {"id": 21,
                         "arguments": [],
                         "name": "delete-ok"},
                        {"id": 30,
                         "arguments": [{"type": "short", "name": "ticket", "default-value": 0},
                                       {"type": "shortstr", "name": "destination"},
                                       {"type": "shortstr", "name": "source"},
                                       {"type": "shortstr", "name": "routing-key", "default-value": ""},
                                       {"type": "bit", "name": "nowait", "default-value": false},
                                       {"type": "table", "name": "arguments", "default-value": {}}],
                         "name": "bind",
                         "synchronous" : true},
                        {"id": 31,
                         "arguments": [],
                         "name": "bind-ok"},
                        {"id": 40,
                         "arguments": [{"type": "short", "name": "ticket", "default-value": 0},
                                       {"type": "shortstr", "name": "destination"},
                                       {"type": "shortstr", "name": "source"},
                                       {"type": "shortstr", "name": "routing-key", "default-value": ""},
                                       {"type": "bit", "name": "nowait", "default-value": false},
                                       {"type": "table", "name": "arguments", "default-value": {}}],
                         "name": "unbind",
                         "synchronous" : true},
                        {"id": 51,
                         "arguments": [],
                         "name": "unbind-ok"}],
            "name": "exchange"
        },
        {
            "id": 50,
            "methods": [{"id": 10,
                         "arguments": [{"type": "short", "name": "ticket", "default-value": 0},
                           {"type": "shortstr", "name": "queue", "default-value": ""},
                                       {"type": "bit", "name": "passive", "default-value": false},
                                       {"type": "bit", "name": "durable", "default-value": false},
                                       {"type": "bit", "name": "exclusive", "default-value": false},
                                       {"type": "bit", "name": "auto-delete", "default-value": false},
                                       {"type": "bit", "name": "nowait", "default-value": false},
                                       {"type": "table", "name": "arguments", "default-value": {}}],
                         "name": "declare",
                         "synchronous" : true},
                        {"id": 11,
                         "arguments": [{"type": "shortstr", "name": "queue"},
                                       {"type": "long", "name": "message-count"},
                                       {"type": "long", "name": "consumer-count"}],
                         "name": "declare-ok"},
                        {"id": 20,
                         "arguments": [{"type": "short", "name": "ticket", "default-value": 0},
                                       {"type": "shortstr", "name": "queue", "default-value": ""},
                                       {"type": "shortstr", "name": "exchange"},
                                       {"type": "shortstr", "name": "routing-key", "default-value": ""},
                                       {"type": "bit", "name": "nowait", "default-value": false},
                                       {"type": "table", "name": "arguments", "default-value": {}}],
                         "name": "bind",
                         "synchronous" : true},
                        {"id": 21,
                         "arguments": [],
                         "name": "bind-ok"},
                        {"id": 30,
                         "arguments": [{"type": "short", "name": "ticket", "default-value": 0},
                                       {"type": "shortstr", "name": "queue", "default-value": ""},
                                       {"type": "bit", "name": "nowait", "default-value": false}],
                         "name": "purge",
                         "synchronous" : true},
                        {"id": 31,
                         "arguments": [{"type": "long", "name": "message-count"}],
                         "name": "purge-ok"},
                        {"id": 40,
                         "arguments": [{"type": "short", "name": "ticket", "default-value": 0},
                                       {"type": "shortstr", "name": "queue", "default-value": ""},
                                       {"type": "bit", "name": "if-unused", "default-value": false},
                                       {"type": "bit", "name": "if-empty", "default-value": false},
                                       {"type": "bit", "name": "nowait", "default-value": false}],
                         "name": "delete",
                         "synchronous" : true},
                        {"id": 41,
                         "arguments": [{"type": "long", "name": "message-count"}],
                         "name": "delete-ok"},
                        {"id": 50,
                         "arguments": [{"type": "short", "name": "ticket", "default-value": 0},
                                       {"type": "shortstr", "name": "queue", "default-value": ""},
                                       {"type": "shortstr", "name": "exchange"},
                                       {"type": "shortstr", "name": "routing-key", "default-value": ""},
                                       {"type": "table", "name": "arguments", "default-value": {}}],
                         "name": "unbind",
                         "synchronous" : true},
                        {"id": 51,
                         "arguments": [],
                         "name": "unbind-ok"}
                        ],
            "name": "queue"
        },
        {
            "id": 60,
            "methods": [{"id": 10,
                         "arguments": [{"type": "long", "name": "prefetch-size", "default-value": 0},
                                       {"type": "short", "name": "prefetch-count", "default-value": 0},
                                       {"type": "bit", "name": "global", "default-value": false}],
                         "name": "qos",
                         "synchronous" : true},
                        {"id": 11,
                         "arguments": [],
                         "name": "qos-ok"},
                        {"id": 20,
                         "arguments": [{"domain": "short", "name": "ticket", "default-value": 0},
                                       {"type": "shortstr", "name": "queue", "default-value": ""},
                                       {"type": "shortstr", "name": "consumer-tag", "default-value": ""},
                                       {"type": "bit", "name": "no-local", "default-value": false},
                                       {"type": "bit", "name": "no-ack", "default-value": false},
                                       {"type": "bit", "name": "exclusive", "default-value": false},
                                       {"type": "bit", "name": "nowait", "default-value": false},
                                       {"type": "table", "name": "arguments", "default-value": {}}],
                         "name": "consume",
                         "synchronous" : true},
                        {"id": 21,
                         "arguments": [{"type": "shortstr", "name": "consumer-tag"}],
                         "name": "consume-ok"},
                        {"id": 30,
                         "arguments": [{"type": "shortstr", "name": "consumer-tag"},
                                       {"type": "bit", "name": "nowait", "default-value": false}],
                         "name": "cancel",
                         "synchronous" : true},
                        {"id": 31,
                         "arguments": [{"type": "shortstr", "name": "consumer-tag"}],
                         "name": "cancel-ok"},
                        {"content": true,
                         "id": 40,
                         "arguments": [{"type": "short", "name": "ticket", "default-value": 0},
                                       {"type": "shortstr", "name": "exchange", "default-value": ""},
                                       {"type": "shortstr", "name": "routing-key", "default-value": ""},
                                       {"type": "bit", "name": "mandatory", "default-value": false},
                                       {"type": "bit", "name": "immediate", "default-value": false}],
                         "name": "publish"},
                        {"content": true,
                         "id": 50,
                         "arguments": [{"type": "short", "name": "reply-code"},
                                       {"type": "shortstr", "name": "reply-text", "default-value": ""},
                                       {"type": "shortstr", "name": "exchange"},
                                       {"type": "shortstr", "name": "routing-key"}],
                         "name": "return"},
                        {"content": true,
                         "id": 60,
                         "arguments": [{"type": "shortstr", "name": "consumer-tag"},
                                       {"type": "longlong", "name": "delivery-tag"},
                                       {"type": "bit", "name": "redelivered", "default-value": false},
                                       {"type": "shortstr", "name": "exchange"},
                                       {"type": "shortstr", "name": "routing-key"}],
                         "name": "deliver"},
                        {"id": 70,
                         "arguments": [{"type": "short", "name": "ticket", "default-value": 0},
                                       {"type": "shortstr", "name": "queue", "default-value": ""},
                                       {"type": "bit", "name": "no-ack", "default-value": false}],
                         "name": "get",
                         "synchronous" : true},
                        {"content": true,
                         "id": 71,
                         "arguments": [{"type": "longlong", "name": "delivery-tag"},
                                       {"type": "bit", "name": "redelivered", "default-value": false},
                                       {"type": "shortstr", "name": "exchange"},
                                       {"type": "shortstr", "name": "routing-key"},
                                       {"type": "long", "name": "message-count"}],
                         "name": "get-ok"},
                        {"id": 72,
                         "arguments": [{"type": "shortstr", "name": "cluster-id", "default-value": ""}],
                         "name": "get-empty"},
                        {"id": 80,
                         "arguments": [{"type": "longlong", "name": "delivery-tag", "default-value": 0},
                                       {"type": "bit", "name": "multiple", "default-value": false}],
                         "name": "ack"},
                        {"id": 90,
                         "arguments": [{"type": "longlong", "name": "delivery-tag"},
                                       {"type": "bit", "name": "requeue", "default-value": true}],
                         "name": "reject"},
                        {"id": 100,
                         "arguments": [{"type": "bit", "name": "requeue", "default-value": false}],
                         "name": "recover-async"},
                        {"id": 110,
                         "arguments": [{"type": "bit", "name": "requeue", "default-value": false}],
                         "name": "recover",
                         "synchronous" : true},
                        {"id": 111,
                         "arguments": [],
                         "name": "recover-ok"},
                        {"id": 120,
                         "arguments": [{"type": "longlong", "name": "delivery-tag", "default-value": 0},
                                       {"type": "bit", "name": "multiple", "default-value": false},
                                       {"type": "bit", "name": "requeue", "default-value": true}],
                         "name": "nack"}],
            "name": "basic",
            "properties": [{"type": "shortstr", "name": "content-type"},
                           {"type": "shortstr", "name": "content-encoding"},
                           {"type": "table", "name": "headers"},
                           {"type": "octet", "name": "delivery-mode"},
                           {"type": "octet", "name": "priority"},
                           {"type": "shortstr", "name": "correlation-id"},
                           {"type": "shortstr", "name": "reply-to"},
                           {"type": "shortstr", "name": "expiration"},
                           {"type": "shortstr", "name": "message-id"},
                           {"type": "timestamp", "name": "timestamp"},
                           {"type": "shortstr", "name": "type"},
                           {"type": "shortstr", "name": "user-id"},
                           {"type": "shortstr", "name": "app-id"},
                           {"type": "shortstr", "name": "cluster-id"}]
        },
        {
            "id": 90,
            "methods": [{"id": 10,
                         "arguments": [],
                         "name": "select",
                         "synchronous" : true},
                        {"id": 11,
                         "arguments": [],
                         "name": "select-ok"},
                        {"id": 20,
                         "arguments": [],
                         "name": "commit",
                         "synchronous" : true},
                        {"id": 21,
                         "arguments": [],
                         "name": "commit-ok"},
                        {"id": 30,
                         "arguments": [],
                         "name": "rollback",
                         "synchronous" : true},
                        {"id": 31,
                         "arguments": [],
                         "name": "rollback-ok"}],
            "name": "tx"
        },
        {
            "id": 85,
            "methods": [{"id": 10,
                         "arguments": [
                             {"type": "bit", "name": "nowait", "default-value": false}],
                         "name": "select",
                         "synchronous": true},
                        {"id": 11,
                         "arguments": [],
                         "name": "select-ok"}],
            "name": "confirm"
        }
    ]
}
<?php
namespace PhpAmqpLib\Exchange;

final class AMQPExchangeType
{
    const DIRECT = 'direct';
    const FANOUT = 'fanout';
    const TOPIC = 'topic';
    const HEADERS = 'headers';
}
<?php
namespace PhpAmqpLib\Wire;

use PhpAmqpLib\Exception\AMQPOutOfBoundsException;

/**
 * AMQP protocol decimal value.
 *
 * Values are represented as (n,e) pairs. The actual value
 * is n * 10^(-e).
 *
 * From 0.8 spec: Decimal values are
 * not intended to support floating point values, but rather
 * business values such as currency rates and amounts. The
 * 'decimals' octet is not signed.
 */
class AMQPDecimal
{
    /** @var int */
    protected $n;

    /** @var int */
    protected $e;

    /**
     * @param int $n
     * @param int $e
     * @throws \PhpAmqpLib\Exception\AMQPOutOfBoundsException
     */
    public function __construct($n, $e)
    {
        if ($e < 0) {
            throw new AMQPOutOfBoundsException('Decimal exponent value must be unsigned!');
        }

        $this->n = $n;
        $this->e = $e;
    }

    /**
     * @return string
     */
    public function asBCvalue()
    {
        return bcdiv($this->n, bcpow(10, $this->e));
    }

    /**
     * @return int
     */
    public function getE()
    {
        return $this->e;
    }

    /**
     * @return int
     */
    public function getN()
    {
        return $this->n;
    }
}
<?php
namespace PhpAmqpLib\Wire;


class AbstractClient
{

    /**
     * @var bool
     */
    protected $is64bits;

    /**
     * @var bool
     */
    protected $isLittleEndian;

    public function __construct()
    {
        $this->is64bits = PHP_INT_SIZE == 8;

        $tmp = unpack('S', "\x01\x00"); // to maintain 5.3 compatibility
        $this->isLittleEndian = $tmp[1] == 1;
    }

    /**
     * Converts byte-string between native and network byte order, in both directions
     *
     * @param string $byteStr
     * @return string
     */
    protected function correctEndianness($byteStr)
    {
        return $this->isLittleEndian ? strrev($byteStr) : $byteStr;
    }
}
<?php
namespace PhpAmqpLib\Wire;

use PhpAmqpLib\Exception;


class AMQPTable extends AMQPAbstractCollection
{

    /**
     * @return int
     */
    final public function getType()
    {
        return self::T_TABLE;
    }

    /**
     * @param string $key
     * @param mixed $val
     * @param int|null $type
     */
    public function set($key, $val, $type = null)
    {
        //https://www.rabbitmq.com/resources/specs/amqp0-9-1.pdf, https://www.rabbitmq.com/resources/specs/amqp0-8.pdf
        //Field names MUST start with a letter, '$' or '#' and may continue with letters, '$' or '#', digits, or underlines, to a maximum length of 128 characters
        //The server SHOULD validate field names and upon receiving an invalid field name, it SHOULD signal a connection exception with reply code 503 (syntax error)

        //validating length only and delegating other stuff to server, as rabbit seems to currently support numeric keys
        if (!($len = strlen($key)) || ($len > 128)) {
            throw new Exception\AMQPInvalidArgumentException('Table key must be non-empty string up to 128 chars in length');
        }
        $this->setValue($val, $type, $key);
    }
}
<?php
namespace PhpAmqpLib\Wire;

use PhpAmqpLib\Exception\AMQPDataReadException;
use PhpAmqpLib\Exception\AMQPInvalidArgumentException;
use PhpAmqpLib\Exception\AMQPNoDataException;
use PhpAmqpLib\Exception\AMQPOutOfBoundsException;
use PhpAmqpLib\Exception\AMQPTimeoutException;
use PhpAmqpLib\Exception\AMQPIOWaitException;
use PhpAmqpLib\Helper\MiscHelper;
use PhpAmqpLib\Wire\IO\AbstractIO;

/**
 * This class can read from a string or from a stream
 *
 * TODO : split this class: AMQPStreamReader and a AMQPBufferReader
 */
class AMQPReader extends AbstractClient
{
    const BIT = 1;
    const OCTET = 1;
    const SHORTSTR = 1;
    const SHORT = 2;
    const LONG = 4;
    const SIGNED_LONG = 4;
    const READ_PHP_INT = 4; // use READ_ to avoid possible clashes with PHP
    const LONGLONG = 8;
    const TIMESTAMP = 8;

    /** @var string */
    protected $str;

    /** @var int */
    protected $str_length;

    /** @var int */
    protected $offset;

    /** @var int */
    protected $bitcount;

    /** @var bool */
    protected $is64bits;

    /** @var int|float|null */
    protected $timeout;

    /** @var int */
    protected $bits;

    /** @var \PhpAmqpLib\Wire\IO\AbstractIO */
    protected $io;

    /**
     * @param string $str
     * @param AbstractIO $io
     * @param int $timeout
     */
    public function __construct($str, AbstractIO $io = null, $timeout = 0)
    {
        parent::__construct();

        $this->str = is_string($str) ? $str : '';
        $this->str_length = mb_strlen($this->str, 'ASCII');
        $this->io = $io;
        $this->offset = 0;
        $this->bitcount = $this->bits = 0;
        $this->timeout = $timeout;
    }

    /**
     * Resets the object from the injected param
     *
     * Used to not need to create a new AMQPReader instance every time.
     * when we can just pass a string and reset the object state.
     * NOTE: since we are working with strings we don't need to pass an AbstractIO
     *       or a timeout.
     *
     * @param string $str
     */
    public function reuse($str)
    {
        $this->str = $str;
        $this->str_length = mb_strlen($this->str, 'ASCII');
        $this->offset = 0;
        $this->bitcount = $this->bits = 0;
    }

    /**
     * Closes the stream
     */
    public function close()
    {
        if ($this->io) {
            $this->io->close();
        }
    }

    /**
     * @param int $n
     * @return string
     */
    public function read($n)
    {
        $this->bitcount = $this->bits = 0;

        return $this->rawread($n);
    }

    /**
     * Waits until some data is retrieved from the socket.
     *
     * AMQPTimeoutException can be raised if the timeout is set
     *
     * @throws \PhpAmqpLib\Exception\AMQPIOWaitException on network errors
     * @throws \PhpAmqpLib\Exception\AMQPTimeoutException when timeout is set and no data received
     * @throws \PhpAmqpLib\Exception\AMQPNoDataException when no data is ready to read from IO
     */
    protected function wait()
    {
        $timeout = $this->getTimeout();
        if (null === $timeout) {
            // timeout=null just poll state and return instantly
            $sec = 0;
            $usec = 0;
        } elseif ($timeout > 0) {
            list($sec, $usec) = MiscHelper::splitSecondsMicroseconds($this->getTimeout());
        } else {
            // wait indefinitely for data if timeout=0
            $sec = null;
            $usec = 0;
        }

        $result = $this->io->select($sec, $usec);

        if ($result === false) {
            throw new AMQPIOWaitException('A network error occurred while awaiting for incoming data');
        }

        if ($result === 0) {
            if ($timeout > 0) {
                throw new AMQPTimeoutException(sprintf(
                    'The connection timed out after %s sec while awaiting incoming data',
                    $timeout
                ));
            } else {
                throw new AMQPNoDataException('No data is ready to read');
            }
        }
    }

    /**
     * @param int $n
     * @return string
     * @throws \RuntimeException
     * @throws \PhpAmqpLib\Exception\AMQPDataReadException
     * @throws \PhpAmqpLib\Exception\AMQPNoDataException
     */
    protected function rawread($n)
    {
        if ($this->io) {
            while (true) {
                $this->wait();
                try {
                    $res = $this->io->read($n);
                    break;
                } catch (AMQPTimeoutException $e) {
                    if ($this->getTimeout() > 0) {
                        throw $e;
                    }
                }
            }
            $this->offset += $n;
            return $res;
        }

        if ($this->str_length < $n) {
            throw new AMQPDataReadException(sprintf(
                'Error reading data. Requested %s bytes while string buffer has only %s',
                $n,
                $this->str_length
            ));
        }

        $res = mb_substr($this->str, 0, $n, 'ASCII');
        $this->str = mb_substr($this->str, $n, mb_strlen($this->str, 'ASCII') - $n, 'ASCII');
        $this->str_length -= $n;
        $this->offset += $n;

        return $res;
    }

    /**
     * @return bool
     */
    public function read_bit()
    {
        if (empty($this->bitcount)) {
            $this->bits = ord($this->rawread(1));
            $this->bitcount = 8;
        }

        $result = ($this->bits & 1) == 1;
        $this->bits >>= 1;
        $this->bitcount -= 1;

        return $result;
    }

    /**
     * @return mixed
     */
    public function read_octet()
    {
        $this->bitcount = $this->bits = 0;
        list(, $res) = unpack('C', $this->rawread(1));

        return $res;
    }

    /**
     * @return mixed
     */
    public function read_signed_octet()
    {
        $this->bitcount = $this->bits = 0;
        list(, $res) = unpack('c', $this->rawread(1));

        return $res;
    }

    /**
     * @return mixed
     */
    public function read_short()
    {
        $this->bitcount = $this->bits = 0;
        list(, $res) = unpack('n', $this->rawread(2));

        return $res;
    }

    /**
     * @return mixed
     */
    public function read_signed_short()
    {
        $this->bitcount = $this->bits = 0;
        list(, $res) = unpack('s', $this->correctEndianness($this->rawread(2)));

        return $res;
    }

    /**
     * Reads 32 bit integer in big-endian byte order.
     *
     * On 64 bit systems it will return always unsigned int
     * value in 0..2^32 range.
     *
     * On 32 bit systems it will return signed int value in
     * -2^31...+2^31 range.
     *
     * Use with caution!
     */
    public function read_php_int()
    {
        list(, $res) = unpack('N', $this->rawread(4));

        if ($this->is64bits) {
            return (int) sprintf('%u', $res);
        }

        return $res;
    }

    /**
     * PHP does not have unsigned 32 bit int,
     * so we return it as a string
     *
     * @return string
     */
    public function read_long()
    {
        $this->bitcount = $this->bits = 0;
        list(, $res) = unpack('N', $this->rawread(4));

        return empty($this->is64bits) && self::getLongMSB($res) ? sprintf('%u', $res) : $res;
    }

    /**
     * @return integer
     */
    private function read_signed_long()
    {
        $this->bitcount = $this->bits = 0;
        list(, $res) = unpack('l', $this->correctEndianness($this->rawread(4)));

        return $res;
    }

    /**
     * Even on 64 bit systems PHP integers are singed.
     * Since we need an unsigned value here we return it
     * as a string.
     *
     * @return string
     */
    public function read_longlong()
    {
        $this->bitcount = $this->bits = 0;

        list(, $hi, $lo) = unpack('N2', $this->rawread(8));
        $msb = self::getLongMSB($hi);

        if (empty($this->is64bits)) {
            if ($msb) {
                $hi = sprintf('%u', $hi);
            }
            if (self::getLongMSB($lo)) {
                $lo = sprintf('%u', $lo);
            }
        }

        return bcadd($this->is64bits && !$msb ? $hi << 32 : bcmul($hi, '4294967296', 0), $lo, 0);
    }

    /**
     * @return string
     */
    public function read_signed_longlong()
    {
        $this->bitcount = $this->bits = 0;

        list(, $hi, $lo) = unpack('N2', $this->rawread(8));

        if ($this->is64bits) {
            return bcadd($hi << 32, $lo, 0);
        } else {
            return bcadd(bcmul($hi, '4294967296', 0), self::getLongMSB($lo) ? sprintf('%u', $lo) : $lo, 0);
        }
    }

    /**
     * @param int $longInt
     * @return bool
     */
    private static function getLongMSB($longInt)
    {
        return (bool) ($longInt & 0x80000000);
    }

    /**
     * Read a utf-8 encoded string that's stored in up to
     * 255 bytes.  Return it decoded as a PHP unicode object.
     */
    public function read_shortstr()
    {
        $this->bitcount = $this->bits = 0;
        list(, $slen) = unpack('C', $this->rawread(1));

        return $this->rawread($slen);
    }

    /**
     * Read a string that's up to 2**32 bytes, the encoding
     * isn't specified in the AMQP spec, so just return it as
     * a plain PHP string.
     */
    public function read_longstr()
    {
        $this->bitcount = $this->bits = 0;
        $slen = $this->read_php_int();

        if ($slen < 0) {
            throw new AMQPOutOfBoundsException('Strings longer than supported on this platform');
        }

        return $this->rawread($slen);
    }

    /**
     * Read and AMQP timestamp, which is a 64-bit integer representing
     * seconds since the Unix epoch in 1-second resolution.
     */
    public function read_timestamp()
    {
        return $this->read_longlong();
    }

    /**
     * Read an AMQP table, and return as a PHP array. keys are strings,
     * values are (type,value) tuples.
     *
     * @param bool $returnObject Whether to return AMQPArray instance instead of plain array
     * @return array|AMQPTable
     */
    public function read_table($returnObject = false)
    {
        $this->bitcount = $this->bits = 0;
        $tlen = $this->read_php_int();

        if ($tlen < 0) {
            throw new AMQPOutOfBoundsException('Table is longer than supported');
        }

        $table_data = new AMQPReader($this->rawread($tlen), null);
        $result = $returnObject ? new AMQPTable() : array();

        while ($table_data->tell() < $tlen) {
            $name = $table_data->read_shortstr();
            $ftype = AMQPAbstractCollection::getDataTypeForSymbol($ftypeSym = $table_data->rawread(1));
            $val = $table_data->read_value($ftype, $returnObject);
            $returnObject ? $result->set($name, $val, $ftype) : $result[$name] = array($ftypeSym, $val);
        }

        return $result;
    }

    /**
     * @return array|AMQPTable
     */
    public function read_table_object()
    {
        return $this->read_table(true);
    }

    /**
     * Reads the array in the next value.
     *
     * @param bool $returnObject Whether to return AMQPArray instance instead of plain array
     * @return array|AMQPArray
     */
    public function read_array($returnObject = false)
    {
        $this->bitcount = $this->bits = 0;

        // Determine array length and its end position
        $arrayLength = $this->read_php_int();
        $endOffset = $this->offset + $arrayLength;

        $result = $returnObject ? new AMQPArray() : array();

        // Read values until we reach the end of the array
        while ($this->offset < $endOffset) {
            $fieldType = AMQPAbstractCollection::getDataTypeForSymbol($this->rawread(1));
            $fieldValue = $this->read_value($fieldType, $returnObject);
            $returnObject ? $result->push($fieldValue, $fieldType) : $result[] = $fieldValue;
        }

        return $result;
    }

    /**
     * @return array|AMQPArray
     */
    public function read_array_object()
    {
        return $this->read_array(true);
    }

    /**
     * Reads the next value as the provided field type.
     *
     * @param int $fieldType One of AMQPAbstractCollection::T_* constants
     * @param bool $collectionsAsObjects Description
     * @return mixed
     * @throws \PhpAmqpLib\Exception\AMQPDataReadException
     */
    public function read_value($fieldType, $collectionsAsObjects = false)
    {
        $this->bitcount = $this->bits = 0;

        switch ($fieldType) {
            case AMQPAbstractCollection::T_INT_SHORTSHORT:
                //according to AMQP091 spec, 'b' is not bit, it is short-short-int, also valid for rabbit/qpid
                //$val=$this->read_bit();
                $val = $this->read_signed_octet();
                break;
            case AMQPAbstractCollection::T_INT_SHORTSHORT_U:
                $val = $this->read_octet();
                break;
            case AMQPAbstractCollection::T_INT_SHORT:
                $val = $this->read_signed_short();
                break;
            case AMQPAbstractCollection::T_INT_SHORT_U:
                $val = $this->read_short();
                break;
            case AMQPAbstractCollection::T_INT_LONG:
                $val = $this->read_signed_long();
                break;
            case AMQPAbstractCollection::T_INT_LONG_U:
                $val = $this->read_long();
                break;
            case AMQPAbstractCollection::T_INT_LONGLONG:
                $val = $this->read_signed_longlong();
                break;
            case AMQPAbstractCollection::T_INT_LONGLONG_U:
                $val = $this->read_longlong();
                break;
            case AMQPAbstractCollection::T_DECIMAL:
                $e = $this->read_octet();
                $n = $this->read_signed_long();
                $val = new AMQPDecimal($n, $e);
                break;
            case AMQPAbstractCollection::T_TIMESTAMP:
                $val = $this->read_timestamp();
                break;
            case AMQPAbstractCollection::T_BOOL:
                $val = $this->read_octet();
                break;
            case AMQPAbstractCollection::T_STRING_SHORT:
                $val = $this->read_shortstr();
                break;
            case AMQPAbstractCollection::T_STRING_LONG:
                $val = $this->read_longstr();
                break;
            case AMQPAbstractCollection::T_ARRAY:
                $val = $this->read_array($collectionsAsObjects);
                break;
            case AMQPAbstractCollection::T_TABLE:
                $val = $this->read_table($collectionsAsObjects);
                break;
            case AMQPAbstractCollection::T_VOID:
                $val = null;
                break;
            case AMQPAbstractCollection::T_BYTES:
                $val = $this->read_longstr();
                break;
            default:
                throw new AMQPInvalidArgumentException(sprintf(
                    'Unsupported type "%s"',
                    $fieldType
                ));
        }

        return isset($val) ? $val : null;
    }

    /**
     * @return int
     */
    protected function tell()
    {
        return $this->offset;
    }

    /**
     * Sets the timeout (second)
     *
     * @param int|float|null $timeout
     */
    public function setTimeout($timeout)
    {
        $this->timeout = $timeout;
    }

    /**
     * @return int|float
     */
    public function getTimeout()
    {
        return $this->timeout;
    }
}
<?php

/* This file was autogenerated by spec/parser.php - Do not modify */

namespace PhpAmqpLib\Wire;

class Constants080
{
    /**
     * @var string
     */
    public static $AMQP_PROTOCOL_HEADER = "AMQP\x01\x01\x08\x00";

    /**
     * @var array
     */
    public static $FRAME_TYPES = array(
        1 => 'FRAME-METHOD',
        2 => 'FRAME-HEADER',
        3 => 'FRAME-BODY',
        4 => 'FRAME-OOB-METHOD',
        5 => 'FRAME-OOB-HEADER',
        6 => 'FRAME-OOB-BODY',
        7 => 'FRAME-TRACE',
        8 => 'FRAME-HEARTBEAT',
        4096 => 'FRAME-MIN-SIZE',
        206 => 'FRAME-END',
        501 => 'FRAME-ERROR',
    );

    /**
     * @var array
     */
    public static $CONTENT_METHODS = array(
        0 => '60,40',
        1 => '60,50',
        2 => '60,60',
        3 => '60,71',
        4 => '70,50',
        5 => '70,70',
        6 => '80,40',
        7 => '80,50',
        8 => '80,60',
        9 => '110,10',
        10 => '120,40',
        11 => '120,41',
    );

    /**
     * @var array
     */
    public static $CLOSE_METHODS = array(
        0 => '10,60',
        1 => '20,40',
    );

    /**
     * @var array
     */
    public static $GLOBAL_METHOD_NAMES = array(
        '10,10' => 'Connection.start',
        '10,11' => 'Connection.start_ok',
        '10,20' => 'Connection.secure',
        '10,21' => 'Connection.secure_ok',
        '10,30' => 'Connection.tune',
        '10,31' => 'Connection.tune_ok',
        '10,40' => 'Connection.open',
        '10,41' => 'Connection.open_ok',
        '10,50' => 'Connection.redirect',
        '10,60' => 'Connection.close',
        '10,61' => 'Connection.close_ok',
        '20,10' => 'Channel.open',
        '20,11' => 'Channel.open_ok',
        '20,20' => 'Channel.flow',
        '20,21' => 'Channel.flow_ok',
        '20,30' => 'Channel.alert',
        '20,40' => 'Channel.close',
        '20,41' => 'Channel.close_ok',
        '30,10' => 'Access.request',
        '30,11' => 'Access.request_ok',
        '40,10' => 'Exchange.declare',
        '40,11' => 'Exchange.declare_ok',
        '40,20' => 'Exchange.delete',
        '40,21' => 'Exchange.delete_ok',
        '50,10' => 'Queue.declare',
        '50,11' => 'Queue.declare_ok',
        '50,20' => 'Queue.bind',
        '50,21' => 'Queue.bind_ok',
        '50,30' => 'Queue.purge',
        '50,31' => 'Queue.purge_ok',
        '50,40' => 'Queue.delete',
        '50,41' => 'Queue.delete_ok',
        '50,50' => 'Queue.unbind',
        '50,51' => 'Queue.unbind_ok',
        '60,10' => 'Basic.qos',
        '60,11' => 'Basic.qos_ok',
        '60,20' => 'Basic.consume',
        '60,21' => 'Basic.consume_ok',
        '60,30' => 'Basic.cancel',
        '60,31' => 'Basic.cancel_ok',
        '60,40' => 'Basic.publish',
        '60,50' => 'Basic.return',
        '60,60' => 'Basic.deliver',
        '60,70' => 'Basic.get',
        '60,71' => 'Basic.get_ok',
        '60,72' => 'Basic.get_empty',
        '60,80' => 'Basic.ack',
        '60,90' => 'Basic.reject',
        '60,100' => 'Basic.recover_async',
        '60,110' => 'Basic.recover',
        '60,111' => 'Basic.recover_ok',
        '70,10' => 'File.qos',
        '70,11' => 'File.qos_ok',
        '70,20' => 'File.consume',
        '70,21' => 'File.consume_ok',
        '70,30' => 'File.cancel',
        '70,31' => 'File.cancel_ok',
        '70,40' => 'File.open',
        '70,41' => 'File.open_ok',
        '70,50' => 'File.stage',
        '70,60' => 'File.publish',
        '70,70' => 'File.return',
        '70,80' => 'File.deliver',
        '70,90' => 'File.ack',
        '70,100' => 'File.reject',
        '80,10' => 'Stream.qos',
        '80,11' => 'Stream.qos_ok',
        '80,20' => 'Stream.consume',
        '80,21' => 'Stream.consume_ok',
        '80,30' => 'Stream.cancel',
        '80,31' => 'Stream.cancel_ok',
        '80,40' => 'Stream.publish',
        '80,50' => 'Stream.return',
        '80,60' => 'Stream.deliver',
        '90,10' => 'Tx.select',
        '90,11' => 'Tx.select_ok',
        '90,20' => 'Tx.commit',
        '90,21' => 'Tx.commit_ok',
        '90,30' => 'Tx.rollback',
        '90,31' => 'Tx.rollback_ok',
        '100,10' => 'Dtx.select',
        '100,11' => 'Dtx.select_ok',
        '100,20' => 'Dtx.start',
        '100,21' => 'Dtx.start_ok',
        '110,10' => 'Tunnel.request',
        '120,10' => 'Test.integer',
        '120,11' => 'Test.integer_ok',
        '120,20' => 'Test.string',
        '120,21' => 'Test.string_ok',
        '120,30' => 'Test.table',
        '120,31' => 'Test.table_ok',
        '120,40' => 'Test.content',
        '120,41' => 'Test.content_ok',
    );
}
<?php
namespace PhpAmqpLib\Wire\IO;

use PhpAmqpLib\Exception\AMQPConnectionClosedException;
use PhpAmqpLib\Exception\AMQPDataReadException;
use PhpAmqpLib\Exception\AMQPIOException;
use PhpAmqpLib\Exception\AMQPRuntimeException;
use PhpAmqpLib\Exception\AMQPTimeoutException;
use PhpAmqpLib\Helper\MiscHelper;

class StreamIO extends AbstractIO
{
    /** @var string */
    protected $protocol;

    /** @var resource */
    protected $context;

    /** @var resource */
    private $sock;

    /**
     * @param string $host
     * @param int $port
     * @param float $connection_timeout
     * @param float $read_write_timeout
     * @param null $context
     * @param bool $keepalive
     * @param int $heartbeat
     */
    public function __construct(
        $host,
        $port,
        $connection_timeout,
        $read_write_timeout = 130.0,
        $context = null,
        $keepalive = false,
        $heartbeat = 60,
        $ssl_protocol = null
    ) {
        // TODO FUTURE change comparison to <=
        // php-amqplib/php-amqplib#648, php-amqplib/php-amqplib#666
        if ($heartbeat !== 0 && ($read_write_timeout < ($heartbeat * 2))) {
            throw new \InvalidArgumentException('read_write_timeout must be at least 2x the heartbeat');
        }

        $this->protocol = 'tcp';
        $this->host = $host;
        $this->port = $port;
        $this->connection_timeout = $connection_timeout;
        $this->read_timeout = $read_write_timeout;
        $this->write_timeout = $read_write_timeout;
        $this->context = $context;
        $this->keepalive = $keepalive;
        $this->heartbeat = $heartbeat;
        $this->initial_heartbeat = $heartbeat;
        $this->canDispatchPcntlSignal = $this->isPcntlSignalEnabled();

        if (!is_resource($this->context) || get_resource_type($this->context) !== 'stream-context') {
            $this->context = stream_context_create();
        }

        // tcp_nodelay was added in 7.1.0
        if (PHP_VERSION_ID >= 70100) {
            stream_context_set_option($this->context, 'socket', 'tcp_nodelay', true);
        }

        $options = stream_context_get_options($this->context);
        if (!empty($options['ssl'])) {
            if (isset($ssl_protocol)) {
                $this->protocol = $ssl_protocol;
            } else {
                $this->protocol = 'ssl';
            }
        }

        if (!defined('SOCKET_EAGAIN')) {
            define('SOCKET_EAGAIN', SOCKET_EWOULDBLOCK);
        }
    }

    /**
     * @inheritdoc
     */
    public function connect()
    {
        $errstr = $errno = null;

        $remote = sprintf(
            '%s://%s:%s',
            $this->protocol,
            $this->host,
            $this->port
        );

        $this->set_error_handler();

        try {
            $this->sock = stream_socket_client(
                $remote,
                $errno,
                $errstr,
                $this->connection_timeout,
                STREAM_CLIENT_CONNECT,
                $this->context
            );
            $this->cleanup_error_handler();
        } catch (\ErrorException $e) {
            throw new AMQPIOException($e->getMessage());
        }

        if (false === $this->sock) {
            throw new AMQPIOException(
                sprintf(
                    'Error Connecting to server(%s): %s ',
                    $errno,
                    $errstr
                ),
                $errno
            );
        }

        if (false === stream_socket_get_name($this->sock, true)) {
            throw new AMQPIOException(
                sprintf(
                    'Connection refused: %s ',
                    $remote
                )
            );
        }

        list($sec, $uSec) = MiscHelper::splitSecondsMicroseconds(max($this->read_timeout, $this->write_timeout));
        if (!stream_set_timeout($this->sock, $sec, $uSec)) {
            throw new AMQPIOException('Timeout could not be set');
        }

        // php cannot capture signals while streams are blocking
        if ($this->canDispatchPcntlSignal) {
            stream_set_blocking($this->sock, 0);
            stream_set_write_buffer($this->sock, 0);
            if (function_exists('stream_set_read_buffer')) {
                stream_set_read_buffer($this->sock, 0);
            }
        } else {
            stream_set_blocking($this->sock, true);
        }

        if ($this->keepalive) {
            $this->enable_keepalive();
        }
        $this->heartbeat = $this->initial_heartbeat;
    }

    /**
     * @inheritdoc
     */
    public function read($len)
    {
        $this->check_heartbeat();

        list($timeout_sec, $timeout_uSec) = MiscHelper::splitSecondsMicroseconds($this->read_timeout);

        $read_start = microtime(true);
        $read = 0;
        $data = '';

        while ($read < $len) {
            if (!is_resource($this->sock) || feof($this->sock)) {
                $this->close();
                throw new AMQPConnectionClosedException('Broken pipe or closed connection');
            }

            $this->set_error_handler();
            try {
                $buffer = fread($this->sock, ($len - $read));
                $this->cleanup_error_handler();
            } catch (\ErrorException $e) {
                throw new AMQPDataReadException($e->getMessage(), $e->getCode(), $e);
            }

            if ($buffer === false) {
                throw new AMQPDataReadException('Error receiving data');
            }

            if ($buffer === '') {
                $read_now = microtime(true);
                $t_read = $read_now - $read_start;
                if ($t_read > $this->read_timeout) {
                    throw new AMQPTimeoutException('Too many read attempts detected in StreamIO');
                }
                $this->select($timeout_sec, $timeout_uSec);

                continue;
            }

            $this->last_read = microtime(true);
            $read_start = $this->last_read;
            $read += mb_strlen($buffer, 'ASCII');
            $data .= $buffer;
        }

        if (mb_strlen($data, 'ASCII') !== $len) {
            throw new AMQPDataReadException(
                sprintf(
                    'Error reading data. Received %s instead of expected %s bytes',
                    mb_strlen($data, 'ASCII'),
                    $len
                )
            );
        }

        $this->last_read = microtime(true);

        return $data;
    }

    /**
     * @inheritdoc
     */
    public function write($data)
    {
        $written = 0;
        $len = mb_strlen($data, 'ASCII');
        $write_start = microtime(true);

        while ($written < $len) {
            if (!is_resource($this->sock)) {
                throw new AMQPConnectionClosedException('Broken pipe or closed connection');
            }

            $result = false;
            $this->set_error_handler();
            // OpenSSL's C library function SSL_write() can balk on buffers > 8192
            // bytes in length, so we're limiting the write size here. On both TLS
            // and plaintext connections, the write loop will continue until the
            // buffer has been fully written.
            // This behavior has been observed in OpenSSL dating back to at least
            // September 2002:
            // http://comments.gmane.org/gmane.comp.encryption.openssl.user/4361
            try {
                $buffer = mb_substr($data, $written, self::BUFFER_SIZE, 'ASCII');
                $result = fwrite($this->sock, $buffer);
                $this->cleanup_error_handler();
            } catch (\ErrorException $e) {
                $code = $this->last_error['errno'];
                switch ($code) {
                    case SOCKET_EPIPE:
                    case SOCKET_ENETDOWN:
                    case SOCKET_ENETUNREACH:
                    case SOCKET_ENETRESET:
                    case SOCKET_ECONNABORTED:
                    case SOCKET_ECONNRESET:
                    case SOCKET_ECONNREFUSED:
                    case SOCKET_ETIMEDOUT:
                        $this->close();
                        throw new AMQPConnectionClosedException(socket_strerror($code), $code, $e);
                    default:
                        throw new AMQPRuntimeException($e->getMessage(), $code, $e);
                }
            }

            if ($result === false) {
                throw new AMQPRuntimeException('Error sending data');
            }

            if ($this->timed_out()) {
                throw AMQPTimeoutException::writeTimeout($this->write_timeout);
            }

            $now = microtime(true);
            if ($result > 0) {
                $this->last_write = $write_start = $now;
                $written += $result;
            } else {
                if (feof($this->sock)) {
                    $this->close();
                    throw new AMQPConnectionClosedException('Broken pipe or closed connection');
                }
                if (($now - $write_start) > $this->write_timeout) {
                    throw AMQPTimeoutException::writeTimeout($this->write_timeout);
                }
                // check stream and prevent from high CPU usage
                $this->select_write();
            }
        }
    }

    /**
     * @inheritdoc
     */
    public function error_handler($errno, $errstr, $errfile, $errline, $errcontext = null)
    {
        $code = $this->extract_error_code($errstr);
        switch ($code) {
            // fwrite notice that the stream isn't ready - EAGAIN or EWOULDBLOCK
            case SOCKET_EAGAIN:
            case SOCKET_EWOULDBLOCK:
            // stream_select warning that it has been interrupted by a signal - EINTR
            case SOCKET_EINTR:
                return;
        }

        parent::error_handler($code > 0 ? $code : $errno, $errstr, $errfile, $errline, $errcontext);
    }

    public function close()
    {
        $this->disableHeartbeat();
        if (is_resource($this->sock)) {
            fclose($this->sock);
        }
        $this->sock = null;
        $this->last_read = null;
        $this->last_write = null;
    }

    /**
     * @inheritdoc
     */
    public function getSocket()
    {
        return $this->sock;
    }

    /**
     * @inheritdoc
     */
    protected function do_select($sec, $usec)
    {
        $read = array($this->sock);
        $write = null;
        $except = null;

        return stream_select($read, $write, $except, $sec, $usec);
    }

    /**
     * @return int|bool
     */
    protected function select_write()
    {
        $read = $except = null;
        $write = array($this->sock);

        return stream_select($read, $write, $except, 0, 100000);
    }

    /**
     * @return mixed
     */
    protected function timed_out()
    {
        // get status of socket to determine whether or not it has timed out
        $info = stream_get_meta_data($this->sock);

        return $info['timed_out'];
    }

    /**
     * @throws \PhpAmqpLib\Exception\AMQPIOException
     */
    protected function enable_keepalive()
    {
        if ($this->protocol === 'ssl') {
            throw new AMQPIOException('Can not enable keepalive: ssl connection does not support keepalive (#70939)');
        }

        if ($this->protocol === 'tls') {
            throw new AMQPIOException('Can not enable keepalive: tls connection does not support keepalive (#70939)');
        }

        if (!function_exists('socket_import_stream')) {
            throw new AMQPIOException('Can not enable keepalive: function socket_import_stream does not exist');
        }

        if (!defined('SOL_SOCKET') || !defined('SO_KEEPALIVE')) {
            throw new AMQPIOException('Can not enable keepalive: SOL_SOCKET or SO_KEEPALIVE is not defined');
        }

        $socket = socket_import_stream($this->sock);
        socket_set_option($socket, SOL_SOCKET, SO_KEEPALIVE, 1);
    }

    /**
     * @param string $message
     * @return int
     */
    protected function extract_error_code($message)
    {
        if (0 === strpos($message, 'stream_select():')) {
            $pattern = '/\s+\[(\d+)\]:\s+/';
        } else {
            $pattern = '/\s+errno=(\d+)\s+/';
        }
        $matches = array();
        $result = preg_match($pattern, $message, $matches);
        if ($result > 0) {
            return (int)$matches[1];
        }

        return 0;
    }
}
<?php

namespace PhpAmqpLib\Wire\IO;

use PhpAmqpLib\Exception\AMQPHeartbeatMissedException;
use PhpAmqpLib\Exception\AMQPIOWaitException;
use PhpAmqpLib\Wire\AMQPWriter;

abstract class AbstractIO
{
    const BUFFER_SIZE = 8192;

    /** @var string */
    protected $host;

    /** @var int */
    protected $port;

    /** @var int|float */
    protected $connection_timeout;

    /** @var int|float */
    protected $read_timeout;

    /** @var int|float */
    protected $write_timeout;

    /** @var int */
    protected $heartbeat;

    /** @var int */
    protected $initial_heartbeat;

    /** @var bool */
    protected $keepalive;

    /** @var float */
    protected $last_read;

    /** @var float */
    protected $last_write;

    /** @var array|null */
    protected $last_error;

    /** @var bool */
    protected $canDispatchPcntlSignal = false;

    /**
     * @param int $len
     * @return string
     * @throws \PhpAmqpLib\Exception\AMQPIOException
     * @throws \PhpAmqpLib\Exception\AMQPRuntimeException
     * @throws \PhpAmqpLib\Exception\AMQPSocketException
     * @throws \PhpAmqpLib\Exception\AMQPTimeoutException
     * @throws \PhpAmqpLib\Exception\AMQPConnectionClosedException
     */
    abstract public function read($len);

    /**
     * @param string $data
     * @throws \PhpAmqpLib\Exception\AMQPIOException
     * @throws \PhpAmqpLib\Exception\AMQPSocketException
     * @throws \PhpAmqpLib\Exception\AMQPConnectionClosedException
     * @throws \PhpAmqpLib\Exception\AMQPTimeoutException
     */
    abstract public function write($data);

    /**
     * @return void
     */
    abstract public function close();

    /**
     * @param int|null $sec
     * @param int|null $usec
     * @return int
     * @throws \PhpAmqpLib\Exception\AMQPIOException
     * @throws \PhpAmqpLib\Exception\AMQPRuntimeException
     */
    public function select($sec, $usec)
    {
        $this->check_heartbeat();
        $this->set_error_handler();
        try {
            $result = $this->do_select($sec, $usec);
            $this->cleanup_error_handler();
        } catch (\ErrorException $e) {
            throw new AMQPIOWaitException($e->getMessage(), $e->getCode(), $e);
        }

        if ($this->canDispatchPcntlSignal) {
            pcntl_signal_dispatch();
        }

        // no exception and false result - either timeout or signal was sent
        if ($result === false) {
            $result = 0;
        }

        return $result;
    }

    /**
     * @param int|null $sec
     * @param int|null $usec
     * @return int|bool
     */
    abstract protected function do_select($sec, $usec);

    /**
     * Set ups the connection.
     * @return void
     * @throws \PhpAmqpLib\Exception\AMQPIOException
     * @throws \PhpAmqpLib\Exception\AMQPRuntimeException
     */
    abstract public function connect();

    /**
     * @return resource
     */
    abstract public function getSocket();

    /**
     * Heartbeat logic: check connection health here
     * @return void
     * @throws \PhpAmqpLib\Exception\AMQPRuntimeException
     */
    public function check_heartbeat()
    {
        // ignore unless heartbeat interval is set
        if ($this->heartbeat !== 0 && $this->last_read && $this->last_write) {
            $t = microtime(true);
            $t_read = round($t - $this->last_read);
            $t_write = round($t - $this->last_write);

            // server has gone away
            if (($this->heartbeat * 2) < $t_read) {
                $this->close();
                throw new AMQPHeartbeatMissedException('Missed server heartbeat');
            }

            // time for client to send a heartbeat
            if (($this->heartbeat / 2) < $t_write) {
                $this->write_heartbeat();
            }
        }
    }

    /**
     * @return $this
     */
    public function disableHeartbeat()
    {
        $this->heartbeat = 0;

        return $this;
    }

    /**
     * @return $this
     */
    public function reenableHeartbeat()
    {
        $this->heartbeat = $this->initial_heartbeat;

        return $this;
    }

    /**
     * Sends a heartbeat message
     */
    protected function write_heartbeat()
    {
        $pkt = new AMQPWriter();
        $pkt->write_octet(8);
        $pkt->write_short(0);
        $pkt->write_long(0);
        $pkt->write_octet(0xCE);
        $this->write($pkt->getvalue());
    }

    /**
     * Begin tracking errors and set the error handler
     */
    protected function set_error_handler()
    {
        $this->last_error = null;
        set_error_handler(array($this, 'error_handler'));
    }

    /**
     * throws an ErrorException if an error was handled
     * @throws \ErrorException
     */
    protected function cleanup_error_handler()
    {
        restore_error_handler();

        if ($this->last_error !== null) {
            throw new \ErrorException(
                $this->last_error['errstr'],
                0,
                $this->last_error['errno'],
                $this->last_error['errfile'],
                $this->last_error['errline']
            );
        }
    }

    /**
     * Internal error handler to deal with stream and socket errors.
     *
     * @param  int $errno
     * @param  string $errstr
     * @param  string $errfile
     * @param  int $errline
     * @param  array $errcontext
     * @return void
     */
    public function error_handler($errno, $errstr, $errfile, $errline, $errcontext = null)
    {
        // throwing an exception in an error handler will halt execution
        //   set the last error and continue
        $this->last_error = compact('errno', 'errstr', 'errfile', 'errline', 'errcontext');
    }

    /**
     * @return bool
     */
    protected function isPcntlSignalEnabled()
    {
        return extension_loaded('pcntl')
            && function_exists('pcntl_signal_dispatch')
            && (defined('AMQP_WITHOUT_SIGNALS') ? !AMQP_WITHOUT_SIGNALS : true);
    }
}
<?php
namespace PhpAmqpLib\Wire\IO;

use PhpAmqpLib\Exception\AMQPConnectionClosedException;
use PhpAmqpLib\Exception\AMQPIOException;
use PhpAmqpLib\Exception\AMQPSocketException;
use PhpAmqpLib\Exception\AMQPTimeoutException;
use PhpAmqpLib\Helper\MiscHelper;

class SocketIO extends AbstractIO
{
    /** @var resource */
    private $sock;

    /**
     * @param string $host
     * @param int $port
     * @param int|float $read_timeout
     * @param bool $keepalive
     * @param int|float|null $write_timeout if null defaults to read timeout
     * @param int $heartbeat how often to send heartbeat. 0 means off
     */
    public function __construct($host, $port, $read_timeout = 130, $keepalive = false, $write_timeout = null, $heartbeat = 60)
    {
        $this->host = $host;
        $this->port = $port;
        $this->read_timeout = $read_timeout;
        $this->write_timeout = $write_timeout ?: $read_timeout;
        $this->heartbeat = $heartbeat;
        $this->initial_heartbeat = $heartbeat;
        $this->keepalive = $keepalive;
        $this->canDispatchPcntlSignal = $this->isPcntlSignalEnabled();

        /*
            TODO FUTURE enable this check
            php-amqplib/php-amqplib#648, php-amqplib/php-amqplib#666
        if ($this->heartbeat !== 0 && ($this->read_timeout <= ($this->heartbeat * 2))) {
            throw new \InvalidArgumentException('read_timeout must be greater than 2x the heartbeat');
        }
        if ($this->heartbeat !== 0 && ($this->write_timeout <= ($this->heartbeat * 2))) {
            throw new \InvalidArgumentException('send_timeout must be greater than 2x the heartbeat');
        }
         */
    }

    /**
     * @inheritdoc
     */
    public function connect()
    {
        $this->sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

        list($sec, $uSec) = MiscHelper::splitSecondsMicroseconds($this->write_timeout);
        socket_set_option($this->sock, SOL_SOCKET, SO_SNDTIMEO, array('sec' => $sec, 'usec' => $uSec));
        list($sec, $uSec) = MiscHelper::splitSecondsMicroseconds($this->read_timeout);
        socket_set_option($this->sock, SOL_SOCKET, SO_RCVTIMEO, array('sec' => $sec, 'usec' => $uSec));

        $this->set_error_handler();
        try {
            $connected = socket_connect($this->sock, $this->host, $this->port);
            $this->cleanup_error_handler();
        } catch (\ErrorException $e) {
            $connected = false;
        }
        if (!$connected) {
            $errno = socket_last_error($this->sock);
            $errstr = socket_strerror($errno);
            throw new AMQPIOException(sprintf(
                'Error Connecting to server (%s): %s',
                $errno,
                $errstr
            ), $errno);
        }

        socket_set_block($this->sock);
        socket_set_option($this->sock, SOL_TCP, TCP_NODELAY, 1);

        if ($this->keepalive) {
            $this->enable_keepalive();
        }

        $this->heartbeat = $this->initial_heartbeat;
    }

    /**
     * @inheritdoc
     */
    public function getSocket()
    {
        return $this->sock;
    }

    /**
     * @inheritdoc
     */
    public function read($len)
    {
        if (is_null($this->sock)) {
            throw new AMQPSocketException(sprintf(
                'Socket was null! Last SocketError was: %s',
                socket_strerror(socket_last_error())
            ));
        }

        $this->check_heartbeat();

        list($timeout_sec, $timeout_uSec) = MiscHelper::splitSecondsMicroseconds($this->read_timeout);
        $read_start = microtime(true);
        $read = 0;
        $data = '';
        while ($read < $len) {
            $buffer = null;
            $result = socket_recv($this->sock, $buffer, $len - $read, 0);
            if ($result === 0) {
                // From linux recv() manual:
                // When a stream socket peer has performed an orderly shutdown,
                // the return value will be 0 (the traditional "end-of-file" return).
                // http://php.net/manual/en/function.socket-recv.php#47182
                $this->close();
                throw new AMQPConnectionClosedException('Broken pipe or closed connection');
            }

            if (empty($buffer)) {
                $read_now = microtime(true);
                $t_read = $read_now - $read_start;
                if ($t_read > $this->read_timeout) {
                    throw new AMQPTimeoutException('Too many read attempts detected in SocketIO');
                }
                $this->select($timeout_sec, $timeout_uSec);
                continue;
            }

            $read += mb_strlen($buffer, 'ASCII');
            $data .= $buffer;
        }

        if (mb_strlen($data, 'ASCII') != $len) {
            throw new AMQPIOException(sprintf(
                'Error reading data. Received %s instead of expected %s bytes',
                mb_strlen($data, 'ASCII'),
                $len
            ));
        }

        $this->last_read = microtime(true);

        return $data;
    }

    /**
     * @inheritdoc
     */
    public function write($data)
    {
        $written = 0;
        $len = mb_strlen($data, 'ASCII');
        $write_start = microtime(true);

        while ($written < $len) {
            // Null sockets are invalid, throw exception
            if (is_null($this->sock)) {
                throw new AMQPSocketException(sprintf(
                    'Socket was null! Last SocketError was: %s',
                    socket_strerror(socket_last_error())
                ));
            }

            $this->set_error_handler();
            try {
                $buffer = mb_substr($data, $written, self::BUFFER_SIZE, 'ASCII');
                $result = socket_write($this->sock, $buffer, self::BUFFER_SIZE);
                $this->cleanup_error_handler();
            } catch (\ErrorException $e) {
                $code = socket_last_error($this->sock);
                switch ($code) {
                    case SOCKET_EPIPE:
                    case SOCKET_ENETDOWN:
                    case SOCKET_ENETUNREACH:
                    case SOCKET_ENETRESET:
                    case SOCKET_ECONNABORTED:
                    case SOCKET_ECONNRESET:
                    case SOCKET_ECONNREFUSED:
                    case SOCKET_ETIMEDOUT:
                        $this->close();
                        throw new AMQPConnectionClosedException(socket_strerror($code), $code, $e);
                    default:
                        throw new AMQPIOException(sprintf(
                            'Error sending data. Last SocketError: %s',
                            socket_strerror($code)
                        ), $code, $e);
                }
            }

            if ($result === false) {
                throw new AMQPIOException(sprintf(
                    'Error sending data. Last SocketError: %s',
                    socket_strerror(socket_last_error($this->sock))
                ));
            }

            $now = microtime(true);
            if ($result > 0) {
                $this->last_write = $write_start = $now;
                $written += $result;
            } else {
                if (($now - $write_start) > $this->write_timeout) {
                    throw AMQPTimeoutException::writeTimeout($this->write_timeout);
                }
                $this->select_write();
            }
        }
    }

    /**
     * @inheritdoc
     */
    public function close()
    {
        $this->disableHeartbeat();
        if (is_resource($this->sock)) {
            socket_close($this->sock);
        }
        $this->sock = null;
        $this->last_read = null;
        $this->last_write = null;
    }

    /**
     * @inheritdoc
     */
    protected function do_select($sec, $usec)
    {
        $read = array($this->sock);
        $write = null;
        $except = null;

        return socket_select($read, $write, $except, $sec, $usec);
    }

    /**
     * @return int|bool
     */
    protected function select_write()
    {
        $read = $except = null;
        $write = array($this->sock);

        return socket_select($read, $write, $except, 0, 100000);
    }

    /**
     * @throws \PhpAmqpLib\Exception\AMQPIOException
     */
    protected function enable_keepalive()
    {
        if (!defined('SOL_SOCKET') || !defined('SO_KEEPALIVE')) {
            throw new AMQPIOException('Can not enable keepalive: SOL_SOCKET or SO_KEEPALIVE is not defined');
        }

        socket_set_option($this->sock, SOL_SOCKET, SO_KEEPALIVE, 1);
    }

    /**
     * @inheritdoc
     */
    public function error_handler($errno, $errstr, $errfile, $errline, $errcontext = null)
    {
        // socket_select warning that it has been interrupted by a signal - EINTR
        if (false !== strrpos($errstr, socket_strerror(SOCKET_EINTR))) {
            // it's allowed while processing signals
            return;
        }

        parent::error_handler($errno, $errstr, $errfile, $errline, $errcontext);
    }

    /**
     * @inheritdoc
     */
    protected function set_error_handler()
    {
        parent::set_error_handler();
        socket_clear_error($this->sock);
    }
}
<?php

/* This file was autogenerated by spec/parser.php - Do not modify */

namespace PhpAmqpLib\Wire;

class Constants091
{
    /**
     * @var string
     */
    public static $AMQP_PROTOCOL_HEADER = "AMQP\x00\x00\x09\x01";

    /**
     * @var array
     */
    public static $FRAME_TYPES = array(
        1 => 'FRAME-METHOD',
        2 => 'FRAME-HEADER',
        3 => 'FRAME-BODY',
        8 => 'FRAME-HEARTBEAT',
        4096 => 'FRAME-MIN-SIZE',
        206 => 'FRAME-END',
        501 => 'FRAME-ERROR',
    );

    /**
     * @var array
     */
    public static $CONTENT_METHODS = array(
        0 => '60,40',
        1 => '60,50',
        2 => '60,60',
        3 => '60,71',
    );

    /**
     * @var array
     */
    public static $CLOSE_METHODS = array(
        0 => '10,50',
        1 => '20,40',
    );

    /**
     * @var array
     */
    public static $GLOBAL_METHOD_NAMES = array(
        '10,10' => 'Connection.start',
        '10,11' => 'Connection.start_ok',
        '10,20' => 'Connection.secure',
        '10,21' => 'Connection.secure_ok',
        '10,30' => 'Connection.tune',
        '10,31' => 'Connection.tune_ok',
        '10,40' => 'Connection.open',
        '10,41' => 'Connection.open_ok',
        '10,50' => 'Connection.close',
        '10,51' => 'Connection.close_ok',
        '10,60' => 'Connection.blocked',
        '10,61' => 'Connection.unblocked',
        '20,10' => 'Channel.open',
        '20,11' => 'Channel.open_ok',
        '20,20' => 'Channel.flow',
        '20,21' => 'Channel.flow_ok',
        '20,40' => 'Channel.close',
        '20,41' => 'Channel.close_ok',
        '30,10' => 'Access.request',
        '30,11' => 'Access.request_ok',
        '40,10' => 'Exchange.declare',
        '40,11' => 'Exchange.declare_ok',
        '40,20' => 'Exchange.delete',
        '40,21' => 'Exchange.delete_ok',
        '40,30' => 'Exchange.bind',
        '40,31' => 'Exchange.bind_ok',
        '40,40' => 'Exchange.unbind',
        '40,51' => 'Exchange.unbind_ok',
        '50,10' => 'Queue.declare',
        '50,11' => 'Queue.declare_ok',
        '50,20' => 'Queue.bind',
        '50,21' => 'Queue.bind_ok',
        '50,30' => 'Queue.purge',
        '50,31' => 'Queue.purge_ok',
        '50,40' => 'Queue.delete',
        '50,41' => 'Queue.delete_ok',
        '50,50' => 'Queue.unbind',
        '50,51' => 'Queue.unbind_ok',
        '60,10' => 'Basic.qos',
        '60,11' => 'Basic.qos_ok',
        '60,20' => 'Basic.consume',
        '60,21' => 'Basic.consume_ok',
        '60,30' => 'Basic.cancel',
        '60,31' => 'Basic.cancel_ok',
        '60,40' => 'Basic.publish',
        '60,50' => 'Basic.return',
        '60,60' => 'Basic.deliver',
        '60,70' => 'Basic.get',
        '60,71' => 'Basic.get_ok',
        '60,72' => 'Basic.get_empty',
        '60,80' => 'Basic.ack',
        '60,90' => 'Basic.reject',
        '60,100' => 'Basic.recover_async',
        '60,110' => 'Basic.recover',
        '60,111' => 'Basic.recover_ok',
        '60,120' => 'Basic.nack',
        '90,10' => 'Tx.select',
        '90,11' => 'Tx.select_ok',
        '90,20' => 'Tx.commit',
        '90,21' => 'Tx.commit_ok',
        '90,30' => 'Tx.rollback',
        '90,31' => 'Tx.rollback_ok',
        '85,10' => 'Confirm.select',
        '85,11' => 'Confirm.select_ok',
    );
}
<?php
namespace PhpAmqpLib\Wire;

use PhpAmqpLib\Channel\AbstractChannel;
use PhpAmqpLib\Exception;


/**
 * Iterator implemented for transparent integration with AMQPWriter::write_[array|table]()
 */
abstract class AMQPAbstractCollection implements \Iterator
{

    //protocol defines available field types and their corresponding symbols
    const PROTOCOL_080 = AbstractChannel::PROTOCOL_080;
    const PROTOCOL_091 = AbstractChannel::PROTOCOL_091;
    const PROTOCOL_RBT = 'rabbit'; //pseudo proto

    //Abstract data types
    const T_INT_SHORTSHORT = 1;
    const T_INT_SHORTSHORT_U = 2;
    const T_INT_SHORT = 3;
    const T_INT_SHORT_U = 4;
    const T_INT_LONG = 5;
    const T_INT_LONG_U = 6;
    const T_INT_LONGLONG = 7;
    const T_INT_LONGLONG_U = 8;

    const T_DECIMAL = 9;
    const T_TIMESTAMP = 10;
    const T_VOID = 11;

    const T_BOOL = 12;

    const T_STRING_SHORT = 13;
    const T_STRING_LONG = 14;

    const T_ARRAY = 15;
    const T_TABLE = 16;

    const T_BYTES = 17;
    /**
     * @var string
     */
    private static $_protocol = null;

    /*
     * Field types messy mess http://www.rabbitmq.com/amqp-0-9-1-errata.html#section_3
     * Default behaviour is to use rabbitMQ compatible field-set
     * Define AMQP_STRICT_FLD_TYPES=true to use strict AMQP instead
     */
    private static $_types_080 = array(
        self::T_INT_LONG => 'I',
        self::T_DECIMAL => 'D',
        self::T_TIMESTAMP => 'T',
        self::T_STRING_LONG => 'S',
        self::T_TABLE => 'F'
    );

    /**
     * @var array
     */
    private static $_types_091 = array(
        self::T_INT_SHORTSHORT => 'b',
        self::T_INT_SHORTSHORT_U => 'B',
        self::T_INT_SHORT => 'U',
        self::T_INT_SHORT_U => 'u',
        self::T_INT_LONG => 'I',
        self::T_INT_LONG_U => 'i',
        self::T_INT_LONGLONG => 'L',
        self::T_INT_LONGLONG_U => 'l',
        self::T_DECIMAL => 'D',
        self::T_TIMESTAMP => 'T',
        self::T_VOID => 'V',
        self::T_BOOL => 't',
        self::T_STRING_SHORT => 's',
        self::T_STRING_LONG => 'S',
        self::T_ARRAY => 'A',
        self::T_TABLE => 'F',
        self::T_BYTES => 'x',
    );

    /**
     * @var array
     */
    private static $_types_rabbit = array(
        self::T_INT_SHORTSHORT => 'b',
        self::T_INT_SHORT => 's',
        self::T_INT_LONG => 'I',
        self::T_INT_LONGLONG => 'l',
        self::T_DECIMAL => 'D',
        self::T_TIMESTAMP => 'T',
        self::T_VOID => 'V',
        self::T_BOOL => 't',
        self::T_STRING_LONG => 'S',
        self::T_ARRAY => 'A',
        self::T_TABLE => 'F',
        self::T_BYTES => 'x',
    );

    /**
     * @var array
     */
    protected $data = array();

    public function __construct(array $data = null)
    {
        if (!empty($data)) {
            $this->data = $this->encodeCollection($data);
        }
    }

    /**
     * @return int
     */
    abstract public function getType();

    /**
     * @param mixed $val
     * @param int $type
     * @param string $key
     */
    final protected function setValue($val, $type = null, $key = null)
    {
        if ($val instanceof self) {
            if ($type && ($type != $val->getType())) {
                throw new Exception\AMQPInvalidArgumentException(
                    'Attempted to add instance of ' . get_class($val) . ' representing type [' . $val->getType() . '] as mismatching type [' . $type . ']'
                );
            }
            $type = $val->getType();
        } elseif ($type) { //ensuring data integrity and that all members are properly validated
            switch ($type) {
                case self::T_ARRAY:
                    throw new Exception\AMQPInvalidArgumentException('Arrays must be passed as AMQPArray instance');
                    break;
                case self::T_TABLE:
                    throw new Exception\AMQPInvalidArgumentException('Tables must be passed as AMQPTable instance');
                    break;
                case self::T_DECIMAL:
                    if (!($val instanceof AMQPDecimal)) {
                        throw new Exception\AMQPInvalidArgumentException('Decimal values must be instance of AMQPDecimal');
                    }
                    break;
            }
        }

        if ($type) {
            self::checkDataTypeIsSupported($type, false);
            $val = array($type, $val);
        } else {
            $val = $this->encodeValue($val);
        }

        if ($key === null) {
            $this->data[] = $val;
        } else {
            $this->data[$key] = $val;
        }
    }

    /**
     * @return array
     */
    final public function getNativeData()
    {
        return $this->decodeCollection($this->data);
    }

    /**
     * @param array $val
     * @return array
     */
    final protected function encodeCollection(array $val)
    {
        foreach ($val as $k=>$v) {
            $val[$k] = $this->encodeValue($v);
        }

        return $val;
    }

    /**
     * @param array $val
     * @return array
     */
    final protected function decodeCollection(array $val)
    {
        foreach ($val as $k=>$v) {
            $val[$k] = $this->decodeValue($v[1], $v[0]);
        }

        return $val;
    }

    /**
     * @param mixed $val
     * @return mixed
     * @throws Exception\AMQPOutOfBoundsException
     */
    protected function encodeValue($val)
    {
        if (is_string($val)) {
            $val = $this->encodeString($val);
        } elseif (is_float($val)) {
            $val = $this->encodeFloat($val);
        } elseif (is_int($val)) {
            $val = $this->encodeInt($val);
        } elseif (is_bool($val)) {
            $val = $this->encodeBool($val);
        } elseif (is_null($val)) {
            $val = $this->encodeVoid();
        } elseif ($val instanceof \DateTimeInterface) {
            $val = array(self::T_TIMESTAMP, $val->getTimestamp());
        } elseif ($val instanceof \DateTime) {
            // PHP <= 5.4 has no DateTimeInterface
            $val = array(self::T_TIMESTAMP, $val->getTimestamp());
        } elseif ($val instanceof AMQPDecimal) {
            $val = array(self::T_DECIMAL, $val);
        } elseif ($val instanceof self) {
            //avoid silent type correction of strictly typed values
            self::checkDataTypeIsSupported($val->getType(), false);
            $val = array($val->getType(), $val);
        } elseif (is_array($val)) {
            //AMQP specs says "Field names MUST start with a letter, '$' or '#'"
            //so beware, some servers may raise an exception with 503 code in cases when indexed array is encoded as table
            if (self::isProtocol(self::PROTOCOL_080)) {
                //080 doesn't support arrays, forcing table
                $val = array(self::T_TABLE, new AMQPTable($val));
            } elseif (empty($val) || (array_keys($val) === range(0, count($val) - 1))) {
                $val = array(self::T_ARRAY, new AMQPArray($val));
            } else {
                $val = array(self::T_TABLE, new AMQPTable($val));
            }
        } else {
            throw new Exception\AMQPOutOfBoundsException(sprintf('Encountered value of unsupported type: %s', gettype($val)));
        }

        return $val;
    }

    /**
     * @param mixed $val
     * @param int $type
     * @return array|bool|\DateTime|null
     */
    protected function decodeValue($val, $type)
    {
        if ($val instanceof self) {
            //covering arrays and tables
            $val = $val->getNativeData();
        } else {
            switch ($type) {
                case self::T_BOOL:
                    $val = (bool) $val;
                    break;
                case self::T_TIMESTAMP:
                    $val = \DateTime::createFromFormat('U', $val);
                    break;
                case self::T_VOID:
                    $val = null;
                    break;
                case self::T_ARRAY:
                case self::T_TABLE:
                    throw new Exception\AMQPLogicException(
                        'Encountered an array/table struct which is not an instance of AMQPCollection. ' .
                        'This is considered a bug and should be fixed, please report'
                    );
            }
        }

        return $val;
    }

    /**
     * @param string $val
     * @return array
     */
    protected function encodeString($val)
    {
        return array(self::T_STRING_LONG, $val);
    }

    /**
     * @param int $val
     * @return array
     */
    protected function encodeInt($val)
    {
        if (($val >= -2147483648) && ($val <= 2147483647)) {
            $ev = array(self::T_INT_LONG, $val);
        } elseif (self::isProtocol(self::PROTOCOL_080)) {
            //080 doesn't support longlong
            $ev = $this->encodeString((string) $val);
        } else {
            $ev = array(self::T_INT_LONGLONG, $val);
        }

        return $ev;
    }

    /**
     * @param float $val
     * @return array
     */
    protected function encodeFloat($val)
    {
        return static::encodeString((string) $val);
    }

    /**
     * @param bool $val
     * @return array
     */
    protected function encodeBool($val)
    {
        $val = (bool) $val;

        return self::isProtocol(self::PROTOCOL_080) ? array(self::T_INT_LONG, (int) $val) : array(self::T_BOOL, $val);
    }

    /**
     * @return array
     */
    protected function encodeVoid()
    {
        return self::isProtocol(self::PROTOCOL_080) ? $this->encodeString('') : array(self::T_VOID, null);
    }

    /**
     * @return string
     */
    final public static function getProtocol()
    {
        if (self::$_protocol === null) {
            self::$_protocol = defined('AMQP_STRICT_FLD_TYPES') && AMQP_STRICT_FLD_TYPES ?
                AbstractChannel::getProtocolVersion() :
                self::PROTOCOL_RBT;
        }

        return self::$_protocol;
    }

    /**
     * @param string $proto
     * @return bool
     */
    final public static function isProtocol($proto)
    {
        return self::getProtocol() == $proto;
    }

    /**
     * @return array  [dataTypeConstant => dataTypeSymbol]
     */
    final public static function getSupportedDataTypes()
    {
        switch ($proto = self::getProtocol()) {
            case self::PROTOCOL_080:
                $types = self::$_types_080;
                break;
            case self::PROTOCOL_091:
                $types = self::$_types_091;
                break;
            case self::PROTOCOL_RBT:
                $types = self::$_types_rabbit;
                break;
            default:
                throw new Exception\AMQPOutOfRangeException(sprintf('Unknown protocol: %s', $proto));
        }

        return $types;
    }

    /**
     * @param string $type
     * @param bool $return Whether to return or raise AMQPOutOfRangeException
     * @return boolean
     */
    final public static function checkDataTypeIsSupported($type, $return = true)
    {
        try {
            $supported = self::getSupportedDataTypes();
            if (!isset($supported[$type])) {
                throw new Exception\AMQPOutOfRangeException(sprintf('AMQP-%s doesn\'t support data of type [%s]', self::getProtocol(), $type));
            }
            return true;

        } catch (Exception\AMQPOutOfRangeException $ex) {
            if (!$return) {
                throw $ex;
            }

            return false;
        }
    }

    /**
     * @param int $type
     * @return string
     */
    final public static function getSymbolForDataType($type)
    {
        $types = self::getSupportedDataTypes();
        if (!isset($types[$type])) {
            throw new Exception\AMQPOutOfRangeException(sprintf('AMQP-%s doesn\'t support data of type [%s]', self::getProtocol(), $type));
        }

        return $types[$type];
    }

    /**
     * @param string $symbol
     * @return integer
     */
    final public static function getDataTypeForSymbol($symbol)
    {
        $symbols = array_flip(self::getSupportedDataTypes());
        if (!isset($symbols[$symbol])) {
            throw new Exception\AMQPOutOfRangeException(sprintf('AMQP-%s doesn\'t define data of type [%s]', self::getProtocol(), $symbol));
        }

        return $symbols[$symbol];
    }

    public function current()
    {
        return current($this->data);
    }

    public function key()
    {
        return key($this->data);
    }

    public function next()
    {
        next($this->data);
    }

    public function rewind()
    {
        reset($this->data);
    }

    public function valid()
    {
        return key($this->data) !== null;
    }
}
<?php
namespace PhpAmqpLib\Wire;

use PhpAmqpLib\Exception\AMQPInvalidArgumentException;
use PhpAmqpLib\Exception\AMQPOutOfBoundsException;

class AMQPWriter extends AbstractClient
{
    /** @var string */
    protected $out;

    /** @var array */
    protected $bits;

    /** @var int */
    protected $bitcount;

    public function __construct()
    {
        parent::__construct();

        $this->out = '';
        $this->bits = array();
        $this->bitcount = 0;
    }

    /**
     * Packs integer into raw byte string in big-endian order
     * Supports positive and negative ints represented as PHP int or string (except scientific notation)
     *
     * Floats has some precision issues and so intentionally not supported.
     * Beware that floats out of PHP_INT_MAX range will be represented in scientific (exponential) notation when casted to string
     *
     * @param int|string $x Value to pack
     * @param int $bytes Must be multiply of 2
     * @return string
     */
    private static function packBigEndian($x, $bytes)
    {
        if (($bytes <= 0) || ($bytes % 2)) {
            throw new AMQPInvalidArgumentException(sprintf('Expected bytes count must be multiply of 2, %s given', $bytes));
        }

        $ox = $x; //purely for dbg purposes (overflow exception)
        $isNeg = false;

        if (is_int($x)) {
            if ($x < 0) {
                $isNeg = true;
                $x = abs($x);
            }
        } elseif (is_string($x)) {
            if (!is_numeric($x)) {
                throw new AMQPInvalidArgumentException(sprintf('Unknown numeric string format: %s', $x));
            }
            $x = preg_replace('/^-/', '', $x, 1, $isNeg);
        } else {
            throw new AMQPInvalidArgumentException('Only integer and numeric string values are supported');
        }

        if ($isNeg) {
            $x = bcadd($x, -1, 0);
        } //in negative domain starting point is -1, not 0

        $res = array();
        for ($b = 0; $b < $bytes; $b += 2) {
            $chnk = (int) bcmod($x, 65536);
            $x = bcdiv($x, 65536, 0);
            $res[] = pack('n', $isNeg ? ~$chnk : $chnk);
        }

        if ($x || ($isNeg && ($chnk & 0x8000))) {
            throw new AMQPOutOfBoundsException(sprintf('Overflow detected while attempting to pack %s into %s bytes', $ox, $bytes));
        }

        return implode(array_reverse($res));
    }

    private function flushbits()
    {
        if (!empty($this->bits)) {
            $this->out .= implode('', array_map('chr', $this->bits));
            $this->bits = array();
            $this->bitcount = 0;
        }
    }

    /**
     * Get what's been encoded so far.
     *
     * @return string
     */
    public function getvalue()
    {
        /* temporarily needed for compatibility with write_bit unit tests */
        if ($this->bitcount) {
            $this->flushbits();
        }

        return $this->out;
    }

    /**
     * Write a plain PHP string, with no special encoding.
     *
     * @param string $s
     *
     * @return $this
     */
    public function write($s)
    {
        $this->out .= $s;

        return $this;
    }

    /**
     * Write a boolean value.
     * (deprecated, use write_bits instead)
     *
     * @deprecated
     * @param bool $b
     * @return $this
     */
    public function write_bit($b)
    {
        $b = $b ? 1 : 0;
        $shift = $this->bitcount % 8;
        $last = $shift === 0 ? 0 : array_pop($this->bits);
        $last |= ($b << $shift);
        array_push($this->bits, $last);
        $this->bitcount += 1;

        return $this;
    }

    /**
     * Write multiple bits as an octet
     *
     * @param bool[] $bits
     * @return $this
     */
    public function write_bits($bits)
    {
        $value = 0;

        foreach ($bits as $n => $bit) {
            $bit = $bit ? 1 : 0;
            $value |= ($bit << $n);
        }

        $this->out .= chr($value);

        return $this;
    }

    /**
     * Write an integer as an unsigned 8-bit value
     *
     * @param int $n
     * @return $this
     * @throws \PhpAmqpLib\Exception\AMQPInvalidArgumentException
     */
    public function write_octet($n)
    {
        if ($n < 0 || $n > 255) {
            throw new AMQPInvalidArgumentException('Octet out of range: ' . $n);
        }

        $this->out .= chr($n);

        return $this;
    }

    /**
     * @param int $n
     * @return $this
     */
    public function write_signed_octet($n)
    {
        if (($n < -128) || ($n > 127)) {
            throw new AMQPInvalidArgumentException('Signed octet out of range: ' . $n);
        }

        $this->out .= pack('c', $n);

        return $this;
    }

    /**
     * Write an integer as an unsigned 16-bit value
     *
     * @param int $n
     * @return $this
     * @throws \PhpAmqpLib\Exception\AMQPInvalidArgumentException
     */
    public function write_short($n)
    {
        if ($n < 0 || $n > 65535) {
            throw new AMQPInvalidArgumentException('Short out of range: ' . $n);
        }

        $this->out .= pack('n', $n);

        return $this;
    }

    /**
     * @param int $n
     * @return $this
     */
    public function write_signed_short($n)
    {
        if (($n < -32768) || ($n > 32767)) {
            throw new AMQPInvalidArgumentException('Signed short out of range: ' . $n);
        }

        $this->out .= $this->correctEndianness(pack('s', $n));

        return $this;
    }

    /**
     * Write an integer as an unsigned 32-bit value
     *
     * @param int $n
     * @return $this
     */
    public function write_long($n)
    {
        if (($n < 0) || ($n > 4294967295)) {
            throw new AMQPInvalidArgumentException('Long out of range: ' . $n);
        }

        //Numeric strings >PHP_INT_MAX on 32bit are casted to PHP_INT_MAX, damn PHP
        if (empty($this->is64bits) && is_string($n)) {
            $n = (float) $n;
        }
        $this->out .= pack('N', $n);

        return $this;
    }

    /**
     * @param int $n
     * @return $this
     */
    private function write_signed_long($n)
    {
        if (($n < -2147483648) || ($n > 2147483647)) {
            throw new AMQPInvalidArgumentException('Signed long out of range: ' . $n);
        }

        //on my 64bit debian this approach is slightly faster than splitIntoQuads()
        $this->out .= $this->correctEndianness(pack('l', $n));

        return $this;
    }

    /**
     * Write an integer as an unsigned 64-bit value
     *
     * @param int $n
     * @return $this
     */
    public function write_longlong($n)
    {
        if ($n < 0) {
            throw new AMQPInvalidArgumentException('Longlong out of range: ' . $n);
        }

        // if PHP_INT_MAX is big enough for that
        // direct $n<=PHP_INT_MAX check is unreliable on 64bit (values close to max) due to limited float precision
        if (bcadd($n, -PHP_INT_MAX, 0) <= 0) {
            // trick explained in http://www.php.net/manual/fr/function.pack.php#109328
            if ($this->is64bits) {
                list($hi, $lo) = $this->splitIntoQuads($n);
            } else {
                $hi = 0;
                $lo = $n;
            } //on 32bits hi quad is 0 a priori
            $this->out .= pack('NN', $hi, $lo);
        } else {
            try {
                $this->out .= self::packBigEndian($n, 8);
            } catch (AMQPOutOfBoundsException $ex) {
                throw new AMQPInvalidArgumentException('Longlong out of range: ' . $n, 0, $ex);
            }
        }

        return $this;
    }

    /**
     * @param int $n
     * @return $this
     */
    public function write_signed_longlong($n)
    {
        if ((bcadd($n, PHP_INT_MAX, 0) >= -1) && (bcadd($n, -PHP_INT_MAX, 0) <= 0)) {
            if ($this->is64bits) {
                list($hi, $lo) = $this->splitIntoQuads($n);
            } else {
                $hi = $n < 0 ? -1 : 0;
                $lo = $n;
            } //0xffffffff for negatives
            $this->out .= pack('NN', $hi, $lo);
        } elseif ($this->is64bits) {
            throw new AMQPInvalidArgumentException('Signed longlong out of range: ' . $n);
        } else {
            if (bcadd($n, '-9223372036854775807', 0) > 0) {
                throw new AMQPInvalidArgumentException('Signed longlong out of range: ' . $n);
            }
            try {
                //will catch only negative overflow, as values >9223372036854775807 are valid for 8bytes range (unsigned)
                $this->out .= self::packBigEndian($n, 8);
            } catch (AMQPOutOfBoundsException $ex) {
                throw new AMQPInvalidArgumentException('Signed longlong out of range: ' . $n, 0, $ex);
            }
        }

        return $this;
    }

    /**
     * @param int|string $n
     * @return integer[]
     */
    private function splitIntoQuads($n)
    {
        $n = (int) $n;

        return array($n >> 32, $n & 0x00000000ffffffff);
    }

    /**
     * Write a string up to 255 bytes long after encoding.
     * Assume UTF-8 encoding
     *
     * @param string $s
     * @return $this
     * @throws \PhpAmqpLib\Exception\AMQPInvalidArgumentException
     */
    public function write_shortstr($s)
    {
        $len = mb_strlen($s, 'ASCII');
        if ($len > 255) {
            throw new AMQPInvalidArgumentException('String too long');
        }

        $this->write_octet($len);
        $this->out .= $s;

        return $this;
    }

    /**
     * Write a string up to 2**32 bytes long.  Assume UTF-8 encoding
     *
     * @param string $s
     * @return $this
     */
    public function write_longstr($s)
    {
        $this->write_long(mb_strlen($s, 'ASCII'));
        $this->out .= $s;

        return $this;
    }

    /**
     * Supports the writing of Array types, so that you can implement
     * array methods, like Rabbitmq's HA parameters
     *
     * @param AMQPArray|array $a Instance of AMQPArray or PHP array WITHOUT format hints (unlike write_table())
     * @return self
     */
    public function write_array($a)
    {
        if (!($a instanceof AMQPArray)) {
            $a = new AMQPArray($a);
        }
        $data = new self();

        foreach ($a as $v) {
            $data->write_value($v[0], $v[1]);
        }

        $data = $data->getvalue();
        $this->write_long(mb_strlen($data, 'ASCII'));
        $this->write($data);

        return $this;
    }

    /**
     * Write unix time_t value as 64 bit timestamp
     *
     * @param int $v
     * @return $this
     */
    public function write_timestamp($v)
    {
        $this->write_longlong($v);

        return $this;
    }

    /**
     * Write PHP array, as table. Input array format: keys are strings,
     * values are (type,value) tuples.
     *
     * @param AMQPTable|array $d Instance of AMQPTable or PHP array WITH format hints (unlike write_array())
     * @return $this
     * @throws \PhpAmqpLib\Exception\AMQPInvalidArgumentException
     */
    public function write_table($d)
    {
        $typeIsSym = !($d instanceof AMQPTable); //purely for back-compat purposes

        $table_data = new AMQPWriter();
        foreach ($d as $k => $va) {
            list($ftype, $v) = $va;
            $table_data->write_shortstr($k);
            $table_data->write_value($typeIsSym ? AMQPAbstractCollection::getDataTypeForSymbol($ftype) : $ftype, $v);
        }

        $table_data = $table_data->getvalue();
        $this->write_long(mb_strlen($table_data, 'ASCII'));
        $this->write($table_data);

        return $this;
    }

    /**
     * for compat with method mapping used by AMQPMessage
     *
     * @param AMQPTable|array
     * @return $this
     */
    public function write_table_object($d)
    {
        return $this->write_table($d);
    }

    /**
     * @param int $type One of AMQPAbstractCollection::T_* constants
     * @param mixed $val
     */
    private function write_value($type, $val)
    {
        //This will find appropriate symbol for given data type for currently selected protocol
        //Also will raise an exception on unknown type
        $this->write(AMQPAbstractCollection::getSymbolForDataType($type));

        switch ($type) {
            case AMQPAbstractCollection::T_INT_SHORTSHORT:
                $this->write_signed_octet($val);
                break;
            case AMQPAbstractCollection::T_INT_SHORTSHORT_U:
                $this->write_octet($val);
                break;
            case AMQPAbstractCollection::T_INT_SHORT:
                $this->write_signed_short($val);
                break;
            case AMQPAbstractCollection::T_INT_SHORT_U:
                $this->write_short($val);
                break;
            case AMQPAbstractCollection::T_INT_LONG:
                $this->write_signed_long($val);
                break;
            case AMQPAbstractCollection::T_INT_LONG_U:
                $this->write_long($val);
                break;
            case AMQPAbstractCollection::T_INT_LONGLONG:
                $this->write_signed_longlong($val);
                break;
            case AMQPAbstractCollection::T_INT_LONGLONG_U:
                $this->write_longlong($val);
                break;
            case AMQPAbstractCollection::T_DECIMAL:
                $this->write_octet($val->getE());
                $this->write_signed_long($val->getN());
                break;
            case AMQPAbstractCollection::T_TIMESTAMP:
                $this->write_timestamp($val);
                break;
            case AMQPAbstractCollection::T_BOOL:
                $this->write_octet($val ? 1 : 0);
                break;
            case AMQPAbstractCollection::T_STRING_SHORT:
                $this->write_shortstr($val);
                break;
            case AMQPAbstractCollection::T_STRING_LONG:
                $this->write_longstr($val);
                break;
            case AMQPAbstractCollection::T_ARRAY:
                $this->write_array($val);
                break;
            case AMQPAbstractCollection::T_TABLE:
                $this->write_table($val);
                break;
            case AMQPAbstractCollection::T_VOID:
                break;
            case AMQPAbstractCollection::T_BYTES:
                $this->write_longstr($val);
                break;
            default:
                throw new AMQPInvalidArgumentException(sprintf(
                    'Unsupported type "%s"',
                    $type
                ));
        }
    }
}
<?php
namespace PhpAmqpLib\Wire;

use PhpAmqpLib\Channel\AMQPChannel;

/**
 * Abstract base class for AMQP content.  Subclasses should override
 * the propertyDefinitions attribute.
 */
abstract class GenericContent
{
    /** @var array */
    public $delivery_info = array();

    /** @var array Final property definitions */
    protected $prop_types;

    /** @var array Properties content */
    private $properties = array();

    /** @var string Compiled properties */
    private $serialized_properties;

    /**
     * @var array
     */
    protected static $propertyDefinitions = array(
        'dummy' => 'shortstr'
    );

    /**
     * @param array $properties Message property content
     * @param array $propertyTypes Message property definitions
     */
    public function __construct($properties, $propertyTypes = null)
    {
        $this->prop_types = !empty($propertyTypes) ? $propertyTypes : self::$propertyDefinitions;

        if (!empty($properties)) {
            $this->properties = array_intersect_key($properties, $this->prop_types);
        }
    }

    /**
     * Check whether a property exists in the 'properties' dictionary
     * or if present - in the 'delivery_info' dictionary.
     *
     * @param string $name
     * @return bool
     */
    public function has($name)
    {
        return isset($this->properties[$name]) || isset($this->delivery_info[$name]);
    }

    /**
     * Look for additional properties in the 'properties' dictionary,
     * and if present - the 'delivery_info' dictionary.
     *
     * @param string $name
     * @throws \OutOfBoundsException
     * @return mixed|AMQPChannel
     */
    public function get($name)
    {
        if (isset($this->properties[$name])) {
            return $this->properties[$name];
        }

        if (isset($this->delivery_info[$name])) {
            return $this->delivery_info[$name];
        }

        throw new \OutOfBoundsException(sprintf(
            'No "%s" property',
            $name
        ));
    }

    /**
     * Returns the properties content
     *
     * @return array
     */
    public function get_properties()
    {
        return $this->properties;
    }

    /**
     * Sets a property value
     *
     * @param string $name The property name (one of the property definition)
     * @param mixed $value The property value
     * @throws \OutOfBoundsException
     */
    public function set($name, $value)
    {
        if (!array_key_exists($name, $this->prop_types)) {
            throw new \OutOfBoundsException(sprintf(
                'No "%s" property',
                $name
            ));
        }
        
        if (isset($this->properties[$name]) && $this->properties[$name] !== $value || !isset($this->properties[$name])) {
            $this->serialized_properties = null;
        }

        $this->properties[$name] = $value;
    }

    /**
     * Given the raw bytes containing the property-flags and
     * property-list from a content-frame-header, parse and insert
     * into a dictionary stored in this object as an attribute named
     * 'properties'.
     *
     * @param AMQPReader $reader
     * NOTE: do not mutate $reader
     * @return $this
     */
    public function load_properties(AMQPReader $reader)
    {
        // Read 16-bit shorts until we get one with a low bit set to zero
        $flags = array();

        while (true) {
            $flag_bits = $reader->read_short();
            $flags[] = $flag_bits;

            if (($flag_bits & 1) === 0) {
                break;
            }
        }

        $shift = 0;
        $data = array();

        foreach ($this->prop_types as $key => $proptype) {
            if ($shift === 0) {
                if (!$flags) {
                    break;
                }
                $flag_bits = array_shift($flags);
                $shift = 15;
            }

            if ($flag_bits & (1 << $shift)) {
                $data[$key] = $reader->{'read_' . $proptype}();
            }

            $shift -= 1;
        }

        $this->properties = $data;

        return $this;
    }


    /**
     * Serializes the 'properties' attribute (a dictionary) into the
     * raw bytes making up a set of property flags and a property
     * list, suitable for putting into a content frame header.
     *
     * @return string
     * @todo Inject the AMQPWriter to make the method easier to test
     */
    public function serialize_properties()
    {
        if (!empty($this->serialized_properties)) {
            return $this->serialized_properties;
        }

        $shift = 15;
        $flag_bits = 0;
        $flags = array();
        $raw_bytes = new AMQPWriter();

        foreach ($this->prop_types as $key => $prototype) {
            $val = isset($this->properties[$key]) ? $this->properties[$key] : null;

            // Very important: PHP type eval is weak, use the === to test the
            // value content. Zero or false value should not be removed
            if ($val === null) {
                $shift -= 1;
                continue;
            }

            if ($shift === 0) {
                $flags[] = $flag_bits;
                $flag_bits = 0;
                $shift = 15;
            }

            $flag_bits |= (1 << $shift);
            if ($prototype != 'bit') {
                $raw_bytes->{'write_' . $prototype}($val);
            }

            $shift -= 1;
        }

        $flags[] = $flag_bits;
        $result = new AMQPWriter();
        foreach ($flags as $flag_bits) {
            $result->write_short($flag_bits);
        }

        $result->write($raw_bytes->getvalue());

        $this->serialized_properties = $result->getvalue();

        return $this->serialized_properties;
    }
}
<?php
namespace PhpAmqpLib\Wire;


class AMQPArray extends AMQPAbstractCollection
{

    /**
     * @param array|null $data
     */
    public function __construct(array $data = null)
    {
        parent::__construct(empty($data) ? null : array_values($data));
    }

    /**
     * @return int
     */
    final public function getType()
    {
        return self::T_ARRAY;
    }

    /**
     * @param mixed $val
     * @param null $type
     * @return $this
     */
    public function push($val, $type = null)
    {
        $this->setValue($val, $type);

        return $this;
    }
}
<?php
namespace PhpAmqpLib\Connection;

use PhpAmqpLib\Wire\IO\SocketIO;

class AMQPSocketConnection extends AbstractConnection
{
    /**
     * @param string $host
     * @param int $port
     * @param string $user
     * @param string $password
     * @param string $vhost
     * @param bool $insist
     * @param string $login_method
     * @param null $login_response
     * @param string $locale
     * @param float $read_timeout
     * @param bool $keepalive
     * @param int $write_timeout
     * @param int $heartbeat
     * @param float $channel_rpc_timeout
     * @throws \Exception
     */
    public function __construct(
        $host,
        $port,
        $user,
        $password,
        $vhost = '/',
        $insist = false,
        $login_method = 'AMQPLAIN',
        $login_response = null,
        $locale = 'en_US',
        $read_timeout = 130,
        $keepalive = false,
        $write_timeout = 130,
        $heartbeat = 60,
        $channel_rpc_timeout = 0.0
    ) {
        if ($channel_rpc_timeout > $read_timeout) {
            throw new \InvalidArgumentException('channel RPC timeout must not be greater than I/O read timeout');
        }

        $io = new SocketIO($host, $port, $read_timeout, $keepalive, $write_timeout, $heartbeat);

        parent::__construct(
            $user,
            $password,
            $vhost,
            $insist,
            $login_method,
            $login_response,
            $locale,
            $io,
            $heartbeat,
            max($read_timeout, $write_timeout),
            $channel_rpc_timeout
        );
    }

    protected static function try_create_connection($host, $port, $user, $password, $vhost, $options){
        $insist = isset($options['insist']) ?
                        $options['insist'] : false;
        $login_method = isset($options['login_method']) ?
                              $options['login_method'] :'AMQPLAIN';
        $login_response = isset($options['login_response']) ?
                                $options['login_response'] : null;
        $locale = isset($options['locale']) ?
                        $options['locale'] : 'en_US';
        $read_timeout = isset($options['read_timeout']) ?
                              $options['read_timeout'] : 130;
        $keepalive = isset($options['keepalive']) ?
                           $options['keepalive'] : false;
        $write_timeout = isset($options['write_timeout']) ?
                               $options['write_timeout'] : 130;
        $heartbeat = isset($options['heartbeat']) ?
                           $options['heartbeat'] : 60;
        return new static($host,
                          $port,
                          $user,
                          $password,
                          $vhost,
                          $insist,
                          $login_method,
                          $login_response,
                          $locale,
                          $read_timeout,
                          $keepalive,
                          $write_timeout,
                          $heartbeat);
    }
}
<?php
namespace PhpAmqpLib\Connection;

class AMQPSSLConnection extends AMQPStreamConnection
{
    /**
     * @param string $host
     * @param int $port
     * @param string $user
     * @param string $password
     * @param string $vhost
     * @param array $ssl_options
     * @param array $options
     * @param string $ssl_protocol
     */
    public function __construct(
        $host,
        $port,
        $user,
        $password,
        $vhost = '/',
        $ssl_options = array(),
        $options = array(),
        $ssl_protocol = 'ssl'
    ) {
        $ssl_context = empty($ssl_options) ? null : $this->create_ssl_context($ssl_options);
        parent::__construct(
            $host,
            $port,
            $user,
            $password,
            $vhost,
            isset($options['insist']) ? $options['insist'] : false,
            isset($options['login_method']) ? $options['login_method'] : 'AMQPLAIN',
            isset($options['login_response']) ? $options['login_response'] : null,
            isset($options['locale']) ? $options['locale'] : 'en_US',
            isset($options['connection_timeout']) ? $options['connection_timeout'] : 3,
            isset($options['read_write_timeout']) ? $options['read_write_timeout'] : 130,
            $ssl_context,
            isset($options['keepalive']) ? $options['keepalive'] : false,
            isset($options['heartbeat']) ? $options['heartbeat'] : 60,
            isset($options['channel_rpc_timeout']) ? $options['channel_rpc_timeout'] : 0.0,
            $ssl_protocol
        );
    }

    public static function try_create_connection($host, $port, $user, $password, $vhost, $options) {
        $ssl_options = isset($options['ssl_options']) ? $options['ssl_options'] : [];
        return new static($host, $port, $user, $password, $vhost, $ssl_options, $options);
    }

    /**
     * @param array $options
     * @return resource
     */
    private function create_ssl_context($options)
    {
        $ssl_context = stream_context_create();
        foreach ($options as $k => $v) {
            stream_context_set_option($ssl_context, 'ssl', $k, $v);
        }

        return $ssl_context;
    }
}
<?php
namespace PhpAmqpLib\Connection;

class AMQPLazyConnection extends AMQPStreamConnection
{
    /**
     * Gets socket from current connection
     *
     * @deprecated
     */
    public function getSocket()
    {
        $this->connect();

        return parent::getSocket();
    }

    /**
     * {@inheritdoc}
     */
    public function channel($channel_id = null)
    {
        $this->connect();

        return parent::channel($channel_id);
    }

    /**
     * @return null|\PhpAmqpLib\Wire\IO\AbstractIO
     */
    public function getIO()
    {
        if (empty($this->io)) {
            $this->connect();
        }

        return $this->io;
    }

    /**
     * Should the connection be attempted during construction?
     *
     * @return bool
     */
    public function connectOnConstruct()
    {
        return false;
    }
}
<?php

namespace PhpAmqpLib\Connection;

/**
 * Yet another lazy connection. This time using sockets. Current architecture doesn't allow to wrap existing connections
 */
class AMQPLazySocketConnection extends AMQPSocketConnection
{
    /**
     * Gets socket from current connection
     *
     * @deprecated
     */
    public function getSocket()
    {
        $this->connect();

        return parent::getSocket();
    }

    /**
     * {@inheritdoc}
     */
    public function channel($channel_id = null)
    {
        $this->connect();

        return parent::channel($channel_id);
    }

    /**
     * @return null|\PhpAmqpLib\Wire\IO\AbstractIO
     */
    public function getIO()
    {
        if (empty($this->io)) {
            $this->connect();
        }

        return $this->io;
    }

    /**
     * Should the connection be attempted during construction?
     *
     * @return bool
     */
    public function connectOnConstruct()
    {
        return false;
    }
}
<?php
namespace PhpAmqpLib\Connection;

use PhpAmqpLib\Channel\AbstractChannel;
use PhpAmqpLib\Channel\AMQPChannel;
use PhpAmqpLib\Exception\AMQPConnectionClosedException;
use PhpAmqpLib\Exception\AMQPHeartbeatMissedException;
use PhpAmqpLib\Exception\AMQPInvalidFrameException;
use PhpAmqpLib\Exception\AMQPNoDataException;
use PhpAmqpLib\Exception\AMQPProtocolConnectionException;
use PhpAmqpLib\Exception\AMQPRuntimeException;
use PhpAmqpLib\Exception\AMQPTimeoutException;
use PhpAmqpLib\Wire\AMQPReader;
use PhpAmqpLib\Wire\AMQPTable;
use PhpAmqpLib\Wire\AMQPWriter;
use PhpAmqpLib\Wire\IO\AbstractIO;

class AbstractConnection extends AbstractChannel
{
    /**
     * @var array
     * @internal
     */
    public static $LIBRARY_PROPERTIES = array(
        'product' => array('S', 'AMQPLib'),
        'platform' => array('S', 'PHP'),
        'version' => array('S', '2.9'),
        'information' => array('S', ''),
        'copyright' => array('S', ''),
        'capabilities' => array(
            'F',
            array(
                'authentication_failure_close' => array('t', true),
                'publisher_confirms' => array('t', true),
                'consumer_cancel_notify' => array('t', true),
                'exchange_exchange_bindings' => array('t', true),
                'basic.nack' => array('t', true),
                'connection.blocked' => array('t', true)
            )
        )
    );

    /**
     * @var AMQPChannel[]
     * @internal
     */
    public $channels = array();

    /** @var int */
    protected $version_major;

    /** @var int */
    protected $version_minor;

    /** @var array */
    protected $server_properties;

    /** @var array */
    protected $mechanisms;

    /** @var array */
    protected $locales;

    /** @var bool */
    protected $wait_tune_ok;

    /** @var string */
    protected $known_hosts;

    /** @var AMQPReader */
    protected $input;

    /** @var string */
    protected $vhost;

    /** @var bool */
    protected $insist;

    /** @var string */
    protected $login_method;

    /** @var string */
    protected $login_response;

    /** @var string */
    protected $locale;

    /** @var int */
    protected $heartbeat;

    /** @var float */
    protected $last_frame;

    /** @var int */
    protected $channel_max = 65535;

    /** @var int */
    protected $frame_max = 131072;

     /** @var array Constructor parameters for clone */
    protected $construct_params;

    /** @var bool Close the connection in destructor */
    protected $close_on_destruct = true;

    /** @var bool Maintain connection status */
    protected $is_connected = false;

    /** @var \PhpAmqpLib\Wire\IO\AbstractIO */
    protected $io;

    /** @var \PhpAmqpLib\Wire\AMQPReader */
    protected $wait_frame_reader;

    /** @var callable Handles connection blocking from the server */
    private $connection_block_handler;

    /** @var callable Handles connection unblocking from the server */
    private $connection_unblock_handler;

    /** @var int Connection timeout value*/
    protected $connection_timeout ;

    /**
     * Circular buffer to speed up prepare_content().
     * Max size limited by $prepare_content_cache_max_size.
     *
     * @var array
     * @see prepare_content()
     */
    private $prepare_content_cache = array();

    /** @var int Maximal size of $prepare_content_cache */
    private $prepare_content_cache_max_size = 100;

    /**
     * Maximum time to wait for channel operations, in seconds
     * @var float $channel_rpc_timeout
     */
    private $channel_rpc_timeout;

    /**
     * @param string $user
     * @param string $password
     * @param string $vhost
     * @param bool $insist
     * @param string $login_method
     * @param null $login_response
     * @param string $locale
     * @param AbstractIO $io
     * @param int $heartbeat
     * @param int $connection_timeout
     * @param float $channel_rpc_timeout
     * @throws \Exception
     */
    public function __construct(
        $user,
        $password,
        $vhost = '/',
        $insist = false,
        $login_method = 'AMQPLAIN',
        $login_response = null,
        $locale = 'en_US',
        AbstractIO $io,
        $heartbeat = 60,
        $connection_timeout = 0,
        $channel_rpc_timeout = 0.0
    ) {
        // save the params for the use of __clone
        $this->construct_params = func_get_args();

        $this->wait_frame_reader = new AMQPReader(null);
        $this->vhost = $vhost;
        $this->insist = $insist;
        $this->login_method = $login_method;
        $this->login_response = $login_response;
        $this->locale = $locale;
        $this->io = $io;
        $this->heartbeat = $heartbeat;
        $this->connection_timeout = $connection_timeout;
        $this->channel_rpc_timeout = $channel_rpc_timeout;

        if ($user && $password) {
            $this->login_response = new AMQPWriter();
            $this->login_response->write_table(array(
                'LOGIN' => array('S', $user),
                'PASSWORD' => array('S', $password)
            ));

            // Skip the length
            $responseValue = $this->login_response->getvalue();
            $this->login_response = mb_substr($responseValue, 4, mb_strlen($responseValue, 'ASCII') - 4, 'ASCII');

        } else {
            $this->login_response = null;
        }

        // Lazy Connection waits on connecting
        if ($this->connectOnConstruct()) {
            $this->connect();
        }
    }

    /**
     * Connects to the AMQP server
     */
    protected function connect()
    {
        try {
            // Loop until we connect
            while (!$this->isConnected()) {
                // Assume we will connect, until we dont
                $this->setIsConnected(true);

                // Connect the socket
                $this->io->connect();

                $this->channels = array();
                // The connection object itself is treated as channel 0
                parent::__construct($this, 0);

                $this->input = new AMQPReader(null, $this->io);

                $this->write($this->amqp_protocol_header);
                $this->wait(array($this->waitHelper->get_wait('connection.start')),false,$this->connection_timeout);
                $this->x_start_ok(
                    $this->getLibraryProperties(),
                    $this->login_method,
                    $this->login_response,
                    $this->locale
                );

                $this->wait_tune_ok = true;
                while ($this->wait_tune_ok) {
                    $this->wait(array(
                        $this->waitHelper->get_wait('connection.secure'),
                        $this->waitHelper->get_wait('connection.tune')
                    ));
                }

                $host = $this->x_open($this->vhost, '', $this->insist);
                if (!$host) {
                    //Reconnected
                    $this->io->reenableHeartbeat();
                    return null; // we weren't redirected
                }

                $this->setIsConnected(false);
                $this->closeChannels();

                // we were redirected, close the socket, loop and try again
                $this->close_socket();
            }

        } catch (\Exception $e) {
            // Something went wrong, set the connection status
            $this->setIsConnected(false);
            $this->closeChannels();
            $this->close_input();
            $this->close_socket();
            throw $e; // Rethrow exception
        }
    }

    /**
     * Reconnects using the original connection settings.
     * This will not recreate any channels that were established previously
     */
    public function reconnect()
    {
        // Try to close the AMQP connection
        $this->safeClose();
        // Reconnect the socket/stream then AMQP
        $this->io->close();
        $this->setIsConnected(false); // getIO can initiate the connection setting via LazyConnection, set it here to be sure
        $this->connect();
    }

    /**
     * Cloning will use the old properties to make a new connection to the same server
     */
    public function __clone()
    {
        call_user_func_array(array($this, '__construct'), $this->construct_params);
    }

    public function __destruct()
    {
        if ($this->close_on_destruct) {
            $this->safeClose();
        }
    }

    /**
     * Attempts to close the connection safely
     */
    protected function safeClose()
    {
        try {
            if (isset($this->input) && $this->input) {
                $this->close();
            }
        } catch (\Exception $e) {
            // Nothing here
        }
    }

    /**
     * @param int $sec
     * @param int $usec
     * @return mixed
     */
    public function select($sec, $usec = 0)
    {
        try {
            return $this->io->select($sec, $usec);
        } catch (AMQPConnectionClosedException $e) {
            $this->do_close();
            throw $e;
        } catch (AMQPRuntimeException $e) {
            $this->setIsConnected(false);
            throw $e;
        }
    }

    /**
     * Allows to not close the connection
     * it's useful after the fork when you don't want to close parent process connection
     *
     * @param bool $close
     */
    public function set_close_on_destruct($close = true)
    {
        $this->close_on_destruct = (bool) $close;
    }

    protected function close_input()
    {
        $this->debug && $this->debug->debug_msg('closing input');

        if (!is_null($this->input)) {
            $this->input->close();
            $this->input = null;
        }
    }

    protected function close_socket()
    {
        $this->debug && $this->debug->debug_msg('closing socket');

        if ($this->io) {
            $this->io->close();
        }
    }

    /**
     * @param string $data
     */
    public function write($data)
    {
        $this->debug->debug_hexdump($data);

        try {
            $this->io->write($data);
        } catch (AMQPConnectionClosedException $e) {
            $this->do_close();
            throw $e;
        } catch (AMQPRuntimeException $e) {
            $this->setIsConnected(false);
            throw $e;
        }
    }

    protected function do_close()
    {
        $this->setIsConnected(false);
        $this->close_input();
        $this->close_socket();
    }

    /**
     * @return int
     * @throws \PhpAmqpLib\Exception\AMQPRuntimeException
     */
    public function get_free_channel_id()
    {
        for ($i = 1; $i <= $this->channel_max; $i++) {
            if (!isset($this->channels[$i])) {
                return $i;
            }
        }

        throw new AMQPRuntimeException('No free channel ids');
    }

    /**
     * @param string $channel
     * @param int $class_id
     * @param int $weight
     * @param int $body_size
     * @param string $packed_properties
     * @param string $body
     * @param AMQPWriter $pkt
     */
    public function send_content($channel, $class_id, $weight, $body_size, $packed_properties, $body, $pkt = null)
    {
        $this->prepare_content($channel, $class_id, $weight, $body_size, $packed_properties, $body, $pkt);
        $this->write($pkt->getvalue());
    }

    /**
     * Returns a new AMQPWriter or mutates the provided $pkt
     *
     * @param string $channel
     * @param int $class_id
     * @param int $weight
     * @param int $body_size
     * @param string $packed_properties
     * @param string $body
     * @param AMQPWriter $pkt
     * @return AMQPWriter
     */
    public function prepare_content($channel, $class_id, $weight, $body_size, $packed_properties, $body, $pkt = null)
    {
        $pkt = $pkt ?: new AMQPWriter();

        // Content already prepared ?
        $key_cache = sprintf(
            '%s|%s|%s|%s',
            $channel,
            $packed_properties,
            $class_id,
            $weight
        );

        if (!isset($this->prepare_content_cache[$key_cache])) {
            $w = new AMQPWriter();
            $w->write_octet(2);
            $w->write_short($channel);
            $w->write_long(mb_strlen($packed_properties, 'ASCII') + 12);
            $w->write_short($class_id);
            $w->write_short($weight);
            $this->prepare_content_cache[$key_cache] = $w->getvalue();
            if (count($this->prepare_content_cache) > $this->prepare_content_cache_max_size) {
                reset($this->prepare_content_cache);
                $old_key = key($this->prepare_content_cache);
                unset($this->prepare_content_cache[$old_key]);
            }
        }
        $pkt->write($this->prepare_content_cache[$key_cache]);

        $pkt->write_longlong($body_size);
        $pkt->write($packed_properties);

        $pkt->write_octet(0xCE);


        // memory efficiency: walk the string instead of biting
        // it. good for very large packets (close in size to
        // memory_limit setting)
        $position = 0;
        $bodyLength = mb_strlen($body,'ASCII');
        while ($position < $bodyLength) {
            $payload = mb_substr($body, $position, $this->frame_max - 8, 'ASCII');
            $position += $this->frame_max - 8;

            $pkt->write_octet(3);
            $pkt->write_short($channel);
            $pkt->write_long(mb_strlen($payload, 'ASCII'));

            $pkt->write($payload);

            $pkt->write_octet(0xCE);
        }

        return $pkt;
    }

    /**
     * @param string $channel
     * @param array $method_sig
     * @param AMQPWriter|string $args
     * @param null $pkt
     */
    protected function send_channel_method_frame($channel, $method_sig, $args = '', $pkt = null)
    {
        $pkt = $this->prepare_channel_method_frame($channel, $method_sig, $args, $pkt);
        $this->write($pkt->getvalue());
        $this->debug->debug_method_signature1($method_sig);
    }

    /**
     * Returns a new AMQPWriter or mutates the provided $pkt
     *
     * @param string $channel
     * @param array $method_sig
     * @param AMQPWriter|string $args
     * @param AMQPWriter $pkt
     * @return AMQPWriter
     */
    protected function prepare_channel_method_frame($channel, $method_sig, $args = '', $pkt = null)
    {
        if ($args instanceof AMQPWriter) {
            $args = $args->getvalue();
        }

        $pkt = $pkt ?: new AMQPWriter();

        $pkt->write_octet(1);
        $pkt->write_short($channel);
        $pkt->write_long(mb_strlen($args, 'ASCII') + 4); // 4 = length of class_id and method_id
        // in payload

        $pkt->write_short($method_sig[0]); // class_id
        $pkt->write_short($method_sig[1]); // method_id
        $pkt->write($args);

        $pkt->write_octet(0xCE);

        $this->debug->debug_method_signature1($method_sig);

        return $pkt;
    }

    /**
     * Waits for a frame from the server
     *
     * @param int|float|null $timeout
     * @return array
     * @throws \Exception
     * @throws \PhpAmqpLib\Exception\AMQPTimeoutException
     * @throws \PhpAmqpLib\Exception\AMQPRuntimeException
     */
    protected function wait_frame($timeout = 0)
    {
        if (is_null($this->input))
        {
            $this->setIsConnected(false);
            throw new AMQPConnectionClosedException('Broken pipe or closed connection');
        }

        $currentTimeout = $this->input->getTimeout();
        $this->input->setTimeout($timeout);

        try {
            // frame_type + channel_id + size
            $this->wait_frame_reader->reuse(
                $this->input->read(AMQPReader::OCTET + AMQPReader::SHORT + AMQPReader::LONG)
            );

            $frame_type = $this->wait_frame_reader->read_octet();
            $class = self::$PROTOCOL_CONSTANTS_CLASS;
            if (!array_key_exists($frame_type, $class::$FRAME_TYPES)) {
                throw new AMQPInvalidFrameException('Invalid frame type ' . $frame_type);
            }
            $channel = $this->wait_frame_reader->read_short();
            $size = $this->wait_frame_reader->read_long();

            // payload + ch
            $this->wait_frame_reader->reuse($this->input->read(AMQPReader::OCTET + (int) $size));

            $payload = $this->wait_frame_reader->read($size);
            $ch = $this->wait_frame_reader->read_octet();

        } catch (AMQPTimeoutException $e) {
            $this->input->setTimeout($currentTimeout);
            throw $e;
        } catch (AMQPNoDataException $e) {
            if ($this->input) {
                $this->input->setTimeout($currentTimeout);
            }
            throw $e;
        } catch (AMQPConnectionClosedException $exception) {
            $this->do_close();
            throw $exception;
        }

        $this->input->setTimeout($currentTimeout);

        if ($ch != 0xCE) {
            throw new AMQPInvalidFrameException(sprintf(
                'Framing error, unexpected byte: %x',
                $ch
            ));
        }

        return array($frame_type, $channel, $payload);
    }

    /**
     * Waits for a frame from the server destined for a particular channel.
     *
     * @param string $channel_id
     * @param int|float|null $timeout
     * @return array
     */
    protected function wait_channel($channel_id, $timeout = 0)
    {
        // Keeping the original timeout unchanged.
        $_timeout = $timeout;
        while (true) {
            $now = time();
            try {
                list($frame_type, $frame_channel, $payload) = $this->wait_frame($_timeout);
            } catch (AMQPTimeoutException $e) {
                if ( $this->heartbeat && microtime(true) - ($this->heartbeat*2) > $this->last_frame ) {
                    $this->debug->debug_msg("missed server heartbeat (at threshold * 2)");
                    $this->setIsConnected(false);
                    throw new AMQPHeartbeatMissedException("Missed server heartbeat");
                }

                throw $e;
            }

            $this->last_frame = microtime(true);

            if ($frame_channel === 0 && $frame_type === 8) {
                // skip heartbeat frames and reduce the timeout by the time passed
                $this->debug->debug_msg("received server heartbeat");
                if($_timeout > 0) {
                    $_timeout -= time() - $now;
                    if($_timeout <= 0) {
                        // If timeout has been reached, throw the exception without calling wait_frame
                        throw new AMQPTimeoutException("Timeout waiting on channel");
                    }
                }
                continue;

            } else {

                if ($frame_channel == $channel_id) {
                    return array($frame_type, $payload);
                }

                // Not the channel we were looking for.  Queue this frame
                //for later, when the other channel is looking for frames.
                // Make sure the channel still exists, it could have been
                // closed by a previous Exception.
                if (isset($this->channels[$frame_channel])) {
                    array_push($this->channels[$frame_channel]->frame_queue, array($frame_type, $payload));
                }

                // If we just queued up a method for channel 0 (the Connection
                // itself) it's probably a close method in reaction to some
                // error, so deal with it right away.
                if (($frame_type == 1) && ($frame_channel == 0)) {
                    $this->wait();
                }
            }
        }
    }

    /**
     * Fetches a channel object identified by the numeric channel_id, or
     * create that object if it doesn't already exist.
     *
     * @param int $channel_id
     * @return AMQPChannel
     */
    public function channel($channel_id = null)
    {
        if (isset($this->channels[$channel_id])) {
            return $this->channels[$channel_id];
        }

        $channel_id = $channel_id ? $channel_id : $this->get_free_channel_id();
        $ch = new AMQPChannel($this->connection, $channel_id, true, $this->channel_rpc_timeout);
        $this->channels[$channel_id] = $ch;

        return $ch;
    }

    /**
     * Requests a connection close
     *
     * @param int $reply_code
     * @param string $reply_text
     * @param array $method_sig
     * @return mixed|null
     */
    public function close($reply_code = 0, $reply_text = '', $method_sig = array(0, 0))
    {
        $result = null;
        $this->io->disableHeartbeat();
        if (empty($this->protocolWriter) || !$this->isConnected()) {
            return $result;
        }

        try {
            $this->closeChannels();
            list($class_id, $method_id, $args) = $this->protocolWriter->connectionClose(
                $reply_code,
                $reply_text,
                $method_sig[0],
                $method_sig[1]
            );
            $this->send_method_frame(array($class_id, $method_id), $args);
            $result = $this->wait(
                array($this->waitHelper->get_wait('connection.close_ok')),
                false,
                $this->connection_timeout
            );
        } catch (\Exception $exception) {
            $this->do_close();
            throw $exception;
        }

        $this->setIsConnected(false);

        return $result;
    }

    /**
     * @param AMQPReader $reader
     * @throws \PhpAmqpLib\Exception\AMQPProtocolConnectionException
     */
    protected function connection_close(AMQPReader $reader)
    {
        $reply_code = $reader->read_short();
        $reply_text = $reader->read_shortstr();
        $class_id = $reader->read_short();
        $method_id = $reader->read_short();

        $this->x_close_ok();

        throw new AMQPProtocolConnectionException($reply_code, $reply_text, array($class_id, $method_id));
    }

    /**
     * Confirms a connection close
     */
    protected function x_close_ok()
    {
        $this->send_method_frame(
            explode(',', $this->waitHelper->get_wait('connection.close_ok'))
        );
        $this->do_close();
    }

    /**
     * Confirm a connection close
     *
     * @param AMQPReader $args
     */
    protected function connection_close_ok($args)
    {
        $this->do_close();
    }

    /**
     * @param string $virtual_host
     * @param string $capabilities
     * @param bool $insist
     * @return mixed
     */
    protected function x_open($virtual_host, $capabilities = '', $insist = false)
    {
        $args = new AMQPWriter();
        $args->write_shortstr($virtual_host);
        $args->write_shortstr($capabilities);
        $args->write_bits(array($insist));
        $this->send_method_frame(array(10, 40), $args);

        $wait = array(
            $this->waitHelper->get_wait('connection.open_ok')
        );

        if ($this->protocolVersion == '0.8') {
            $wait[] = $this->waitHelper->get_wait('connection.redirect');
        }

        return $this->wait($wait);
    }

    /**
     * Signals that the connection is ready
     *
     * @param AMQPReader $args
     */
    protected function connection_open_ok($args)
    {
        $this->known_hosts = $args->read_shortstr();
        $this->debug->debug_msg('Open OK! known_hosts: ' . $this->known_hosts);
    }

    /**
     * Asks the client to use a different server
     *
     * @param AMQPReader $args
     * @return string
     */
    protected function connection_redirect($args)
    {
        $host = $args->read_shortstr();
        $this->known_hosts = $args->read_shortstr();
        $this->debug->debug_msg(sprintf(
                'Redirected to [%s], known_hosts [%s]',
                $host,
                $this->known_hosts
            ));

        return $host;
    }

    /**
     * Security mechanism challenge
     *
     * @param AMQPReader $args
     */
    protected function connection_secure($args)
    {
        $challenge = $args->read_longstr();
    }

    /**
     * Security mechanism response
     *
     * @param string $response
     */
    protected function x_secure_ok($response)
    {
        $args = new AMQPWriter();
        $args->write_longstr($response);
        $this->send_method_frame(array(10, 21), $args);
    }

    /**
     * Starts connection negotiation
     *
     * @param AMQPReader $args
     */
    protected function connection_start($args)
    {
        $this->version_major = $args->read_octet();
        $this->version_minor = $args->read_octet();
        $this->server_properties = $args->read_table();
        $this->mechanisms = explode(' ', $args->read_longstr());
        $this->locales = explode(' ', $args->read_longstr());

        $this->debug->debug_connection_start(
            $this->version_major,
            $this->version_minor,
            $this->server_properties,
            $this->mechanisms,
            $this->locales
        );
    }

    /**
     * @param AMQPTable|array $clientProperties
     * @param string $mechanism
     * @param string $response
     * @param string $locale
     */
    protected function x_start_ok($clientProperties, $mechanism, $response, $locale)
    {
        $args = new AMQPWriter();
        $args->write_table($clientProperties);
        $args->write_shortstr($mechanism);
        $args->write_longstr($response);
        $args->write_shortstr($locale);
        $this->send_method_frame(array(10, 11), $args);
    }

    /**
     * Proposes connection tuning parameters
     *
     * @param AMQPReader $args
     */
    protected function connection_tune($args)
    {
        $v = $args->read_short();
        if ($v) {
            $this->channel_max = $v;
        }

        $v = $args->read_long();
        if ($v) {
            $this->frame_max = $v;
        }

        // use server proposed value if not set
        if ($this->heartbeat === null) {
            $this->heartbeat = $args->read_short();
        }

        $this->x_tune_ok($this->channel_max, $this->frame_max, $this->heartbeat);
    }

    /**
     * Negotiates connection tuning parameters
     *
     * @param int $channel_max
     * @param int $frame_max
     * @param int $heartbeat
     */
    protected function x_tune_ok($channel_max, $frame_max, $heartbeat)
    {
        $args = new AMQPWriter();
        $args->write_short($channel_max);
        $args->write_long($frame_max);
        $args->write_short($heartbeat);
        $this->send_method_frame(array(10, 31), $args);
        $this->wait_tune_ok = false;
    }

    /**
     * @return resource
     * @deprecated No direct access to communication socket should be available.
     */
    public function getSocket()
    {
        return $this->io->getSocket();
    }

    /**
     * @return \PhpAmqpLib\Wire\IO\AbstractIO
     * @deprecated
     */
    public function getIO()
    {
        return $this->io;
    }

    /**
     * Handles connection blocked notifications
     *
     * @param AMQPReader $args
     */
    protected function connection_blocked(AMQPReader $args)
    {
        // Call the block handler and pass in the reason
        $this->dispatch_to_handler($this->connection_block_handler, array($args->read_shortstr()));
    }

    /**
     * Handles connection unblocked notifications
     *
     * @param AMQPReader $args
     */
    protected function connection_unblocked(AMQPReader $args)
    {
        // No args to an unblock event
        $this->dispatch_to_handler($this->connection_unblock_handler, array());
    }

    /**
     * Sets a handler which is called whenever a connection.block is sent from the server
     *
     * @param callable $callback
     */
    public function set_connection_block_handler($callback)
    {
        $this->connection_block_handler = $callback;
    }

    /**
     * Sets a handler which is called whenever a connection.block is sent from the server
     *
     * @param callable $callback
     */
    public function set_connection_unblock_handler($callback)
    {
        $this->connection_unblock_handler = $callback;
    }

    /**
     * Gets the connection status
     *
     * @return bool
     */
    public function isConnected()
    {
        return (bool) $this->is_connected;
    }

    /**
     * Set the connection status
     *
     * @param bool $is_connected
     */
    protected function setIsConnected($is_connected)
    {
        $this->is_connected = (bool) $is_connected;
    }

    /**
     * Closes all available channels
     */
    protected function closeChannels()
    {
        foreach ($this->channels as $key => $channel) {
            // channels[0] is this connection object, so don't close it yet
            if ($key === 0) {
                continue;
            }
            try {
                $channel->close();
            } catch (\Exception $e) {
                /* Ignore closing errors */
            }
        }
    }

    /**
     * Should the connection be attempted during construction?
     *
     * @return bool
     */
    public function connectOnConstruct()
    {
        return true;
    }

    /**
     * @return array
     */
    public function getServerProperties()
    {
        return $this->server_properties;
    }

    /**
     * Get the library properties for populating the client protocol information
     *
     * @return array
     */
    public function getLibraryProperties()
    {
        return self::$LIBRARY_PROPERTIES;
    }

    public static function create_connection($hosts, $options = array()){
        $latest_exception = null;
        for($i = 0; $i < count($hosts); $i++) {
            AbstractConnection::validate_host($hosts[$i]);
            $host = $hosts[$i]['host'];
            $port = $hosts[$i]['port'];
            $user = $hosts[$i]['user'];
            $password = $hosts[$i]['password'];
            $vhost = isset($hosts[$i]['vhost']) ? $hosts[$i]['vhost'] : "/";
            try {
                $conn = static::try_create_connection($host, $port, $user, $password, $vhost, $options);
                return $conn;
            } catch (\Exception $e) {
                $latest_exception = $e;
            }
        }
        throw $latest_exception;
    }

    public static function validate_host($host) {
        if(!isset($host['host'])){
            throw new \InvalidArgumentException("'host' key is required.");
        }
        if(!isset($host['port'])){
            throw new \InvalidArgumentException("'port' key is required.");
        }
        if(!isset($host['user'])){
            throw new \InvalidArgumentException("'user' key is required.");
        }
        if(!isset($host['password'])){
            throw new \InvalidArgumentException("'password' key is required.");
        }
    }
}
<?php
namespace PhpAmqpLib\Connection;

/**
 * Class AMQPConnection
 *
 * Kept for BC
 *
 * @deprecated
 */
class AMQPConnection extends AMQPStreamConnection
{
}
<?php
namespace PhpAmqpLib\Connection;

use PhpAmqpLib\Wire\IO\StreamIO;

class AMQPStreamConnection extends AbstractConnection
{
    /**
     * @param string $host
     * @param string $port
     * @param string $user
     * @param string $password
     * @param string $vhost
     * @param bool $insist
     * @param string $login_method
     * @param null $login_response
     * @param string $locale
     * @param float $connection_timeout
     * @param float $read_write_timeout
     * @param null $context
     * @param bool $keepalive
     * @param int $heartbeat
     * @param float $channel_rpc_timeout
     */
    public function __construct(
        $host,
        $port,
        $user,
        $password,
        $vhost = '/',
        $insist = false,
        $login_method = 'AMQPLAIN',
        $login_response = null,
        $locale = 'en_US',
        $connection_timeout = 3.0,
        $read_write_timeout = 130.0,
        $context = null,
        $keepalive = false,
        $heartbeat = 60,
        $channel_rpc_timeout = 0.0,
        $ssl_protocol = null
    ) {
        if ($channel_rpc_timeout > $read_write_timeout) {
            throw new \InvalidArgumentException('channel RPC timeout must not be greater than I/O read-write timeout');
        }

        $io = new StreamIO(
            $host,
            $port,
            $connection_timeout,
            $read_write_timeout,
            $context,
            $keepalive,
            $heartbeat,
            $ssl_protocol
        );

        parent::__construct(
            $user,
            $password,
            $vhost,
            $insist,
            $login_method,
            $login_response,
            $locale,
            $io,
            $heartbeat,
            $connection_timeout,
            $channel_rpc_timeout
        );

        // save the params for the use of __clone, this will overwrite the parent
        $this->construct_params = func_get_args();
    }

    protected static function try_create_connection($host, $port, $user, $password, $vhost, $options){
        $insist = isset($options['insist']) ?
                        $options['insist'] : false;
        $login_method = isset($options['login_method']) ?
                              $options['login_method'] :'AMQPLAIN';
        $login_response = isset($options['login_response']) ?
                                $options['login_response'] : null;
        $locale = isset($options['locale']) ?
                        $options['locale'] : 'en_US';
        $connection_timeout = isset($options['connection_timeout']) ?
                                    $options['connection_timeout'] : 3.0;
        $read_write_timeout = isset($options['read_write_timeout']) ?
                                    $options['read_write_timeout'] : 130.0;
        $context = isset($options['context']) ?
                         $options['context'] : null;
        $keepalive = isset($options['keepalive']) ?
                           $options['keepalive'] : false;
        $heartbeat = isset($options['heartbeat']) ?
                           $options['heartbeat'] : 60;
        return new static($host,
                          $port,
                          $user,
                          $password,
                          $vhost,
                          $insist,
                          $login_method,
                          $login_response,
                          $locale,
                          $connection_timeout,
                          $read_write_timeout,
                          $context,
                          $keepalive,
                          $heartbeat);
    }
}
<?php
namespace PhpAmqpLib\Message;

use PhpAmqpLib\Wire\GenericContent;

/**
 * A Message for use with the Channnel.basic_* methods.
 */
class AMQPMessage extends GenericContent
{
    const DELIVERY_MODE_NON_PERSISTENT = 1;
    const DELIVERY_MODE_PERSISTENT = 2;

    /** @var string */
    public $body;

    /** @var int */
    public $body_size;

    /** @var bool */
    public $is_truncated = false;

    /** @var string */
    public $content_encoding;

    /** @var array */
    protected static $propertyDefinitions = array(
        'content_type' => 'shortstr',
        'content_encoding' => 'shortstr',
        'application_headers' => 'table_object',
        'delivery_mode' => 'octet',
        'priority' => 'octet',
        'correlation_id' => 'shortstr',
        'reply_to' => 'shortstr',
        'expiration' => 'shortstr',
        'message_id' => 'shortstr',
        'timestamp' => 'timestamp',
        'type' => 'shortstr',
        'user_id' => 'shortstr',
        'app_id' => 'shortstr',
        'cluster_id' => 'shortstr',
    );

    /**
     * @param string $body
     * @param array $properties
     */
    public function __construct($body = '', $properties = array())
    {
        $this->setBody($body);
        parent::__construct($properties, static::$propertyDefinitions);
    }

    /**
     * @return string
     */
    public function getBody()
    {
        return $this->body;
    }

    /**
     * Sets the message payload
     *
     * @param string $body
     * @return $this
     */
    public function setBody($body)
    {
        $this->body = $body;

        return $this;
    }

    /**
     * @return string
     */
    public function getContentEncoding()
    {
        return $this->content_encoding;
    }

    /**
     * @return int
     */
    public function getBodySize()
    {
        return $this->body_size;
    }

    /**
     * @param int $body_size Message body size in byte(s)
     * @return AMQPMessage
     */
    public function setBodySize($body_size)
    {
        $this->body_size = (int) $body_size;

        return $this;
    }

    /**
     * @return boolean
     */
    public function isTruncated()
    {
        return $this->is_truncated;
    }

    /**
     * @param bool $is_truncated
     * @return AMQPMessage
     */
    public function setIsTruncated($is_truncated)
    {
        $this->is_truncated = (bool) $is_truncated;

        return $this;
    }
}
<?php
namespace PhpAmqpLib\Channel;

use PhpAmqpLib\Exception\AMQPBasicCancelException;
use PhpAmqpLib\Exception\AMQPConnectionClosedException;
use PhpAmqpLib\Exception\AMQPProtocolChannelException;
use PhpAmqpLib\Exception\AMQPRuntimeException;
use PhpAmqpLib\Message\AMQPMessage;
use PhpAmqpLib\Wire\AMQPReader;
use PhpAmqpLib\Wire\AMQPWriter;

class AMQPChannel extends AbstractChannel
{
    /**
     * @var callable[]
     * @internal Use is_consuming() to check if there is active callbacks
     */
    public $callbacks = array();

    /** @var bool Whether or not the channel has been "opened" */
    protected $is_open = false;

    /** @var int */
    protected $default_ticket = 0;

    /** @var bool */
    protected $active = true;

    /** @var array */
    protected $alerts = array();

    /** @var bool */
    protected $auto_decode;

    /**
     * These parameters will be passed to function in case of basic_return:
     *    param int $reply_code
     *    param string $reply_text
     *    param string $exchange
     *    param string $routing_key
     *    param AMQPMessage $msg
     *
     * @var callable
     */
    protected $basic_return_callback;

    /** @var array Used to keep track of the messages that are going to be batch published. */
    protected $batch_messages = array();

    /**
     * If the channel is in confirm_publish mode this array will store all published messages
     * until they get ack'ed or nack'ed
     *
     * @var AMQPMessage[]
     */
    private $published_messages = array();

    /** @var int */
    private $next_delivery_tag = 0;

    /** @var callable */
    private $ack_handler;

    /** @var callable */
    private $nack_handler;

    /**
     * Circular buffer to speed up both basic_publish() and publish_batch().
     * Max size limited by $publish_cache_max_size.
     *
     * @var array
     * @see basic_publish()
     * @see publish_batch()
     */
    private $publish_cache = array();

    /**
     * Maximal size of $publish_cache
     *
     * @var int
     */
    private $publish_cache_max_size = 100;

    /**
     * Maximum time to wait for operations on this channel, in seconds.
     * @var float $channel_rpc_timeout
     */
    private $channel_rpc_timeout;

    /**
     * @param \PhpAmqpLib\Connection\AbstractConnection $connection
     * @param null $channel_id
     * @param bool $auto_decode
     * @param int $channel_rpc_timeout
     * @throws \Exception
     */
    public function __construct($connection, $channel_id = null, $auto_decode = true, $channel_rpc_timeout = 0)
    {
        if ($channel_id == null) {
            $channel_id = $connection->get_free_channel_id();
        }

        parent::__construct($connection, $channel_id);

        $this->debug->debug_msg('using channel_id: ' . $channel_id);

        $this->auto_decode = $auto_decode;
        $this->channel_rpc_timeout = $channel_rpc_timeout;

        try {
            $this->x_open();
        } catch (\Exception $e) {
            $this->close();
            throw $e;
        }
    }

    /**
     * @return bool
     */
    public function is_open()
    {
        return $this->is_open;
    }

    /**
     * Tear down this object, after we've agreed to close with the server.
     */
    protected function do_close()
    {
        if ($this->channel_id !== null) {
            unset($this->connection->channels[$this->channel_id]);
        }
        $this->channel_id = $this->connection = null;
        $this->is_open = false;
        $this->callbacks = array();
    }

    /**
     * Only for AMQP0.8.0
     * This method allows the server to send a non-fatal warning to
     * the client.  This is used for methods that are normally
     * asynchronous and thus do not have confirmations, and for which
     * the server may detect errors that need to be reported.  Fatal
     * errors are handled as channel or connection exceptions; non-
     * fatal errors are sent through this method.
     *
     * @param AMQPReader $reader
     */
    protected function channel_alert($reader)
    {
        $reply_code = $reader->read_short();
        $reply_text = $reader->read_shortstr();
        $details = $reader->read_table();
        array_push($this->alerts, array($reply_code, $reply_text, $details));
    }

    /**
     * Request a channel close
     *
     * @param int $reply_code
     * @param string $reply_text
     * @param array $method_sig
     * @throws \PhpAmqpLib\Exception\AMQPTimeoutException if the specified operation timeout was exceeded
     * @return mixed
     */
    public function close($reply_code = 0, $reply_text = '', $method_sig = array(0, 0))
    {
        $this->callbacks = array();
        if ($this->is_open === false || $this->connection === null) {
            $this->do_close();

            return null; // already closed
        }
        list($class_id, $method_id, $args) = $this->protocolWriter->channelClose(
            $reply_code,
            $reply_text,
            $method_sig[0],
            $method_sig[1]
        );

        try {
            $this->send_method_frame(array($class_id, $method_id), $args);
        } catch (\Exception $e) {
            $this->do_close();

            throw $e;
        }

        return $this->wait(array(
            $this->waitHelper->get_wait('channel.close_ok')
        ), false, $this->channel_rpc_timeout );
    }

    /**
     * @param AMQPReader $reader
     * @throws \PhpAmqpLib\Exception\AMQPProtocolChannelException
     */
    protected function channel_close($reader)
    {
        $reply_code = $reader->read_short();
        $reply_text = $reader->read_shortstr();
        $class_id = $reader->read_short();
        $method_id = $reader->read_short();

        $this->send_method_frame(array(20, 41));
        $this->do_close();

        throw new AMQPProtocolChannelException($reply_code, $reply_text, array($class_id, $method_id));
    }

    /**
     * Confirm a channel close
     * Alias of AMQPChannel::do_close()
     *
     * @param AMQPReader $reader
     */
    protected function channel_close_ok($reader)
    {
        $this->do_close();
    }

    /**
     * Enables/disables flow from peer
     *
     * @param $active
     * @throws \PhpAmqpLib\Exception\AMQPTimeoutException if the specified operation timeout was exceeded
     * @return mixed
     */
    public function flow($active)
    {
        list($class_id, $method_id, $args) = $this->protocolWriter->channelFlow($active);
        $this->send_method_frame(array($class_id, $method_id), $args);

        return $this->wait(array(
            $this->waitHelper->get_wait('channel.flow_ok')
        ), false, $this->channel_rpc_timeout);
    }

    /**
     * @param AMQPReader $reader
     */
    protected function channel_flow($reader)
    {
        $this->active = $reader->read_bit();
        $this->x_flow_ok($this->active);
    }

    /**
     * @param bool $active
     */
    protected function x_flow_ok($active)
    {
        list($class_id, $method_id, $args) = $this->protocolWriter->channelFlow($active);
        $this->send_method_frame(array($class_id, $method_id), $args);
    }

    /**
     * @param AMQPReader $reader
     * @return bool
     */
    protected function channel_flow_ok($reader)
    {
        return $reader->read_bit();
    }

    /**
     * @param string $out_of_band
     * @throws \PhpAmqpLib\Exception\AMQPTimeoutException if the specified operation timeout was exceeded
     * @return mixed
     */
    protected function x_open($out_of_band = '')
    {
        if ($this->is_open) {
            return null;
        }

        list($class_id, $method_id, $args) = $this->protocolWriter->channelOpen($out_of_band);
        $this->send_method_frame(array($class_id, $method_id), $args);

        return $this->wait(array(
            $this->waitHelper->get_wait('channel.open_ok')
        ), false, $this->channel_rpc_timeout);
    }

    /**
     * @param AMQPReader $reader
     */
    protected function channel_open_ok($reader)
    {
        $this->is_open = true;

        $this->debug->debug_msg('Channel open');
    }

    /**
     * Requests an access ticket
     *
     * @param string $realm
     * @param bool $exclusive
     * @param bool $passive
     * @param bool $active
     * @param bool $write
     * @param bool $read
     * @throws \PhpAmqpLib\Exception\AMQPTimeoutException if the specified operation timeout was exceeded
     * @return mixed
     */
    public function access_request(
        $realm,
        $exclusive = false,
        $passive = false,
        $active = false,
        $write = false,
        $read = false
    ) {
        list($class_id, $method_id, $args) = $this->protocolWriter->accessRequest(
            $realm,
            $exclusive,
            $passive,
            $active,
            $write,
            $read
        );

        $this->send_method_frame(array($class_id, $method_id), $args);

        return $this->wait(array(
            $this->waitHelper->get_wait('access.request_ok')
        ), false, $this->channel_rpc_timeout);
    }

    /**
     * Grants access to server resources
     *
     * @param AMQPReader $reader
     * @return string
     */
    protected function access_request_ok($reader)
    {
        $this->default_ticket = $reader->read_short();

        return $this->default_ticket;
    }

    /**
     * Declares exchange
     *
     * @param string $exchange
     * @param string $type
     * @param bool $passive
     * @param bool $durable
     * @param bool $auto_delete
     * @param bool $internal
     * @param bool $nowait
     * @param array $arguments
     * @param int $ticket
     * @throws \PhpAmqpLib\Exception\AMQPTimeoutException if the specified operation timeout was exceeded
     * @return mixed|null
     */
    public function exchange_declare(
        $exchange,
        $type,
        $passive = false,
        $durable = false,
        $auto_delete = true,
        $internal = false,
        $nowait = false,
        $arguments = array(),
        $ticket = null
    ) {
        $ticket = $this->getTicket($ticket);

        list($class_id, $method_id, $args) = $this->protocolWriter->exchangeDeclare(
            $ticket,
            $exchange,
            $type,
            $passive,
            $durable,
            $auto_delete,
            $internal,
            $nowait,
            $arguments
        );

        $this->send_method_frame(array($class_id, $method_id), $args);

        if ($nowait) {
            return null;
        }

        return $this->wait(array(
            $this->waitHelper->get_wait('exchange.declare_ok')
        ), false, $this->channel_rpc_timeout);
    }

    /**
     * Confirms an exchange declaration
     * @param AMQPReader $reader
     */
    protected function exchange_declare_ok($reader)
    {
    }

    /**
     * Deletes an exchange
     *
     * @param string $exchange
     * @param bool $if_unused
     * @param bool $nowait
     * @param null $ticket
     * @throws \PhpAmqpLib\Exception\AMQPTimeoutException if the specified operation timeout was exceeded
     * @return mixed|null
     */
    public function exchange_delete(
        $exchange,
        $if_unused = false,
        $nowait = false,
        $ticket = null
    ) {
        $ticket = $this->getTicket($ticket);
        list($class_id, $method_id, $args) = $this->protocolWriter->exchangeDelete(
            $ticket,
            $exchange,
            $if_unused,
            $nowait
        );

        $this->send_method_frame(array($class_id, $method_id), $args);

        if ($nowait) {
            return null;
        }

        return $this->wait(array(
            $this->waitHelper->get_wait('exchange.delete_ok')
        ), false, $this->channel_rpc_timeout);
    }

    /**
     * Confirms deletion of an exchange
     *
     * @param AMQPReader $reader
     */
    protected function exchange_delete_ok($reader)
    {
    }

    /**
     * Binds dest exchange to source exchange
     *
     * @param string $destination
     * @param string $source
     * @param string $routing_key
     * @param bool $nowait
     * @param array $arguments
     * @param int $ticket
     * @throws \PhpAmqpLib\Exception\AMQPTimeoutException if the specified operation timeout was exceeded
     * @return mixed|null
     */
    public function exchange_bind(
        $destination,
        $source,
        $routing_key = '',
        $nowait = false,
        $arguments = array(),
        $ticket = null
    ) {
        $ticket = $this->getTicket($ticket);

        list($class_id, $method_id, $args) = $this->protocolWriter->exchangeBind(
            $ticket,
            $destination,
            $source,
            $routing_key,
            $nowait,
            $arguments
        );

        $this->send_method_frame(array($class_id, $method_id), $args);

        if ($nowait) {
            return null;
        }

        return $this->wait(array(
            $this->waitHelper->get_wait('exchange.bind_ok')
        ), false, $this->channel_rpc_timeout);
    }

    /**
     * Confirms bind successful
     * @param AMQPReader $reader
     */
    protected function exchange_bind_ok($reader)
    {
    }

    /**
     * Unbinds dest exchange from source exchange
     *
     * @param string $destination
     * @param string $source
     * @param string $routing_key
     * @param bool $nowait
     * @param array $arguments
     * @param int $ticket
     * @throws \PhpAmqpLib\Exception\AMQPTimeoutException if the specified operation timeout was exceeded
     * @return mixed
     */
    public function exchange_unbind(
        $destination,
        $source,
        $routing_key = '',
        $nowait = false,
        $arguments = array(),
        $ticket = null
    ) {
        $ticket = $this->getTicket($ticket);

        list($class_id, $method_id, $args) = $this->protocolWriter->exchangeUnbind(
            $ticket,
            $destination,
            $source,
            $routing_key,
            $nowait,
            $arguments
        );

        $this->send_method_frame(array($class_id, $method_id), $args);

        return $this->wait(array(
            $this->waitHelper->get_wait('exchange.unbind_ok')
        ), false, $this->channel_rpc_timeout);
    }

    /**
     * Confirms unbind successful
     *
     * @param AMQPReader $reader
     */
    protected function exchange_unbind_ok($reader)
    {
    }

    /**
     * Binds queue to an exchange
     *
     * @param string $queue
     * @param string $exchange
     * @param string $routing_key
     * @param bool $nowait
     * @param array $arguments
     * @param int $ticket
     * @throws \PhpAmqpLib\Exception\AMQPTimeoutException if the specified operation timeout was exceeded
     * @return mixed|null
     */
    public function queue_bind(
        $queue,
        $exchange,
        $routing_key = '',
        $nowait = false,
        $arguments = array(),
        $ticket = null
    ) {
        $ticket = $this->getTicket($ticket);

        list($class_id, $method_id, $args) = $this->protocolWriter->queueBind(
            $ticket,
            $queue,
            $exchange,
            $routing_key,
            $nowait,
            $arguments
        );

        $this->send_method_frame(array($class_id, $method_id), $args);

        if ($nowait) {
            return null;
        }

        return $this->wait(array(
            $this->waitHelper->get_wait('queue.bind_ok')
        ), false, $this->channel_rpc_timeout);
    }

    /**
     * Confirms bind successful
     *
     * @param AMQPReader $reader
     */
    protected function queue_bind_ok($reader)
    {
    }

    /**
     * Unbind queue from an exchange
     *
     * @param string $queue
     * @param string $exchange
     * @param string $routing_key
     * @param array $arguments
     * @param int $ticket
     * @throws \PhpAmqpLib\Exception\AMQPTimeoutException if the specified operation timeout was exceeded
     * @return mixed
     */
    public function queue_unbind(
        $queue,
        $exchange,
        $routing_key = '',
        $arguments = array(),
        $ticket = null
    ) {
        $ticket = $this->getTicket($ticket);

        list($class_id, $method_id, $args) = $this->protocolWriter->queueUnbind(
            $ticket,
            $queue,
            $exchange,
            $routing_key,
            $arguments
        );

        $this->send_method_frame(array($class_id, $method_id), $args);

        return $this->wait(array(
            $this->waitHelper->get_wait('queue.unbind_ok')
        ), false, $this->channel_rpc_timeout);
    }

    /**
     * Confirms unbind successful
     * @param AMQPReader $reader
     */
    protected function queue_unbind_ok($reader)
    {
    }

    /**
     * Declares queue, creates if needed
     *
     * @param string $queue
     * @param bool $passive
     * @param bool $durable
     * @param bool $exclusive
     * @param bool $auto_delete
     * @param bool $nowait
     * @param array $arguments
     * @param int $ticket
     * @throws \PhpAmqpLib\Exception\AMQPTimeoutException if the specified operation timeout was exceeded
     * @return mixed|null
     */
    public function queue_declare(
        $queue = '',
        $passive = false,
        $durable = false,
        $exclusive = false,
        $auto_delete = true,
        $nowait = false,
        $arguments = array(),
        $ticket = null
    ) {
        $ticket = $this->getTicket($ticket);

        list($class_id, $method_id, $args) = $this->protocolWriter->queueDeclare(
            $ticket,
            $queue,
            $passive,
            $durable,
            $exclusive,
            $auto_delete,
            $nowait,
            $arguments
        );

        $this->send_method_frame(array($class_id, $method_id), $args);

        if ($nowait) {
            return null;
        }

        return $this->wait(array(
            $this->waitHelper->get_wait('queue.declare_ok')
        ), false, $this->channel_rpc_timeout);
    }

    /**
     * Confirms a queue definition
     *
     * @param AMQPReader $reader
     * @return string[]
     */
    protected function queue_declare_ok($reader)
    {
        $queue = $reader->read_shortstr();
        $message_count = $reader->read_long();
        $consumer_count = $reader->read_long();

        return array($queue, $message_count, $consumer_count);
    }

    /**
     * Deletes a queue
     *
     * @param string $queue
     * @param bool $if_unused
     * @param bool $if_empty
     * @param bool $nowait
     * @param int $ticket
     * @throws \PhpAmqpLib\Exception\AMQPTimeoutException if the specified operation timeout was exceeded
     * @return mixed|null
     */
    public function queue_delete($queue = '', $if_unused = false, $if_empty = false, $nowait = false, $ticket = null)
    {
        $ticket = $this->getTicket($ticket);

        list($class_id, $method_id, $args) = $this->protocolWriter->queueDelete(
            $ticket,
            $queue,
            $if_unused,
            $if_empty,
            $nowait
        );

        $this->send_method_frame(array($class_id, $method_id), $args);

        if ($nowait) {
            return null;
        }

        return $this->wait(array(
            $this->waitHelper->get_wait('queue.delete_ok')
        ), false, $this->channel_rpc_timeout);
    }

    /**
     * Confirms deletion of a queue
     *
     * @param AMQPReader $reader
     * @return string
     */
    protected function queue_delete_ok($reader)
    {
        return $reader->read_long();
    }

    /**
     * Purges a queue
     *
     * @param string $queue
     * @param bool $nowait
     * @param int $ticket
     * @throws \PhpAmqpLib\Exception\AMQPTimeoutException if the specified operation timeout was exceeded
     * @return mixed|null
     */
    public function queue_purge($queue = '', $nowait = false, $ticket = null)
    {
        $ticket = $this->getTicket($ticket);
        list($class_id, $method_id, $args) = $this->protocolWriter->queuePurge($ticket, $queue, $nowait);

        $this->send_method_frame(array($class_id, $method_id), $args);

        if ($nowait) {
            return null;
        }

        return $this->wait(array(
            $this->waitHelper->get_wait('queue.purge_ok')
        ), false, $this->channel_rpc_timeout);
    }

    /**
     * Confirms a queue purge
     *
     * @param AMQPReader $reader
     * @return string
     */
    protected function queue_purge_ok($reader)
    {
        return $reader->read_long();
    }

    /**
     * Acknowledges one or more messages
     *
     * @param string $delivery_tag
     * @param bool $multiple
     */
    public function basic_ack($delivery_tag, $multiple = false)
    {
        list($class_id, $method_id, $args) = $this->protocolWriter->basicAck($delivery_tag, $multiple);
        $this->send_method_frame(array($class_id, $method_id), $args);
    }

    /**
     * Called when the server sends a basic.ack
     *
     * @param AMQPReader $reader
     * @throws AMQPRuntimeException
     */
    protected function basic_ack_from_server(AMQPReader $reader)
    {
        $delivery_tag = $reader->read_longlong();
        $multiple = (bool) $reader->read_bit();

        if (!isset($this->published_messages[$delivery_tag])) {
            throw new AMQPRuntimeException(sprintf(
                'Server ack\'ed unknown delivery_tag "%s"',
                $delivery_tag
            ));
        }

        $this->internal_ack_handler($delivery_tag, $multiple, $this->ack_handler);
    }

    /**
     * Called when the server sends a basic.nack
     *
     * @param AMQPReader $reader
     * @throws AMQPRuntimeException
     */
    protected function basic_nack_from_server($reader)
    {
        $delivery_tag = $reader->read_longlong();
        $multiple = (bool) $reader->read_bit();

        if (!isset($this->published_messages[$delivery_tag])) {
            throw new AMQPRuntimeException(sprintf(
                'Server nack\'ed unknown delivery_tag "%s"',
                $delivery_tag
            ));
        }

        $this->internal_ack_handler($delivery_tag, $multiple, $this->nack_handler);
    }

    /**
     * Handles the deletion of messages from this->publishedMessages and dispatches them to the $handler
     *
     * @param string $delivery_tag
     * @param bool $multiple
     * @param callable $handler
     */
    protected function internal_ack_handler($delivery_tag, $multiple, $handler)
    {
        if ($multiple) {
            $keys = $this->get_keys_less_or_equal($this->published_messages, $delivery_tag);

            foreach ($keys as $key) {
                $this->internal_ack_handler($key, false, $handler);
            }

        } else {
            $message = $this->get_and_unset_message($delivery_tag);
            $message->delivery_info['delivery_tag'] = $delivery_tag;
            $this->dispatch_to_handler($handler, array($message));
        }
    }

    /**
     * @param AMQPMessage[] $messages
     * @param string $value
     * @return mixed
     */
    protected function get_keys_less_or_equal(array $messages, $value)
    {
        $keys = array_reduce(
            array_keys($messages),

            /**
             * @param string $key
             */
            function ($keys, $key) use ($value) {
                if (bccomp($key, $value, 0) <= 0) {
                    $keys[] = $key;
                }

                return $keys;
            },
            array()
        );

        return $keys;
    }

    /**
     * Rejects one or several received messages
     *
     * @param string $delivery_tag
     * @param bool $multiple
     * @param bool $requeue
     */
    public function basic_nack($delivery_tag, $multiple = false, $requeue = false)
    {
        list($class_id, $method_id, $args) = $this->protocolWriter->basicNack($delivery_tag, $multiple, $requeue);
        $this->send_method_frame(array($class_id, $method_id), $args);
    }

    /**
     * Ends a queue consumer
     *
     * @param string $consumer_tag
     * @param bool $nowait
     * @param bool $noreturn
     * @throws \PhpAmqpLib\Exception\AMQPTimeoutException if the specified operation timeout was exceeded
     * @return mixed
     */
    public function basic_cancel($consumer_tag, $nowait = false, $noreturn = false)
    {
        list($class_id, $method_id, $args) = $this->protocolWriter->basicCancel($consumer_tag, $nowait);
        $this->send_method_frame(array($class_id, $method_id), $args);

        if ($nowait || $noreturn) {
            unset($this->callbacks[$consumer_tag]);
            return $consumer_tag;
        }

        return $this->wait(array(
            $this->waitHelper->get_wait('basic.cancel_ok')
        ), false, $this->channel_rpc_timeout);
    }

    /**
     * @param AMQPReader $reader
     * @throws \PhpAmqpLib\Exception\AMQPBasicCancelException
     */
    protected function basic_cancel_from_server(AMQPReader $reader)
    {
        throw new AMQPBasicCancelException($reader->read_shortstr());
    }

    /**
     * Confirm a cancelled consumer
     *
     * @param AMQPReader $reader
     * @return string
     */
    protected function basic_cancel_ok($reader)
    {
        $consumerTag = $reader->read_shortstr();
        unset($this->callbacks[$consumerTag]);

        return $consumerTag;
    }

    /**
     * @return bool
     */
    public function is_consuming()
    {
        return !empty($this->callbacks);
    }

    /**
     * Starts a queue consumer
     *
     * @param string $queue
     * @param string $consumer_tag
     * @param bool $no_local
     * @param bool $no_ack
     * @param bool $exclusive
     * @param bool $nowait
     * @param callable|null $callback
     * @param int|null $ticket
     * @param array $arguments
     * @throws \PhpAmqpLib\Exception\AMQPTimeoutException if the specified operation timeout was exceeded
     * @return mixed|string
     */
    public function basic_consume(
        $queue = '',
        $consumer_tag = '',
        $no_local = false,
        $no_ack = false,
        $exclusive = false,
        $nowait = false,
        $callback = null,
        $ticket = null,
        $arguments = array()
    ) {
        $ticket = $this->getTicket($ticket);
        list($class_id, $method_id, $args) = $this->protocolWriter->basicConsume(
            $ticket,
            $queue,
            $consumer_tag,
            $no_local,
            $no_ack,
            $exclusive,
            $nowait,
            $this->protocolVersion == '0.9.1' ? $arguments : null
        );

        $this->send_method_frame(array($class_id, $method_id), $args);

        if (false === $nowait) {
            $consumer_tag = $this->wait(array(
                $this->waitHelper->get_wait('basic.consume_ok')
            ), false, $this->channel_rpc_timeout);
        }

        $this->callbacks[$consumer_tag] = $callback;

        return $consumer_tag;
    }

    /**
     * Confirms a new consumer
     *
     * @param AMQPReader $reader
     * @return string
     */
    protected function basic_consume_ok($reader)
    {
        return $reader->read_shortstr();
    }

    /**
     * Notifies the client of a consumer message
     *
     * @param AMQPReader $reader
     * @param AMQPMessage $message
     */
    protected function basic_deliver($reader, $message)
    {
        $consumer_tag = $reader->read_shortstr();
        $delivery_tag = $reader->read_longlong();
        $redelivered = $reader->read_bit();
        $exchange = $reader->read_shortstr();
        $routing_key = $reader->read_shortstr();

        $message->delivery_info = array(
            'channel' => $this,
            'consumer_tag' => $consumer_tag,
            'delivery_tag' => $delivery_tag,
            'redelivered' => $redelivered,
            'exchange' => $exchange,
            'routing_key' => $routing_key
        );

        if (isset($this->callbacks[$consumer_tag])) {
            call_user_func($this->callbacks[$consumer_tag], $message);
        }
    }

    /**
     * Direct access to a queue if no message was available in the queue, return null
     *
     * @param string $queue
     * @param bool $no_ack
     * @param int $ticket
     * @throws \PhpAmqpLib\Exception\AMQPTimeoutException if the specified operation timeout was exceeded
     * @return mixed
     */
    public function basic_get($queue = '', $no_ack = false, $ticket = null)
    {
        $ticket = $this->getTicket($ticket);
        list($class_id, $method_id, $args) = $this->protocolWriter->basicGet($ticket, $queue, $no_ack);

        $this->send_method_frame(array($class_id, $method_id), $args);

        return $this->wait(array(
            $this->waitHelper->get_wait('basic.get_ok'),
            $this->waitHelper->get_wait('basic.get_empty')
        ), false, $this->channel_rpc_timeout);
    }

    /**
     * Indicates no messages available
     *
     * @param AMQPReader $reader
     */
    protected function basic_get_empty($reader)
    {
    }

    /**
     * Provides client with a message
     *
     * @param AMQPReader $reader
     * @param AMQPMessage $message
     * @return AMQPMessage
     */
    protected function basic_get_ok($reader, $message)
    {
        $delivery_tag = $reader->read_longlong();
        $redelivered = $reader->read_bit();
        $exchange = $reader->read_shortstr();
        $routing_key = $reader->read_shortstr();
        $message_count = $reader->read_long();

        $message->delivery_info = array(
            'delivery_tag' => $delivery_tag,
            'redelivered' => $redelivered,
            'exchange' => $exchange,
            'routing_key' => $routing_key,
            'message_count' => $message_count
        );

        return $message;
    }

    /**
     * @param string $exchange
     * @param string $routing_key
     * @param $mandatory
     * @param $immediate
     * @param int $ticket
     * @return mixed
     */
    private function pre_publish($exchange, $routing_key, $mandatory, $immediate, $ticket)
    {
        $cache_key = sprintf(
            '%s|%s|%s|%s|%s',
            $exchange,
            $routing_key,
            $mandatory,
            $immediate,
            $ticket
        );
        if (false === isset($this->publish_cache[$cache_key])) {
            $ticket = $this->getTicket($ticket);
            list($class_id, $method_id, $args) = $this->protocolWriter->basicPublish(
                $ticket,
                $exchange,
                $routing_key,
                $mandatory,
                $immediate
            );

            $pkt = $this->prepare_method_frame(array($class_id, $method_id), $args);
            $this->publish_cache[$cache_key] = $pkt->getvalue();
            if (count($this->publish_cache) > $this->publish_cache_max_size) {
                reset($this->publish_cache);
                $old_key = key($this->publish_cache);
                unset($this->publish_cache[$old_key]);
            }
        }

        return $this->publish_cache[$cache_key];
    }

    /**
     * Publishes a message
     *
     * @param AMQPMessage $msg
     * @param string $exchange
     * @param string $routing_key
     * @param bool $mandatory
     * @param bool $immediate
     * @param int $ticket
     */
    public function basic_publish(
        $msg,
        $exchange = '',
        $routing_key = '',
        $mandatory = false,
        $immediate = false,
        $ticket = null
    ) {
        $pkt = new AMQPWriter();
        $pkt->write($this->pre_publish($exchange, $routing_key, $mandatory, $immediate, $ticket));

        try {
            $this->connection->send_content(
                $this->channel_id,
                60,
                0,
                mb_strlen($msg->body, 'ASCII'),
                $msg->serialize_properties(),
                $msg->body,
                $pkt
            );
        } catch (AMQPConnectionClosedException $e) {
            $this->do_close();
            throw $e;
        }

        if ($this->next_delivery_tag > 0) {
            $this->published_messages[$this->next_delivery_tag] = $msg;
            $this->next_delivery_tag = bcadd($this->next_delivery_tag, '1', 0);
        }
    }

    /**
     * @param AMQPMessage $msg
     * @param string $exchange
     * @param string $routing_key
     * @param bool $mandatory
     * @param bool $immediate
     * @param int $ticket
     */
    public function batch_basic_publish(
        $msg,
        $exchange = '',
        $routing_key = '',
        $mandatory = false,
        $immediate = false,
        $ticket = null
    ) {
        $this->batch_messages[] = func_get_args();
    }

    /**
     * Publish batch
     *
     * @return void
     */
    public function publish_batch()
    {
        if (empty($this->batch_messages)) {
            return null;
        }

        /** @var AMQPWriter $pkt */
        $pkt = new AMQPWriter();

        /** @var AMQPMessage $msg */
        foreach ($this->batch_messages as $m) {
            $msg = $m[0];

            $exchange = isset($m[1]) ? $m[1] : '';
            $routing_key = isset($m[2]) ? $m[2] : '';
            $mandatory = isset($m[3]) ? $m[3] : false;
            $immediate = isset($m[4]) ? $m[4] : false;
            $ticket = isset($m[5]) ? $m[5] : null;
            $pkt->write($this->pre_publish($exchange, $routing_key, $mandatory, $immediate, $ticket));

            $this->connection->prepare_content(
                $this->channel_id,
                60,
                0,
                mb_strlen($msg->body, 'ASCII'),
                $msg->serialize_properties(),
                $msg->body,
                $pkt
            );

            if ($this->next_delivery_tag > 0) {
                $this->published_messages[$this->next_delivery_tag] = $msg;
                $this->next_delivery_tag = bcadd($this->next_delivery_tag, '1', 0);
            }
        }

        //call write here
        $this->connection->write($pkt->getvalue());
        $this->batch_messages = array();
    }

    /**
     * Specifies QoS
     *
     * @param int $prefetch_size
     * @param int $prefetch_count
     * @param bool $a_global
     * @throws \PhpAmqpLib\Exception\AMQPTimeoutException if the specified operation timeout was exceeded
     * @return mixed
     */
    public function basic_qos($prefetch_size, $prefetch_count, $a_global)
    {
        list($class_id, $method_id, $args) = $this->protocolWriter->basicQos(
            $prefetch_size,
            $prefetch_count,
            $a_global
        );

        $this->send_method_frame(array($class_id, $method_id), $args);

        return $this->wait(array(
            $this->waitHelper->get_wait('basic.qos_ok')
        ), false, $this->channel_rpc_timeout);
    }

    /**
     * Confirms QoS request
     * @param AMQPReader $reader
     */
    protected function basic_qos_ok($reader)
    {
    }

    /**
     * Redelivers unacknowledged messages
     *
     * @param bool $requeue
     * @throws \PhpAmqpLib\Exception\AMQPTimeoutException if the specified operation timeout was exceeded
     * @return mixed
     */
    public function basic_recover($requeue = false)
    {
        list($class_id, $method_id, $args) = $this->protocolWriter->basicRecover($requeue);
        $this->send_method_frame(array($class_id, $method_id), $args);

        return $this->wait(array(
            $this->waitHelper->get_wait('basic.recover_ok')
        ), false, $this->channel_rpc_timeout);
    }

    /**
     * Confirm the requested recover
     * @param AMQPReader $reader
     */
    protected function basic_recover_ok($reader)
    {
    }

    /**
     * Rejects an incoming message
     *
     * @param string $delivery_tag
     * @param bool $requeue
     */
    public function basic_reject($delivery_tag, $requeue)
    {
        list($class_id, $method_id, $args) = $this->protocolWriter->basicReject($delivery_tag, $requeue);
        $this->send_method_frame(array($class_id, $method_id), $args);
    }

    /**
     * Returns a failed message
     *
     * @param AMQPReader $reader
     * @param AMQPMessage $message
     * @return null
     */
    protected function basic_return($reader, $message)
    {
        $callback = $this->basic_return_callback;
        if (!is_callable($callback)) {
            $this->debug->debug_msg('Skipping unhandled basic_return message');
            return null;
        }

        $reply_code = $reader->read_short();
        $reply_text = $reader->read_shortstr();
        $exchange = $reader->read_shortstr();
        $routing_key = $reader->read_shortstr();

        call_user_func_array($callback, array(
            $reply_code,
            $reply_text,
            $exchange,
            $routing_key,
            $message,
        ));
    }

    /**
     * @throws \PhpAmqpLib\Exception\AMQPTimeoutException if the specified operation timeout was exceeded
     * @return mixed
     */
    public function tx_commit()
    {
        $this->send_method_frame(array(90, 20));

        return $this->wait(array(
            $this->waitHelper->get_wait('tx.commit_ok')
        ), false, $this->channel_rpc_timeout);
    }

    /**
     * Confirms a successful commit
     * @param AMQPReader $reader
     */
    protected function tx_commit_ok($reader)
    {
    }

    /**
     * Rollbacks the current transaction
     *
     * @throws \PhpAmqpLib\Exception\AMQPTimeoutException if the specified operation timeout was exceeded
     * @return mixed
     */
    public function tx_rollback()
    {
        $this->send_method_frame(array(90, 30));

        return $this->wait(array(
            $this->waitHelper->get_wait('tx.rollback_ok')
        ), false, $this->channel_rpc_timeout);
    }

    /**
     * Confirms a successful rollback
     *
     * @param AMQPReader $reader
     */
    protected function tx_rollback_ok($reader)
    {
    }

    /**
     * Puts the channel into confirm mode
     * Beware that only non-transactional channels may be put into confirm mode and vice versa
     *
     * @param bool $nowait
     * @throws \PhpAmqpLib\Exception\AMQPTimeoutException if the specified operation timeout was exceeded
     * @return null
     */
    public function confirm_select($nowait = false)
    {
        list($class_id, $method_id, $args) = $this->protocolWriter->confirmSelect($nowait);

        $this->send_method_frame(array($class_id, $method_id), $args);

        if ($nowait) {
            return null;
        }

        $this->wait(array(
            $this->waitHelper->get_wait('confirm.select_ok')
        ), false, $this->channel_rpc_timeout);
        $this->next_delivery_tag = 1;
    }

    /**
     * Confirms a selection
     *
     * @param AMQPReader $reader
     */
    public function confirm_select_ok($reader)
    {
    }

    /**
     * Waits for pending acks and nacks from the server.
     * If there are no pending acks, the method returns immediately
     *
     * @param int $timeout Waits until $timeout value is reached
     */
    public function wait_for_pending_acks($timeout = 0)
    {
        $functions = array(
            $this->waitHelper->get_wait('basic.ack'),
            $this->waitHelper->get_wait('basic.nack'),
        );

        while (count($this->published_messages) !== 0) {
            if ($timeout > 0) {
                $this->wait($functions, true, $timeout);
            } else {
                $this->wait($functions);
            }
        }
    }

    /**
     * Waits for pending acks, nacks and returns from the server. If there are no pending acks, the method returns immediately.
     *
     * @param int $timeout If set to value > 0 the method will wait at most $timeout seconds for pending acks.
     */
    public function wait_for_pending_acks_returns($timeout = 0)
    {
        $functions = array(
            $this->waitHelper->get_wait('basic.ack'),
            $this->waitHelper->get_wait('basic.nack'),
            $this->waitHelper->get_wait('basic.return'),
        );

        while (count($this->published_messages) !== 0) {
            if ($timeout > 0) {
                $this->wait($functions, true, $timeout);
            } else {
                $this->wait($functions);
            }
        }
    }

    /**
     * Selects standard transaction mode
     *
     * @throws \PhpAmqpLib\Exception\AMQPTimeoutException if the specified operation timeout was exceeded
     * @return mixed
     */
    public function tx_select()
    {
        $this->send_method_frame(array(90, 10));

        return $this->wait(array(
            $this->waitHelper->get_wait('tx.select_ok')
        ), false, $this->channel_rpc_timeout);
    }

    /**
     * Confirms transaction mode
     * @param AMQPReader $reader
     */
    protected function tx_select_ok($reader)
    {
    }

    /**
     * @param array $arguments
     * @return array
     */
    protected function getArguments($arguments)
    {
        @trigger_error(sprintf(
            'Method "%s" is deprecated, please use an array as a default argument instead',
            __METHOD__
        ), E_USER_DEPRECATED);
        return (null === $arguments) ? array() : $arguments;
    }

    /**
     * @param int $ticket
     * @return int
     */
    protected function getTicket($ticket)
    {
        return (null === $ticket) ? $this->default_ticket : $ticket;
    }

    /**
     * Helper method to get a particular method from $this->publishedMessages, removes it from the array and returns it.
     *
     * @param int $index
     * @return AMQPMessage
     */
    protected function get_and_unset_message($index)
    {
        $message = $this->published_messages[$index];
        unset($this->published_messages[$index]);

        return $message;
    }

    /**
     * Sets callback for basic_return
     *
     * @param  callable $callback
     * @throws \InvalidArgumentException if $callback is not callable
     */
    public function set_return_listener($callback)
    {
        if (!is_callable($callback)) {
            throw new \InvalidArgumentException(sprintf(
                'Given callback "%s" should be callable. %s type was given.',
                $callback,
                gettype($callback)
            ));
        }

        $this->basic_return_callback = $callback;
    }

    /**
     * Sets a handler which called for any message nack'ed by the server, with the AMQPMessage as first argument.
     *
     * @param callable $callback
     * @throws \InvalidArgumentException
     */
    public function set_nack_handler($callback)
    {
        if (!is_callable($callback)) {
            throw new \InvalidArgumentException(sprintf(
                'Given callback "%s" should be callable. %s type was given.',
                $callback,
                gettype($callback)
            ));
        }

        $this->nack_handler = $callback;
    }

    /**
     * Sets a handler which called for any message ack'ed by the server, with the AMQPMessage as first argument.
     *
     * @param callable $callback
     * @throws \InvalidArgumentException
     */
    public function set_ack_handler($callback)
    {
        if (!is_callable($callback)) {
            throw new \InvalidArgumentException(sprintf(
                'Given callback "%s" should be callable. %s type was given.',
                $callback,
                gettype($callback)
            ));
        }

        $this->ack_handler = $callback;
    }
}
<?php
namespace PhpAmqpLib\Channel;

use PhpAmqpLib\Connection\AbstractConnection;
use PhpAmqpLib\Exception\AMQPChannelClosedException;
use PhpAmqpLib\Exception\AMQPConnectionClosedException;
use PhpAmqpLib\Exception\AMQPInvalidFrameException;
use PhpAmqpLib\Exception\AMQPNoDataException;
use PhpAmqpLib\Exception\AMQPNotImplementedException;
use PhpAmqpLib\Exception\AMQPOutOfBoundsException;
use PhpAmqpLib\Exception\AMQPOutOfRangeException;
use PhpAmqpLib\Helper\DebugHelper;
use PhpAmqpLib\Helper\Protocol\MethodMap080;
use PhpAmqpLib\Helper\Protocol\MethodMap091;
use PhpAmqpLib\Helper\Protocol\Protocol080;
use PhpAmqpLib\Helper\Protocol\Protocol091;
use PhpAmqpLib\Helper\Protocol\Wait080;
use PhpAmqpLib\Helper\Protocol\Wait091;
use PhpAmqpLib\Message\AMQPMessage;
use PhpAmqpLib\Wire\AMQPReader;

abstract class AbstractChannel
{
    const PROTOCOL_080 = '0.8';
    const PROTOCOL_091 = '0.9.1';

    /**
     * @var string
     * @deprecated
     */
    public static $PROTOCOL_CONSTANTS_CLASS;

    /** @var array */
    protected $frame_queue;

    /** @var array */
    protected $method_queue;

    /** @var bool */
    protected $auto_decode;

    /** @var string */
    protected $amqp_protocol_header;

    /** @var \PhpAmqpLib\Helper\DebugHelper */
    protected $debug;

    /** @var \PhpAmqpLib\Connection\AbstractConnection */
    protected $connection;

    /** @var string */
    protected $protocolVersion;

    /** @var int */
    protected $maxBodySize;

    /** @var \PhpAmqpLib\Helper\Protocol\Protocol080|\PhpAmqpLib\Helper\Protocol\Protocol091 */
    protected $protocolWriter;

    /** @var \PhpAmqpLib\Helper\Protocol\Wait080|\PhpAmqpLib\Helper\Protocol\Wait091 */
    protected $waitHelper;

    /** @var \PhpAmqpLib\Helper\Protocol\MethodMap080|\PhpAmqpLib\Helper\Protocol\MethodMap091 */
    protected $methodMap;

    /** @var string */
    protected $channel_id;

    /** @var \PhpAmqpLib\Wire\AMQPReader */
    protected $msg_property_reader;

    /** @var \PhpAmqpLib\Wire\AMQPReader */
    protected $wait_content_reader;

    /** @var \PhpAmqpLib\Wire\AMQPReader */
    protected $dispatch_reader;

    /**
     * @param AbstractConnection $connection
     * @param string $channel_id
     * @throws \PhpAmqpLib\Exception\AMQPRuntimeException
     */
    public function __construct(AbstractConnection $connection, $channel_id)
    {
        $this->connection = $connection;
        $this->channel_id = $channel_id;
        $connection->channels[$channel_id] = $this;
        $this->frame_queue = array(); // Lower level queue for frames
        $this->method_queue = array(); // Higher level queue for methods
        $this->auto_decode = false;

        $this->msg_property_reader = new AMQPReader(null);
        $this->wait_content_reader = new AMQPReader(null);
        $this->dispatch_reader = new AMQPReader(null);

        $this->protocolVersion = self::getProtocolVersion();
        switch ($this->protocolVersion) {
            case self::PROTOCOL_091:
                self::$PROTOCOL_CONSTANTS_CLASS = 'PhpAmqpLib\Wire\Constants091';
                $c = self::$PROTOCOL_CONSTANTS_CLASS;
                $this->debug = new DebugHelper($c);
                $this->amqp_protocol_header = $c::$AMQP_PROTOCOL_HEADER;
                $this->protocolWriter = new Protocol091();
                $this->waitHelper = new Wait091();
                $this->methodMap = new MethodMap091();
                break;
            case self::PROTOCOL_080:
                self::$PROTOCOL_CONSTANTS_CLASS = 'PhpAmqpLib\Wire\Constants080';
                $c = self::$PROTOCOL_CONSTANTS_CLASS;
                $this->debug = new DebugHelper($c);
                $this->amqp_protocol_header = $c::$AMQP_PROTOCOL_HEADER;
                $this->protocolWriter = new Protocol080();
                $this->waitHelper = new Wait080();
                $this->methodMap = new MethodMap080();
                break;
            default:
                throw new AMQPNotImplementedException(sprintf(
                    'Protocol: %s not implemented.',
                    $this->protocolVersion
                ));
        }
    }

    /**
     * @return string
     * @throws AMQPOutOfRangeException
     */
    public static function getProtocolVersion()
    {
        $protocol = defined('AMQP_PROTOCOL') ? AMQP_PROTOCOL : self::PROTOCOL_091;
        //adding check here to catch unknown protocol ASAP, as this method may be called from the outside
        if (!in_array($protocol, array(self::PROTOCOL_080, self::PROTOCOL_091), TRUE)) {
            throw new AMQPOutOfRangeException(sprintf('Protocol version %s not implemented.', $protocol));
        }

        return $protocol;
    }

    /**
     * @return string
     */
    public function getChannelId()
    {
        return $this->channel_id;
    }

    /**
     * @param int $max_bytes Max message body size for this channel
     * @return $this
     */
    public function setBodySizeLimit($max_bytes)
    {
        $max_bytes = (int) $max_bytes;

        if ($max_bytes > 0) {
            $this->maxBodySize = $max_bytes;
        }

        return $this;
    }

    /**
     * @return AbstractConnection
     */
    public function getConnection()
    {
        return $this->connection;
    }

    /**
     * @return array
     */
    public function getMethodQueue()
    {
        return $this->method_queue;
    }

    /**
     * @return bool
     */
    public function hasPendingMethods()
    {
        return !empty($this->method_queue);
    }

    /**
     * @param string $method_sig
     * @param string $args
     * @param AMQPMessage|null $amqpMessage
     * @return mixed
     * @throws \PhpAmqpLib\Exception\AMQPRuntimeException
     */
    public function dispatch($method_sig, $args, $amqpMessage)
    {
        if (!$this->methodMap->valid_method($method_sig)) {
            throw new AMQPNotImplementedException(sprintf(
                'Unknown AMQP method "%s"',
                $method_sig
            ));
        }

        $amqp_method = $this->methodMap->get_method($method_sig);

        if (!method_exists($this, $amqp_method)) {
            throw new AMQPNotImplementedException(sprintf(
                'Method: "%s" not implemented by class: %s',
                $amqp_method,
                get_class($this)
            ));
        }

        $this->dispatch_reader->reuse($args);

        if ($amqpMessage == null) {
            return call_user_func(array($this, $amqp_method), $this->dispatch_reader);
        }

        return call_user_func(array($this, $amqp_method), $this->dispatch_reader, $amqpMessage);
    }

    /**
     * @param int|float|null $timeout
     * @return array|mixed
     */
    public function next_frame($timeout = 0)
    {
        $this->debug->debug_msg('waiting for a new frame');

        if (!empty($this->frame_queue)) {
            return array_shift($this->frame_queue);
        }

        return $this->connection->wait_channel($this->channel_id, $timeout);
    }

    /**
     * @param array $method_sig
     * @param \PhpAmqpLib\Wire\AMQPWriter|string $args
     */
    protected function send_method_frame($method_sig, $args = '')
    {
        if ($this->connection === null) {
            throw new AMQPChannelClosedException('Channel connection is closed.');
        }

        $this->connection->send_channel_method_frame($this->channel_id, $method_sig, $args);
    }

    /**
     * This is here for performance reasons to batch calls to fwrite from basic.publish
     *
     * @param array $method_sig
     * @param \PhpAmqpLib\Wire\AMQPWriter|string $args
     * @param \PhpAmqpLib\Wire\AMQPWriter $pkt
     * @return \PhpAmqpLib\Wire\AMQPWriter
     */
    protected function prepare_method_frame($method_sig, $args = '', $pkt = null)
    {
        return $this->connection->prepare_channel_method_frame($this->channel_id, $method_sig, $args, $pkt);
    }

    /**
     * @return AMQPMessage
     * @throws \PhpAmqpLib\Exception\AMQPRuntimeException
     */
    public function wait_content()
    {
        list($frame_type, $payload) = $this->next_frame();

        $this->validate_header_frame($frame_type);

        $this->wait_content_reader->reuse(mb_substr($payload, 0, 12, 'ASCII'));

        $class_id = $this->wait_content_reader->read_short();
        $weight = $this->wait_content_reader->read_short();

        //hack to avoid creating new instances of AMQPReader;
        $this->msg_property_reader->reuse(mb_substr($payload, 12, mb_strlen($payload, 'ASCII') - 12, 'ASCII'));

        return $this->createMessage(
            $this->msg_property_reader,
            $this->wait_content_reader
        );
    }

    /**
     * @param AMQPReader $propertyReader
     * @param AMQPReader $contentReader
     * @return \PhpAmqpLib\Message\AMQPMessage
     */
    protected function createMessage($propertyReader, $contentReader)
    {
        $bodyChunks = array();
        $bodyReceivedBytes = 0;

        $message = new AMQPMessage();
        $message
            ->load_properties($propertyReader)
            ->setBodySize($contentReader->read_longlong());

        while (bccomp($message->getBodySize(), $bodyReceivedBytes, 0) == 1) {
            list($frame_type, $payload) = $this->next_frame();

            $this->validate_body_frame($frame_type);
            $bodyReceivedBytes = bcadd($bodyReceivedBytes, mb_strlen($payload, 'ASCII'), 0);

            if (is_int($this->maxBodySize) && $bodyReceivedBytes > $this->maxBodySize ) {
                $message->setIsTruncated(true);
                continue;
            }

            $bodyChunks[] = $payload;
        }

        $message->setBody(implode('', $bodyChunks));

        return $message;
    }

    /**
     * Wait for some expected AMQP methods and dispatch to them.
     * Unexpected methods are queued up for later calls to this PHP
     * method.
     *
     * @param array $allowed_methods
     * @param bool $non_blocking
     * @param int|float|null $timeout
     * @throws \PhpAmqpLib\Exception\AMQPOutOfBoundsException
     * @throws \PhpAmqpLib\Exception\AMQPRuntimeException
     * @throws \PhpAmqpLib\Exception\AMQPTimeoutException
     * @throws \ErrorException
     * @return mixed
     */
    public function wait($allowed_methods = null, $non_blocking = false, $timeout = 0)
    {
        $this->debug->debug_allowed_methods($allowed_methods);

        $deferred = $this->process_deferred_methods($allowed_methods);
        if ($deferred['dispatch'] === true) {
            return $this->dispatch_deferred_method($deferred['queued_method']);
        }

        // timeouts must be deactivated for non-blocking actions
        if (true === $non_blocking) {
            $timeout = null;
        }

        // No deferred methods?  wait for new ones
        while (true) {
            try {
                list($frame_type, $payload) = $this->next_frame($timeout);
            } catch (AMQPNoDataException $e) {
                // no data ready for non-blocking actions - stop and exit
                break;
            } catch (AMQPConnectionClosedException $exception) {
                if ($this instanceof AMQPChannel) {
                    $this->do_close();
                }
                throw $exception;
            }

            $this->validate_method_frame($frame_type);
            $this->validate_frame_payload($payload);

            $method_sig = $this->build_method_signature($payload);
            $args = $this->extract_args($payload);

            $this->debug->debug_method_signature('> %s', $method_sig);

            $amqpMessage = $this->maybe_wait_for_content($method_sig);

            if ($this->should_dispatch_method($allowed_methods, $method_sig)) {
                return $this->dispatch($method_sig, $args, $amqpMessage);
            }

            // Wasn't what we were looking for? save it for later
            $this->debug->debug_method_signature('Queueing for later: %s', $method_sig);
            $this->method_queue[] = array($method_sig, $args, $amqpMessage);

            if ($non_blocking) {
                break;
            }
        }
    }

    /**
     * @param array $allowed_methods
     * @return array
     */
    protected function process_deferred_methods($allowed_methods)
    {
        $dispatch = false;
        $queued_method = array();

        foreach ($this->method_queue as $qk => $qm) {
            $this->debug->debug_msg('checking queue method ' . $qk);

            $method_sig = $qm[0];

            if ($allowed_methods == null || in_array($method_sig, $allowed_methods)) {
                unset($this->method_queue[$qk]);
                $dispatch = true;
                $queued_method = $qm;
                break;
            }
        }

        return array('dispatch' => $dispatch, 'queued_method' => $queued_method);
    }

    /**
     * @param array $queued_method
     * @return mixed
     */
    protected function dispatch_deferred_method($queued_method)
    {
        $this->debug->debug_method_signature('Executing queued method: %s', $queued_method[0]);

        return $this->dispatch($queued_method[0], $queued_method[1], $queued_method[2]);
    }

    /**
     * @param int $frame_type
     * @throws \PhpAmqpLib\Exception\AMQPInvalidFrameException
     */
    protected function validate_method_frame($frame_type)
    {
        $this->validate_frame($frame_type, 1, 'AMQP method');
    }

    /**
     * @param int $frame_type
     * @throws \PhpAmqpLib\Exception\AMQPInvalidFrameException
     */
    protected function validate_header_frame($frame_type)
    {
        $this->validate_frame($frame_type, 2, 'AMQP Content header');
    }

    /**
     * @param int $frame_type
     * @throws \PhpAmqpLib\Exception\AMQPInvalidFrameException
     */
    protected function validate_body_frame($frame_type)
    {
        $this->validate_frame($frame_type, 3, 'AMQP Content body');
    }

    /**
     * @param int $frameType
     * @param int $expectedType
     * @param string $expectedMessage
     */
    protected function validate_frame($frameType, $expectedType, $expectedMessage)
    {
        if ($frameType != $expectedType) {
            $protocolClass = self::$PROTOCOL_CONSTANTS_CLASS;
            throw new AMQPInvalidFrameException(sprintf(
                    'Expecting %s, received frame type %s (%s)',
                    $expectedMessage,
                    $frameType,
                    $protocolClass::$FRAME_TYPES[$frameType]
                ));
        }
    }

    /**
     * @param string $payload
     * @throws \PhpAmqpLib\Exception\AMQPOutOfBoundsException
     */
    protected function validate_frame_payload($payload)
    {
        if (mb_strlen($payload, 'ASCII') < 4) {
            throw new AMQPOutOfBoundsException('Method frame too short');
        }
    }

    /**
     * @param string $payload
     * @return string
     */
    protected function build_method_signature($payload)
    {
        $method_sig_array = unpack('n2', mb_substr($payload, 0, 4, 'ASCII'));

        return sprintf('%s,%s', $method_sig_array[1], $method_sig_array[2]);
    }

    /**
     * @param string $payload
     * @return string
     */
    protected function extract_args($payload)
    {
        return mb_substr($payload, 4, mb_strlen($payload, 'ASCII') - 4, 'ASCII');
    }

    /**
     * @param array|null $allowed_methods
     * @param string $method_sig
     * @return bool
     */
    protected function should_dispatch_method($allowed_methods, $method_sig)
    {
        $protocolClass = self::$PROTOCOL_CONSTANTS_CLASS;

        return $allowed_methods == null
            || in_array($method_sig, $allowed_methods)
            || in_array($method_sig, $protocolClass::$CLOSE_METHODS);
    }

    /**
     * @param string $method_sig
     * @return AMQPMessage|null
     */
    protected function maybe_wait_for_content($method_sig)
    {
        $protocolClass = self::$PROTOCOL_CONSTANTS_CLASS;
        $amqpMessage = null;

        if (in_array($method_sig, $protocolClass::$CONTENT_METHODS)) {
            $amqpMessage = $this->wait_content();
        }

        return $amqpMessage;
    }

    /**
     * @param callable $handler
     * @param array $arguments
     */
    protected function dispatch_to_handler($handler, array $arguments)
    {
        if (is_callable($handler)) {
            call_user_func_array($handler, $arguments);
        }
    }
}
<?php
namespace PhpAmqpLib\Helper;

class MiscHelper
{
    /**
     * @param string|array $a
     * @return string
     */
    public static function methodSig($a)
    {
        if (is_string($a)) {
            return $a;
        }

        return sprintf('%d,%d', $a[0], $a[1]);
    }

    /**
     * @param string $bytes
     */
    public static function saveBytes($bytes)
    {
        $fh = fopen('/tmp/bytes', 'wb');
        fwrite($fh, $bytes);
        fclose($fh);
    }

    /**
     * Gets a number (either int or float) and returns an array containing its integer part as first element and its
     * decimal part mutliplied by 10^6. Useful for some PHP stream functions that need seconds and microseconds as
     * different arguments
     *
     * @param int|float $number
     * @return int[]
     */
    public static function splitSecondsMicroseconds($number)
    {
        return array((int)floor($number), (int)(fmod($number, 1) * 1000000));
    }

    /**
     * View any string as a hexdump.
     *
     * This is most commonly used to view binary data from streams
     * or sockets while debugging, but can be used to view any string
     * with non-viewable characters.
     *
     * @version     1.3.2
     * @author      Aidan Lister <aidan@php.net>
     * @author      Peter Waller <iridum@php.net>
     * @link        http://aidanlister.com/repos/v/function.hexdump.php
     *
     * @param string $data The string to be dumped
     * @param bool $htmloutput Set to false for non-HTML output
     * @param bool $uppercase Set to true for uppercase hex
     * @param bool $return Set to true to return the dump
     * @return string|null
     */
    public static function hexdump($data, $htmloutput = true, $uppercase = false, $return = false)
    {
        // Init
        $hexi = '';
        $ascii = '';
        $dump = $htmloutput ? '<pre>' : '';
        $offset = 0;
        $len = mb_strlen($data, 'ASCII');

        // Upper or lower case hexidecimal
        $hexFormat = $uppercase ? 'X' : 'x';

        // Iterate string
        for ($i = $j = 0; $i < $len; $i++) {
            // Convert to hexidecimal
            // We must use concatenation here because the $hexFormat value
            // is needed for sprintf() to parse the format
            $hexi .= sprintf('%02' .  $hexFormat . ' ', ord($data[$i]));

            // Replace non-viewable bytes with '.'
            if (ord($data[$i]) >= 32) {
                $ascii .= $htmloutput ? htmlentities($data[$i]) : $data[$i];
            } else {
                $ascii .= '.';
            }

            // Add extra column spacing
            if ($j === 7) {
                $hexi .= ' ';
                $ascii .= ' ';
            }

            // Add row
            if (++$j === 16 || $i === $len - 1) {
                // Join the hexi / ascii output
                // We must use concatenation here because the $hexFormat value
                // is needed for sprintf() to parse the format
                $dump .= sprintf('%04' . $hexFormat . '  %-49s  %s', $offset, $hexi, $ascii);

                // Reset vars
                $hexi = $ascii = '';
                $offset += 16;
                $j = 0;

                // Add newline
                if ($i !== $len - 1) {
                    $dump .= PHP_EOL;
                }
            }
        }

        // Finish dump
        $dump .= $htmloutput ? '</pre>' : '';
        $dump .= PHP_EOL;

        if ($return) {
            return $dump;
        }

        echo $dump;
    }

    /**
     * @param array $table
     * @return string
     */
    public static function dump_table($table)
    {
        $tokens = array();
        foreach ($table as $name => $value) {
            switch ($value[0]) {
                case 'D':
                    $val = $value[1]->n . 'E' . $value[1]->e;
                    break;
                case 'F':
                    $val = '(' . self::dump_table($value[1]) . ')';
                    break;
                case 'T':
                    $val = date('Y-m-d H:i:s', $value[1]);
                    break;
                default:
                    $val = $value[1];
            }
            $tokens[] = $name . '=' . $val;
        }

        return implode(', ', $tokens);
    }
}
<?php

/* This file was autogenerated by spec/parser.php - Do not modify */

namespace PhpAmqpLib\Helper\Protocol;

use PhpAmqpLib\Wire\AMQPWriter;
use PhpAmqpLib\Wire\AMQPReader;

class Protocol091
{
    /**
     * @param int $version_major
     * @param int $version_minor
     * @param mixed $server_properties
     * @param string $mechanisms
     * @param string $locales
     * @return array
     */
    public function connectionStart($version_major = 0, $version_minor = 9, $server_properties, $mechanisms = 'PLAIN', $locales = 'en_US')
    {
        $writer = new AMQPWriter();
        $writer->write_octet($version_major);
        $writer->write_octet($version_minor);
        $writer->write_table(empty($server_properties) ? array() : $server_properties);
        $writer->write_longstr($mechanisms);
        $writer->write_longstr($locales);
        return array(10, 10, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function connectionStartOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_table();
        $response[] = $reader->read_shortstr();
        $response[] = $reader->read_longstr();
        $response[] = $reader->read_shortstr();
        return $response;
    }

    /**
     * @param string $challenge
     * @return array
     */
    public function connectionSecure($challenge)
    {
        $writer = new AMQPWriter();
        $writer->write_longstr($challenge);
        return array(10, 20, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function connectionSecureOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_longstr();
        return $response;
    }

    /**
     * @param int $channel_max
     * @param int $frame_max
     * @param int $heartbeat
     * @return array
     */
    public function connectionTune($channel_max = 0, $frame_max = 0, $heartbeat = 0)
    {
        $writer = new AMQPWriter();
        $writer->write_short($channel_max);
        $writer->write_long($frame_max);
        $writer->write_short($heartbeat);
        return array(10, 30, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function connectionTuneOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_short();
        $response[] = $reader->read_long();
        $response[] = $reader->read_short();
        return $response;
    }

    /**
     * @param string $virtual_host
     * @param string $capabilities
     * @param bool $insist
     * @return array
     */
    public function connectionOpen($virtual_host = '/', $capabilities = '', $insist = false)
    {
        $writer = new AMQPWriter();
        $writer->write_shortstr($virtual_host);
        $writer->write_shortstr($capabilities);
        $writer->write_bits(array($insist));
        return array(10, 40, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function connectionOpenOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_shortstr();
        return $response;
    }

    /**
     * @param int $reply_code
     * @param string $reply_text
     * @param int $class_id
     * @param int $method_id
     * @return array
     */
    public function connectionClose($reply_code, $reply_text = '', $class_id, $method_id)
    {
        $writer = new AMQPWriter();
        $writer->write_short($reply_code);
        $writer->write_shortstr($reply_text);
        $writer->write_short($class_id);
        $writer->write_short($method_id);
        return array(10, 50, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function connectionCloseOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**
     * @param string $reason
     * @return array
     */
    public function connectionBlocked($reason = '')
    {
        $writer = new AMQPWriter();
        $writer->write_shortstr($reason);
        return array(10, 60, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function connectionUnblocked(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**
     * @param string $out_of_band
     * @return array
     */
    public function channelOpen($out_of_band = '')
    {
        $writer = new AMQPWriter();
        $writer->write_shortstr($out_of_band);
        return array(20, 10, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function channelOpenOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_longstr();
        return $response;
    }

    /**
     * @param bool $active
     * @return array
     */
    public function channelFlow($active)
    {
        $writer = new AMQPWriter();
        $writer->write_bits(array($active));
        return array(20, 20, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function channelFlowOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_bit();
        return $response;
    }

    /**
     * @param int $reply_code
     * @param string $reply_text
     * @param int $class_id
     * @param int $method_id
     * @return array
     */
    public function channelClose($reply_code, $reply_text = '', $class_id, $method_id)
    {
        $writer = new AMQPWriter();
        $writer->write_short($reply_code);
        $writer->write_shortstr($reply_text);
        $writer->write_short($class_id);
        $writer->write_short($method_id);
        return array(20, 40, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function channelCloseOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**
     * @param string $realm
     * @param bool $exclusive
     * @param bool $passive
     * @param bool $active
     * @param bool $write
     * @param bool $read
     * @return array
     */
    public function accessRequest($realm = '/data', $exclusive = false, $passive = true, $active = true, $write = true, $read = true)
    {
        $writer = new AMQPWriter();
        $writer->write_shortstr($realm);
        $writer->write_bits(array($exclusive, $passive, $active, $write, $read));
        return array(30, 10, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function accessRequestOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_short();
        return $response;
    }

    /**
     * @param int $ticket
     * @param string $exchange
     * @param string $type
     * @param bool $passive
     * @param bool $durable
     * @param bool $auto_delete
     * @param bool $internal
     * @param bool $nowait
     * @param array $arguments
     * @return array
     */
    public function exchangeDeclare($ticket = 0, $exchange, $type = 'direct', $passive = false, $durable = false, $auto_delete = false, $internal = false, $nowait = false, $arguments = array())
    {
        $writer = new AMQPWriter();
        $writer->write_short($ticket);
        $writer->write_shortstr($exchange);
        $writer->write_shortstr($type);
        $writer->write_bits(array($passive, $durable, $auto_delete, $internal, $nowait));
        $writer->write_table(empty($arguments) ? array() : $arguments);
        return array(40, 10, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function exchangeDeclareOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**
     * @param int $ticket
     * @param string $exchange
     * @param bool $if_unused
     * @param bool $nowait
     * @return array
     */
    public function exchangeDelete($ticket = 0, $exchange, $if_unused = false, $nowait = false)
    {
        $writer = new AMQPWriter();
        $writer->write_short($ticket);
        $writer->write_shortstr($exchange);
        $writer->write_bits(array($if_unused, $nowait));
        return array(40, 20, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function exchangeDeleteOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**
     * @param int $ticket
     * @param string $destination
     * @param string $source
     * @param string $routing_key
     * @param bool $nowait
     * @param array $arguments
     * @return array
     */
    public function exchangeBind($ticket = 0, $destination, $source, $routing_key = '', $nowait = false, $arguments = array())
    {
        $writer = new AMQPWriter();
        $writer->write_short($ticket);
        $writer->write_shortstr($destination);
        $writer->write_shortstr($source);
        $writer->write_shortstr($routing_key);
        $writer->write_bits(array($nowait));
        $writer->write_table(empty($arguments) ? array() : $arguments);
        return array(40, 30, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function exchangeBindOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**
     * @param int $ticket
     * @param string $destination
     * @param string $source
     * @param string $routing_key
     * @param bool $nowait
     * @param array $arguments
     * @return array
     */
    public function exchangeUnbind($ticket = 0, $destination, $source, $routing_key = '', $nowait = false, $arguments = array())
    {
        $writer = new AMQPWriter();
        $writer->write_short($ticket);
        $writer->write_shortstr($destination);
        $writer->write_shortstr($source);
        $writer->write_shortstr($routing_key);
        $writer->write_bits(array($nowait));
        $writer->write_table(empty($arguments) ? array() : $arguments);
        return array(40, 40, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function exchangeUnbindOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**
     * @param int $ticket
     * @param string $queue
     * @param bool $passive
     * @param bool $durable
     * @param bool $exclusive
     * @param bool $auto_delete
     * @param bool $nowait
     * @param array $arguments
     * @return array
     */
    public function queueDeclare($ticket = 0, $queue = '', $passive = false, $durable = false, $exclusive = false, $auto_delete = false, $nowait = false, $arguments = array())
    {
        $writer = new AMQPWriter();
        $writer->write_short($ticket);
        $writer->write_shortstr($queue);
        $writer->write_bits(array($passive, $durable, $exclusive, $auto_delete, $nowait));
        $writer->write_table(empty($arguments) ? array() : $arguments);
        return array(50, 10, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function queueDeclareOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_shortstr();
        $response[] = $reader->read_long();
        $response[] = $reader->read_long();
        return $response;
    }

    /**
     * @param int $ticket
     * @param string $queue
     * @param string $exchange
     * @param string $routing_key
     * @param bool $nowait
     * @param array $arguments
     * @return array
     */
    public function queueBind($ticket = 0, $queue = '', $exchange, $routing_key = '', $nowait = false, $arguments = array())
    {
        $writer = new AMQPWriter();
        $writer->write_short($ticket);
        $writer->write_shortstr($queue);
        $writer->write_shortstr($exchange);
        $writer->write_shortstr($routing_key);
        $writer->write_bits(array($nowait));
        $writer->write_table(empty($arguments) ? array() : $arguments);
        return array(50, 20, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function queueBindOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**
     * @param int $ticket
     * @param string $queue
     * @param bool $nowait
     * @return array
     */
    public function queuePurge($ticket = 0, $queue = '', $nowait = false)
    {
        $writer = new AMQPWriter();
        $writer->write_short($ticket);
        $writer->write_shortstr($queue);
        $writer->write_bits(array($nowait));
        return array(50, 30, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function queuePurgeOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_long();
        return $response;
    }

    /**
     * @param int $ticket
     * @param string $queue
     * @param bool $if_unused
     * @param bool $if_empty
     * @param bool $nowait
     * @return array
     */
    public function queueDelete($ticket = 0, $queue = '', $if_unused = false, $if_empty = false, $nowait = false)
    {
        $writer = new AMQPWriter();
        $writer->write_short($ticket);
        $writer->write_shortstr($queue);
        $writer->write_bits(array($if_unused, $if_empty, $nowait));
        return array(50, 40, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function queueDeleteOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_long();
        return $response;
    }

    /**
     * @param int $ticket
     * @param string $queue
     * @param string $exchange
     * @param string $routing_key
     * @param array $arguments
     * @return array
     */
    public function queueUnbind($ticket = 0, $queue = '', $exchange, $routing_key = '', $arguments = array())
    {
        $writer = new AMQPWriter();
        $writer->write_short($ticket);
        $writer->write_shortstr($queue);
        $writer->write_shortstr($exchange);
        $writer->write_shortstr($routing_key);
        $writer->write_table(empty($arguments) ? array() : $arguments);
        return array(50, 50, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function queueUnbindOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**
     * @param int $prefetch_size
     * @param int $prefetch_count
     * @param bool $global
     * @return array
     */
    public function basicQos($prefetch_size = 0, $prefetch_count = 0, $global = false)
    {
        $writer = new AMQPWriter();
        $writer->write_long($prefetch_size);
        $writer->write_short($prefetch_count);
        $writer->write_bits(array($global));
        return array(60, 10, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function basicQosOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**
     * @param int $ticket
     * @param string $queue
     * @param string $consumer_tag
     * @param bool $no_local
     * @param bool $no_ack
     * @param bool $exclusive
     * @param bool $nowait
     * @param array $arguments
     * @return array
     */
    public function basicConsume($ticket = 0, $queue = '', $consumer_tag = '', $no_local = false, $no_ack = false, $exclusive = false, $nowait = false, $arguments = array())
    {
        $writer = new AMQPWriter();
        $writer->write_short($ticket);
        $writer->write_shortstr($queue);
        $writer->write_shortstr($consumer_tag);
        $writer->write_bits(array($no_local, $no_ack, $exclusive, $nowait));
        $writer->write_table(empty($arguments) ? array() : $arguments);
        return array(60, 20, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function basicConsumeOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_shortstr();
        return $response;
    }

    /**
     * @param string $consumer_tag
     * @param bool $nowait
     * @return array
     */
    public function basicCancel($consumer_tag, $nowait = false)
    {
        $writer = new AMQPWriter();
        $writer->write_shortstr($consumer_tag);
        $writer->write_bits(array($nowait));
        return array(60, 30, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function basicCancelOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_shortstr();
        return $response;
    }

    /**
     * @param int $ticket
     * @param string $exchange
     * @param string $routing_key
     * @param bool $mandatory
     * @param bool $immediate
     * @return array
     */
    public function basicPublish($ticket = 0, $exchange = '', $routing_key = '', $mandatory = false, $immediate = false)
    {
        $writer = new AMQPWriter();
        $writer->write_short($ticket);
        $writer->write_shortstr($exchange);
        $writer->write_shortstr($routing_key);
        $writer->write_bits(array($mandatory, $immediate));
        return array(60, 40, $writer);
    }

    /**
     * @param int $reply_code
     * @param string $reply_text
     * @param string $exchange
     * @param string $routing_key
     * @return array
     */
    public function basicReturn($reply_code, $reply_text = '', $exchange, $routing_key)
    {
        $writer = new AMQPWriter();
        $writer->write_short($reply_code);
        $writer->write_shortstr($reply_text);
        $writer->write_shortstr($exchange);
        $writer->write_shortstr($routing_key);
        return array(60, 50, $writer);
    }

    /**
     * @param string $consumer_tag
     * @param int $delivery_tag
     * @param bool $redelivered
     * @param string $exchange
     * @param string $routing_key
     * @return array
     */
    public function basicDeliver($consumer_tag, $delivery_tag, $redelivered = false, $exchange, $routing_key)
    {
        $writer = new AMQPWriter();
        $writer->write_shortstr($consumer_tag);
        $writer->write_longlong($delivery_tag);
        $writer->write_bits(array($redelivered));
        $writer->write_shortstr($exchange);
        $writer->write_shortstr($routing_key);
        return array(60, 60, $writer);
    }

    /**
     * @param int $ticket
     * @param string $queue
     * @param bool $no_ack
     * @return array
     */
    public function basicGet($ticket = 0, $queue = '', $no_ack = false)
    {
        $writer = new AMQPWriter();
        $writer->write_short($ticket);
        $writer->write_shortstr($queue);
        $writer->write_bits(array($no_ack));
        return array(60, 70, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function basicGetOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_longlong();
        $response[] = $reader->read_bit();
        $response[] = $reader->read_shortstr();
        $response[] = $reader->read_shortstr();
        $response[] = $reader->read_long();
        return $response;
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function basicGetEmpty(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_shortstr();
        return $response;
    }

    /**
     * @param int $delivery_tag
     * @param bool $multiple
     * @return array
     */
    public function basicAck($delivery_tag = 0, $multiple = false)
    {
        $writer = new AMQPWriter();
        $writer->write_longlong($delivery_tag);
        $writer->write_bits(array($multiple));
        return array(60, 80, $writer);
    }

    /**
     * @param int $delivery_tag
     * @param bool $requeue
     * @return array
     */
    public function basicReject($delivery_tag, $requeue = true)
    {
        $writer = new AMQPWriter();
        $writer->write_longlong($delivery_tag);
        $writer->write_bits(array($requeue));
        return array(60, 90, $writer);
    }

    /**
     * @param bool $requeue
     * @return array
     */
    public function basicRecoverAsync($requeue = false)
    {
        $writer = new AMQPWriter();
        $writer->write_bits(array($requeue));
        return array(60, 100, $writer);
    }

    /**
     * @param bool $requeue
     * @return array
     */
    public function basicRecover($requeue = false)
    {
        $writer = new AMQPWriter();
        $writer->write_bits(array($requeue));
        return array(60, 110, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function basicRecoverOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**
     * @param int $delivery_tag
     * @param bool $multiple
     * @param bool $requeue
     * @return array
     */
    public function basicNack($delivery_tag = 0, $multiple = false, $requeue = true)
    {
        $writer = new AMQPWriter();
        $writer->write_longlong($delivery_tag);
        $writer->write_bits(array($multiple, $requeue));
        return array(60, 120, $writer);
    }

    /**

     * @return array
     */
    public function txSelect()
    {
        $writer = new AMQPWriter();
        return array(90, 10, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function txSelectOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**

     * @return array
     */
    public function txCommit()
    {
        $writer = new AMQPWriter();
        return array(90, 20, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function txCommitOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**

     * @return array
     */
    public function txRollback()
    {
        $writer = new AMQPWriter();
        return array(90, 30, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function txRollbackOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**
     * @param bool $nowait
     * @return array
     */
    public function confirmSelect($nowait = false)
    {
        $writer = new AMQPWriter();
        $writer->write_bits(array($nowait));
        return array(85, 10, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function confirmSelectOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }
}
<?php

/* This file was autogenerated by spec/parser.php - Do not modify */

namespace PhpAmqpLib\Helper\Protocol;

use PhpAmqpLib\Wire\AMQPWriter;
use PhpAmqpLib\Wire\AMQPReader;

class Protocol080
{
    /**
     * @param int $version_major
     * @param int $version_minor
     * @param mixed $server_properties
     * @param string $mechanisms
     * @param string $locales
     * @return array
     */
    public function connectionStart($version_major = 0, $version_minor = 8, $server_properties, $mechanisms = 'PLAIN', $locales = 'en_US')
    {
        $writer = new AMQPWriter();
        $writer->write_octet($version_major);
        $writer->write_octet($version_minor);
        $writer->write_table(empty($server_properties) ? array() : $server_properties);
        $writer->write_longstr($mechanisms);
        $writer->write_longstr($locales);
        return array(10, 10, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function connectionStartOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_table();
        $response[] = $reader->read_shortstr();
        $response[] = $reader->read_longstr();
        $response[] = $reader->read_shortstr();
        return $response;
    }

    /**
     * @param string $challenge
     * @return array
     */
    public function connectionSecure($challenge)
    {
        $writer = new AMQPWriter();
        $writer->write_longstr($challenge);
        return array(10, 20, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function connectionSecureOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_longstr();
        return $response;
    }

    /**
     * @param int $channel_max
     * @param int $frame_max
     * @param int $heartbeat
     * @return array
     */
    public function connectionTune($channel_max = 0, $frame_max = 0, $heartbeat = 0)
    {
        $writer = new AMQPWriter();
        $writer->write_short($channel_max);
        $writer->write_long($frame_max);
        $writer->write_short($heartbeat);
        return array(10, 30, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function connectionTuneOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_short();
        $response[] = $reader->read_long();
        $response[] = $reader->read_short();
        return $response;
    }

    /**
     * @param string $virtual_host
     * @param string $capabilities
     * @param bool $insist
     * @return array
     */
    public function connectionOpen($virtual_host = '/', $capabilities = '', $insist = false)
    {
        $writer = new AMQPWriter();
        $writer->write_shortstr($virtual_host);
        $writer->write_shortstr($capabilities);
        $writer->write_bits(array($insist));
        return array(10, 40, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function connectionOpenOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_shortstr();
        return $response;
    }

    /**
     * @param string $host
     * @param string $known_hosts
     * @return array
     */
    public function connectionRedirect($host, $known_hosts = '')
    {
        $writer = new AMQPWriter();
        $writer->write_shortstr($host);
        $writer->write_shortstr($known_hosts);
        return array(10, 50, $writer);
    }

    /**
     * @param int $reply_code
     * @param string $reply_text
     * @param int $class_id
     * @param int $method_id
     * @return array
     */
    public function connectionClose($reply_code, $reply_text = '', $class_id, $method_id)
    {
        $writer = new AMQPWriter();
        $writer->write_short($reply_code);
        $writer->write_shortstr($reply_text);
        $writer->write_short($class_id);
        $writer->write_short($method_id);
        return array(10, 60, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function connectionCloseOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**
     * @param string $out_of_band
     * @return array
     */
    public function channelOpen($out_of_band = '')
    {
        $writer = new AMQPWriter();
        $writer->write_shortstr($out_of_band);
        return array(20, 10, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function channelOpenOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**
     * @param bool $active
     * @return array
     */
    public function channelFlow($active)
    {
        $writer = new AMQPWriter();
        $writer->write_bits(array($active));
        return array(20, 20, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function channelFlowOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_bit();
        return $response;
    }

    /**
     * @param int $reply_code
     * @param string $reply_text
     * @param array $details
     * @return array
     */
    public function channelAlert($reply_code, $reply_text = '', $details = array())
    {
        $writer = new AMQPWriter();
        $writer->write_short($reply_code);
        $writer->write_shortstr($reply_text);
        $writer->write_table(empty($details) ? array() : $details);
        return array(20, 30, $writer);
    }

    /**
     * @param int $reply_code
     * @param string $reply_text
     * @param int $class_id
     * @param int $method_id
     * @return array
     */
    public function channelClose($reply_code, $reply_text = '', $class_id, $method_id)
    {
        $writer = new AMQPWriter();
        $writer->write_short($reply_code);
        $writer->write_shortstr($reply_text);
        $writer->write_short($class_id);
        $writer->write_short($method_id);
        return array(20, 40, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function channelCloseOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**
     * @param string $realm
     * @param bool $exclusive
     * @param bool $passive
     * @param bool $active
     * @param bool $write
     * @param bool $read
     * @return array
     */
    public function accessRequest($realm = '/data', $exclusive = false, $passive = true, $active = true, $write = true, $read = true)
    {
        $writer = new AMQPWriter();
        $writer->write_shortstr($realm);
        $writer->write_bits(array($exclusive, $passive, $active, $write, $read));
        return array(30, 10, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function accessRequestOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_short();
        return $response;
    }

    /**
     * @param int $ticket
     * @param string $exchange
     * @param string $type
     * @param bool $passive
     * @param bool $durable
     * @param bool $auto_delete
     * @param bool $internal
     * @param bool $nowait
     * @param array $arguments
     * @return array
     */
    public function exchangeDeclare($ticket = 1, $exchange, $type = 'direct', $passive = false, $durable = false, $auto_delete = false, $internal = false, $nowait = false, $arguments = array())
    {
        $writer = new AMQPWriter();
        $writer->write_short($ticket);
        $writer->write_shortstr($exchange);
        $writer->write_shortstr($type);
        $writer->write_bits(array($passive, $durable, $auto_delete, $internal, $nowait));
        $writer->write_table(empty($arguments) ? array() : $arguments);
        return array(40, 10, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function exchangeDeclareOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**
     * @param int $ticket
     * @param string $exchange
     * @param bool $if_unused
     * @param bool $nowait
     * @return array
     */
    public function exchangeDelete($ticket = 1, $exchange, $if_unused = false, $nowait = false)
    {
        $writer = new AMQPWriter();
        $writer->write_short($ticket);
        $writer->write_shortstr($exchange);
        $writer->write_bits(array($if_unused, $nowait));
        return array(40, 20, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function exchangeDeleteOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**
     * @param int $ticket
     * @param string $queue
     * @param bool $passive
     * @param bool $durable
     * @param bool $exclusive
     * @param bool $auto_delete
     * @param bool $nowait
     * @param array $arguments
     * @return array
     */
    public function queueDeclare($ticket = 1, $queue = '', $passive = false, $durable = false, $exclusive = false, $auto_delete = false, $nowait = false, $arguments = array())
    {
        $writer = new AMQPWriter();
        $writer->write_short($ticket);
        $writer->write_shortstr($queue);
        $writer->write_bits(array($passive, $durable, $exclusive, $auto_delete, $nowait));
        $writer->write_table(empty($arguments) ? array() : $arguments);
        return array(50, 10, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function queueDeclareOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_shortstr();
        $response[] = $reader->read_long();
        $response[] = $reader->read_long();
        return $response;
    }

    /**
     * @param int $ticket
     * @param string $queue
     * @param string $exchange
     * @param string $routing_key
     * @param bool $nowait
     * @param array $arguments
     * @return array
     */
    public function queueBind($ticket = 1, $queue = '', $exchange, $routing_key = '', $nowait = false, $arguments = array())
    {
        $writer = new AMQPWriter();
        $writer->write_short($ticket);
        $writer->write_shortstr($queue);
        $writer->write_shortstr($exchange);
        $writer->write_shortstr($routing_key);
        $writer->write_bits(array($nowait));
        $writer->write_table(empty($arguments) ? array() : $arguments);
        return array(50, 20, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function queueBindOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**
     * @param int $ticket
     * @param string $queue
     * @param bool $nowait
     * @return array
     */
    public function queuePurge($ticket = 1, $queue = '', $nowait = false)
    {
        $writer = new AMQPWriter();
        $writer->write_short($ticket);
        $writer->write_shortstr($queue);
        $writer->write_bits(array($nowait));
        return array(50, 30, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function queuePurgeOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_long();
        return $response;
    }

    /**
     * @param int $ticket
     * @param string $queue
     * @param bool $if_unused
     * @param bool $if_empty
     * @param bool $nowait
     * @return array
     */
    public function queueDelete($ticket = 1, $queue = '', $if_unused = false, $if_empty = false, $nowait = false)
    {
        $writer = new AMQPWriter();
        $writer->write_short($ticket);
        $writer->write_shortstr($queue);
        $writer->write_bits(array($if_unused, $if_empty, $nowait));
        return array(50, 40, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function queueDeleteOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_long();
        return $response;
    }

    /**
     * @param int $ticket
     * @param string $queue
     * @param string $exchange
     * @param string $routing_key
     * @param array $arguments
     * @return array
     */
    public function queueUnbind($ticket = 1, $queue = '', $exchange, $routing_key = '', $arguments = array())
    {
        $writer = new AMQPWriter();
        $writer->write_short($ticket);
        $writer->write_shortstr($queue);
        $writer->write_shortstr($exchange);
        $writer->write_shortstr($routing_key);
        $writer->write_table(empty($arguments) ? array() : $arguments);
        return array(50, 50, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function queueUnbindOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**
     * @param int $prefetch_size
     * @param int $prefetch_count
     * @param bool $global
     * @return array
     */
    public function basicQos($prefetch_size = 0, $prefetch_count = 0, $global = false)
    {
        $writer = new AMQPWriter();
        $writer->write_long($prefetch_size);
        $writer->write_short($prefetch_count);
        $writer->write_bits(array($global));
        return array(60, 10, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function basicQosOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**
     * @param int $ticket
     * @param string $queue
     * @param string $consumer_tag
     * @param bool $no_local
     * @param bool $no_ack
     * @param bool $exclusive
     * @param bool $nowait
     * @return array
     */
    public function basicConsume($ticket = 1, $queue = '', $consumer_tag = '', $no_local = false, $no_ack = false, $exclusive = false, $nowait = false)
    {
        $writer = new AMQPWriter();
        $writer->write_short($ticket);
        $writer->write_shortstr($queue);
        $writer->write_shortstr($consumer_tag);
        $writer->write_bits(array($no_local, $no_ack, $exclusive, $nowait));
        return array(60, 20, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function basicConsumeOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_shortstr();
        return $response;
    }

    /**
     * @param string $consumer_tag
     * @param bool $nowait
     * @return array
     */
    public function basicCancel($consumer_tag, $nowait = false)
    {
        $writer = new AMQPWriter();
        $writer->write_shortstr($consumer_tag);
        $writer->write_bits(array($nowait));
        return array(60, 30, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function basicCancelOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_shortstr();
        return $response;
    }

    /**
     * @param int $ticket
     * @param string $exchange
     * @param string $routing_key
     * @param bool $mandatory
     * @param bool $immediate
     * @return array
     */
    public function basicPublish($ticket = 1, $exchange = '', $routing_key = '', $mandatory = false, $immediate = false)
    {
        $writer = new AMQPWriter();
        $writer->write_short($ticket);
        $writer->write_shortstr($exchange);
        $writer->write_shortstr($routing_key);
        $writer->write_bits(array($mandatory, $immediate));
        return array(60, 40, $writer);
    }

    /**
     * @param int $reply_code
     * @param string $reply_text
     * @param string $exchange
     * @param string $routing_key
     * @return array
     */
    public function basicReturn($reply_code, $reply_text = '', $exchange, $routing_key)
    {
        $writer = new AMQPWriter();
        $writer->write_short($reply_code);
        $writer->write_shortstr($reply_text);
        $writer->write_shortstr($exchange);
        $writer->write_shortstr($routing_key);
        return array(60, 50, $writer);
    }

    /**
     * @param string $consumer_tag
     * @param int $delivery_tag
     * @param bool $redelivered
     * @param string $exchange
     * @param string $routing_key
     * @return array
     */
    public function basicDeliver($consumer_tag, $delivery_tag, $redelivered = false, $exchange, $routing_key)
    {
        $writer = new AMQPWriter();
        $writer->write_shortstr($consumer_tag);
        $writer->write_longlong($delivery_tag);
        $writer->write_bits(array($redelivered));
        $writer->write_shortstr($exchange);
        $writer->write_shortstr($routing_key);
        return array(60, 60, $writer);
    }

    /**
     * @param int $ticket
     * @param string $queue
     * @param bool $no_ack
     * @return array
     */
    public function basicGet($ticket = 1, $queue = '', $no_ack = false)
    {
        $writer = new AMQPWriter();
        $writer->write_short($ticket);
        $writer->write_shortstr($queue);
        $writer->write_bits(array($no_ack));
        return array(60, 70, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function basicGetOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_longlong();
        $response[] = $reader->read_bit();
        $response[] = $reader->read_shortstr();
        $response[] = $reader->read_shortstr();
        $response[] = $reader->read_long();
        return $response;
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function basicGetEmpty(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_shortstr();
        return $response;
    }

    /**
     * @param int $delivery_tag
     * @param bool $multiple
     * @return array
     */
    public function basicAck($delivery_tag = 0, $multiple = false)
    {
        $writer = new AMQPWriter();
        $writer->write_longlong($delivery_tag);
        $writer->write_bits(array($multiple));
        return array(60, 80, $writer);
    }

    /**
     * @param int $delivery_tag
     * @param bool $requeue
     * @return array
     */
    public function basicReject($delivery_tag, $requeue = true)
    {
        $writer = new AMQPWriter();
        $writer->write_longlong($delivery_tag);
        $writer->write_bits(array($requeue));
        return array(60, 90, $writer);
    }

    /**
     * @param bool $requeue
     * @return array
     */
    public function basicRecoverAsync($requeue = false)
    {
        $writer = new AMQPWriter();
        $writer->write_bits(array($requeue));
        return array(60, 100, $writer);
    }

    /**
     * @param bool $requeue
     * @return array
     */
    public function basicRecover($requeue = false)
    {
        $writer = new AMQPWriter();
        $writer->write_bits(array($requeue));
        return array(60, 110, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function basicRecoverOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**
     * @param int $prefetch_size
     * @param int $prefetch_count
     * @param bool $global
     * @return array
     */
    public function fileQos($prefetch_size = 0, $prefetch_count = 0, $global = false)
    {
        $writer = new AMQPWriter();
        $writer->write_long($prefetch_size);
        $writer->write_short($prefetch_count);
        $writer->write_bits(array($global));
        return array(70, 10, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function fileQosOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**
     * @param int $ticket
     * @param string $queue
     * @param string $consumer_tag
     * @param bool $no_local
     * @param bool $no_ack
     * @param bool $exclusive
     * @param bool $nowait
     * @return array
     */
    public function fileConsume($ticket = 1, $queue = '', $consumer_tag = '', $no_local = false, $no_ack = false, $exclusive = false, $nowait = false)
    {
        $writer = new AMQPWriter();
        $writer->write_short($ticket);
        $writer->write_shortstr($queue);
        $writer->write_shortstr($consumer_tag);
        $writer->write_bits(array($no_local, $no_ack, $exclusive, $nowait));
        return array(70, 20, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function fileConsumeOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_shortstr();
        return $response;
    }

    /**
     * @param string $consumer_tag
     * @param bool $nowait
     * @return array
     */
    public function fileCancel($consumer_tag, $nowait = false)
    {
        $writer = new AMQPWriter();
        $writer->write_shortstr($consumer_tag);
        $writer->write_bits(array($nowait));
        return array(70, 30, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function fileCancelOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_shortstr();
        return $response;
    }

    /**
     * @param string $identifier
     * @param int $content_size
     * @return array
     */
    public function fileOpen($identifier, $content_size)
    {
        $writer = new AMQPWriter();
        $writer->write_shortstr($identifier);
        $writer->write_longlong($content_size);
        return array(70, 40, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function fileOpenOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_longlong();
        return $response;
    }

    /**

     * @return array
     */
    public function fileStage()
    {
        $writer = new AMQPWriter();
        return array(70, 50, $writer);
    }

    /**
     * @param int $ticket
     * @param string $exchange
     * @param string $routing_key
     * @param bool $mandatory
     * @param bool $immediate
     * @param string $identifier
     * @return array
     */
    public function filePublish($ticket = 1, $exchange = '', $routing_key = '', $mandatory = false, $immediate = false, $identifier)
    {
        $writer = new AMQPWriter();
        $writer->write_short($ticket);
        $writer->write_shortstr($exchange);
        $writer->write_shortstr($routing_key);
        $writer->write_bits(array($mandatory, $immediate));
        $writer->write_shortstr($identifier);
        return array(70, 60, $writer);
    }

    /**
     * @param int $reply_code
     * @param string $reply_text
     * @param string $exchange
     * @param string $routing_key
     * @return array
     */
    public function fileReturn($reply_code = 200, $reply_text = '', $exchange, $routing_key)
    {
        $writer = new AMQPWriter();
        $writer->write_short($reply_code);
        $writer->write_shortstr($reply_text);
        $writer->write_shortstr($exchange);
        $writer->write_shortstr($routing_key);
        return array(70, 70, $writer);
    }

    /**
     * @param string $consumer_tag
     * @param int $delivery_tag
     * @param bool $redelivered
     * @param string $exchange
     * @param string $routing_key
     * @param string $identifier
     * @return array
     */
    public function fileDeliver($consumer_tag, $delivery_tag, $redelivered = false, $exchange, $routing_key, $identifier)
    {
        $writer = new AMQPWriter();
        $writer->write_shortstr($consumer_tag);
        $writer->write_longlong($delivery_tag);
        $writer->write_bits(array($redelivered));
        $writer->write_shortstr($exchange);
        $writer->write_shortstr($routing_key);
        $writer->write_shortstr($identifier);
        return array(70, 80, $writer);
    }

    /**
     * @param int $delivery_tag
     * @param bool $multiple
     * @return array
     */
    public function fileAck($delivery_tag = 0, $multiple = false)
    {
        $writer = new AMQPWriter();
        $writer->write_longlong($delivery_tag);
        $writer->write_bits(array($multiple));
        return array(70, 90, $writer);
    }

    /**
     * @param int $delivery_tag
     * @param bool $requeue
     * @return array
     */
    public function fileReject($delivery_tag, $requeue = true)
    {
        $writer = new AMQPWriter();
        $writer->write_longlong($delivery_tag);
        $writer->write_bits(array($requeue));
        return array(70, 100, $writer);
    }

    /**
     * @param int $prefetch_size
     * @param int $prefetch_count
     * @param int $consume_rate
     * @param bool $global
     * @return array
     */
    public function streamQos($prefetch_size = 0, $prefetch_count = 0, $consume_rate = 0, $global = false)
    {
        $writer = new AMQPWriter();
        $writer->write_long($prefetch_size);
        $writer->write_short($prefetch_count);
        $writer->write_long($consume_rate);
        $writer->write_bits(array($global));
        return array(80, 10, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function streamQosOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**
     * @param int $ticket
     * @param string $queue
     * @param string $consumer_tag
     * @param bool $no_local
     * @param bool $exclusive
     * @param bool $nowait
     * @return array
     */
    public function streamConsume($ticket = 1, $queue = '', $consumer_tag = '', $no_local = false, $exclusive = false, $nowait = false)
    {
        $writer = new AMQPWriter();
        $writer->write_short($ticket);
        $writer->write_shortstr($queue);
        $writer->write_shortstr($consumer_tag);
        $writer->write_bits(array($no_local, $exclusive, $nowait));
        return array(80, 20, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function streamConsumeOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_shortstr();
        return $response;
    }

    /**
     * @param string $consumer_tag
     * @param bool $nowait
     * @return array
     */
    public function streamCancel($consumer_tag, $nowait = false)
    {
        $writer = new AMQPWriter();
        $writer->write_shortstr($consumer_tag);
        $writer->write_bits(array($nowait));
        return array(80, 30, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function streamCancelOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_shortstr();
        return $response;
    }

    /**
     * @param int $ticket
     * @param string $exchange
     * @param string $routing_key
     * @param bool $mandatory
     * @param bool $immediate
     * @return array
     */
    public function streamPublish($ticket = 1, $exchange = '', $routing_key = '', $mandatory = false, $immediate = false)
    {
        $writer = new AMQPWriter();
        $writer->write_short($ticket);
        $writer->write_shortstr($exchange);
        $writer->write_shortstr($routing_key);
        $writer->write_bits(array($mandatory, $immediate));
        return array(80, 40, $writer);
    }

    /**
     * @param int $reply_code
     * @param string $reply_text
     * @param string $exchange
     * @param string $routing_key
     * @return array
     */
    public function streamReturn($reply_code = 200, $reply_text = '', $exchange, $routing_key)
    {
        $writer = new AMQPWriter();
        $writer->write_short($reply_code);
        $writer->write_shortstr($reply_text);
        $writer->write_shortstr($exchange);
        $writer->write_shortstr($routing_key);
        return array(80, 50, $writer);
    }

    /**
     * @param string $consumer_tag
     * @param int $delivery_tag
     * @param string $exchange
     * @param string $queue
     * @return array
     */
    public function streamDeliver($consumer_tag, $delivery_tag, $exchange, $queue)
    {
        $writer = new AMQPWriter();
        $writer->write_shortstr($consumer_tag);
        $writer->write_longlong($delivery_tag);
        $writer->write_shortstr($exchange);
        $writer->write_shortstr($queue);
        return array(80, 60, $writer);
    }

    /**

     * @return array
     */
    public function txSelect()
    {
        $writer = new AMQPWriter();
        return array(90, 10, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function txSelectOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**

     * @return array
     */
    public function txCommit()
    {
        $writer = new AMQPWriter();
        return array(90, 20, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function txCommitOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**

     * @return array
     */
    public function txRollback()
    {
        $writer = new AMQPWriter();
        return array(90, 30, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function txRollbackOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**

     * @return array
     */
    public function dtxSelect()
    {
        $writer = new AMQPWriter();
        return array(100, 10, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function dtxSelectOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**
     * @param string $dtx_identifier
     * @return array
     */
    public function dtxStart($dtx_identifier)
    {
        $writer = new AMQPWriter();
        $writer->write_shortstr($dtx_identifier);
        return array(100, 20, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function dtxStartOk(AMQPReader $reader)
    {
        $response = array();
        return $response;
    }

    /**
     * @param mixed $meta_data
     * @return array
     */
    public function tunnelRequest($meta_data)
    {
        $writer = new AMQPWriter();
        $writer->write_table(empty($meta_data) ? array() : $meta_data);
        return array(110, 10, $writer);
    }

    /**
     * @param mixed $integer_1
     * @param int $integer_2
     * @param int $integer_3
     * @param int $integer_4
     * @param mixed $operation
     * @return array
     */
    public function testInteger($integer_1, $integer_2, $integer_3, $integer_4, $operation)
    {
        $writer = new AMQPWriter();
        $writer->write_octet($integer_1);
        $writer->write_short($integer_2);
        $writer->write_long($integer_3);
        $writer->write_longlong($integer_4);
        $writer->write_octet($operation);
        return array(120, 10, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function testIntegerOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_longlong();
        return $response;
    }

    /**
     * @param string $string_1
     * @param string $string_2
     * @param mixed $operation
     * @return array
     */
    public function testString($string_1, $string_2, $operation)
    {
        $writer = new AMQPWriter();
        $writer->write_shortstr($string_1);
        $writer->write_longstr($string_2);
        $writer->write_octet($operation);
        return array(120, 20, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function testStringOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_longstr();
        return $response;
    }

    /**
     * @param mixed $table
     * @param mixed $integer_op
     * @param mixed $string_op
     * @return array
     */
    public function testTable($table, $integer_op, $string_op)
    {
        $writer = new AMQPWriter();
        $writer->write_table(empty($table) ? array() : $table);
        $writer->write_octet($integer_op);
        $writer->write_octet($string_op);
        return array(120, 30, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function testTableOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_longlong();
        $response[] = $reader->read_longstr();
        return $response;
    }

    /**

     * @return array
     */
    public function testContent()
    {
        $writer = new AMQPWriter();
        return array(120, 40, $writer);
    }

    /**
     * @param AMQPReader $reader
     * @return array
     */
    public static function testContentOk(AMQPReader $reader)
    {
        $response = array();
        $response[] = $reader->read_long();
        return $response;
    }
}
<?php

/* This file was autogenerated by spec/parser.php - Do not modify */

namespace PhpAmqpLib\Helper\Protocol;

class Wait091
{
    /**
     * @var array
     */
    protected $wait = array(
        'connection.start' => '10,10',
        'connection.start_ok' => '10,11',
        'connection.secure' => '10,20',
        'connection.secure_ok' => '10,21',
        'connection.tune' => '10,30',
        'connection.tune_ok' => '10,31',
        'connection.open' => '10,40',
        'connection.open_ok' => '10,41',
        'connection.close' => '10,50',
        'connection.close_ok' => '10,51',
        'connection.blocked' => '10,60',
        'connection.unblocked' => '10,61',
        'channel.open' => '20,10',
        'channel.open_ok' => '20,11',
        'channel.flow' => '20,20',
        'channel.flow_ok' => '20,21',
        'channel.close' => '20,40',
        'channel.close_ok' => '20,41',
        'access.request' => '30,10',
        'access.request_ok' => '30,11',
        'exchange.declare' => '40,10',
        'exchange.declare_ok' => '40,11',
        'exchange.delete' => '40,20',
        'exchange.delete_ok' => '40,21',
        'exchange.bind' => '40,30',
        'exchange.bind_ok' => '40,31',
        'exchange.unbind' => '40,40',
        'exchange.unbind_ok' => '40,51',
        'queue.declare' => '50,10',
        'queue.declare_ok' => '50,11',
        'queue.bind' => '50,20',
        'queue.bind_ok' => '50,21',
        'queue.purge' => '50,30',
        'queue.purge_ok' => '50,31',
        'queue.delete' => '50,40',
        'queue.delete_ok' => '50,41',
        'queue.unbind' => '50,50',
        'queue.unbind_ok' => '50,51',
        'basic.qos' => '60,10',
        'basic.qos_ok' => '60,11',
        'basic.consume' => '60,20',
        'basic.consume_ok' => '60,21',
        'basic.cancel' => '60,30',
        'basic.cancel_ok' => '60,31',
        'basic.publish' => '60,40',
        'basic.return' => '60,50',
        'basic.deliver' => '60,60',
        'basic.get' => '60,70',
        'basic.get_ok' => '60,71',
        'basic.get_empty' => '60,72',
        'basic.ack' => '60,80',
        'basic.reject' => '60,90',
        'basic.recover_async' => '60,100',
        'basic.recover' => '60,110',
        'basic.recover_ok' => '60,111',
        'basic.nack' => '60,120',
        'tx.select' => '90,10',
        'tx.select_ok' => '90,11',
        'tx.commit' => '90,20',
        'tx.commit_ok' => '90,21',
        'tx.rollback' => '90,30',
        'tx.rollback_ok' => '90,31',
        'confirm.select' => '85,10',
        'confirm.select_ok' => '85,11',
    );

    /**
     * @var string $method
     * @return string
     */
    public function get_wait($method)
    {
        return $this->wait[$method];
    }
}
<?php

/* This file was autogenerated by spec/parser.php - Do not modify */

namespace PhpAmqpLib\Helper\Protocol;

class MethodMap091
{
    /**
     * @var array
     */
    protected $method_map = array(
        '10,10' => 'connection_start',
        '10,11' => 'connection_start_ok',
        '10,20' => 'connection_secure',
        '10,21' => 'connection_secure_ok',
        '10,30' => 'connection_tune',
        '10,31' => 'connection_tune_ok',
        '10,40' => 'connection_open',
        '10,41' => 'connection_open_ok',
        '10,50' => 'connection_close',
        '10,51' => 'connection_close_ok',
        '10,60' => 'connection_blocked',
        '10,61' => 'connection_unblocked',
        '20,10' => 'channel_open',
        '20,11' => 'channel_open_ok',
        '20,20' => 'channel_flow',
        '20,21' => 'channel_flow_ok',
        '20,40' => 'channel_close',
        '20,41' => 'channel_close_ok',
        '30,10' => 'access_request',
        '30,11' => 'access_request_ok',
        '40,10' => 'exchange_declare',
        '40,11' => 'exchange_declare_ok',
        '40,20' => 'exchange_delete',
        '40,21' => 'exchange_delete_ok',
        '40,30' => 'exchange_bind',
        '40,31' => 'exchange_bind_ok',
        '40,40' => 'exchange_unbind',
        '40,51' => 'exchange_unbind_ok',
        '50,10' => 'queue_declare',
        '50,11' => 'queue_declare_ok',
        '50,20' => 'queue_bind',
        '50,21' => 'queue_bind_ok',
        '50,30' => 'queue_purge',
        '50,31' => 'queue_purge_ok',
        '50,40' => 'queue_delete',
        '50,41' => 'queue_delete_ok',
        '50,50' => 'queue_unbind',
        '50,51' => 'queue_unbind_ok',
        '60,10' => 'basic_qos',
        '60,11' => 'basic_qos_ok',
        '60,20' => 'basic_consume',
        '60,21' => 'basic_consume_ok',
        '60,30' => 'basic_cancel_from_server',
        '60,31' => 'basic_cancel_ok',
        '60,40' => 'basic_publish',
        '60,50' => 'basic_return',
        '60,60' => 'basic_deliver',
        '60,70' => 'basic_get',
        '60,71' => 'basic_get_ok',
        '60,72' => 'basic_get_empty',
        '60,80' => 'basic_ack_from_server',
        '60,90' => 'basic_reject',
        '60,100' => 'basic_recover_async',
        '60,110' => 'basic_recover',
        '60,111' => 'basic_recover_ok',
        '60,120' => 'basic_nack_from_server',
        '90,10' => 'tx_select',
        '90,11' => 'tx_select_ok',
        '90,20' => 'tx_commit',
        '90,21' => 'tx_commit_ok',
        '90,30' => 'tx_rollback',
        '90,31' => 'tx_rollback_ok',
        '85,10' => 'confirm_select',
        '85,11' => 'confirm_select_ok',
    );

    /**
     * @var string $method_sig
     * @return string
     */
    public function get_method($method_sig)
    {
        return $this->method_map[$method_sig];
    }

    /**
     * @var string $method_sig
     * @return bool
     */
    public function valid_method($method_sig)
    {
        return array_key_exists($method_sig, $this->method_map);
    }
}
<?php

/* This file was autogenerated by spec/parser.php - Do not modify */

namespace PhpAmqpLib\Helper\Protocol;

class MethodMap080
{
    /**
     * @var array
     */
    protected $method_map = array(
        '10,10' => 'connection_start',
        '10,11' => 'connection_start_ok',
        '10,20' => 'connection_secure',
        '10,21' => 'connection_secure_ok',
        '10,30' => 'connection_tune',
        '10,31' => 'connection_tune_ok',
        '10,40' => 'connection_open',
        '10,41' => 'connection_open_ok',
        '10,50' => 'connection_redirect',
        '10,60' => 'connection_close',
        '10,61' => 'connection_close_ok',
        '20,10' => 'channel_open',
        '20,11' => 'channel_open_ok',
        '20,20' => 'channel_flow',
        '20,21' => 'channel_flow_ok',
        '20,30' => 'channel_alert',
        '20,40' => 'channel_close',
        '20,41' => 'channel_close_ok',
        '30,10' => 'access_request',
        '30,11' => 'access_request_ok',
        '40,10' => 'exchange_declare',
        '40,11' => 'exchange_declare_ok',
        '40,20' => 'exchange_delete',
        '40,21' => 'exchange_delete_ok',
        '50,10' => 'queue_declare',
        '50,11' => 'queue_declare_ok',
        '50,20' => 'queue_bind',
        '50,21' => 'queue_bind_ok',
        '50,30' => 'queue_purge',
        '50,31' => 'queue_purge_ok',
        '50,40' => 'queue_delete',
        '50,41' => 'queue_delete_ok',
        '50,50' => 'queue_unbind',
        '50,51' => 'queue_unbind_ok',
        '60,10' => 'basic_qos',
        '60,11' => 'basic_qos_ok',
        '60,20' => 'basic_consume',
        '60,21' => 'basic_consume_ok',
        '60,30' => 'basic_cancel',
        '60,31' => 'basic_cancel_ok',
        '60,40' => 'basic_publish',
        '60,50' => 'basic_return',
        '60,60' => 'basic_deliver',
        '60,70' => 'basic_get',
        '60,71' => 'basic_get_ok',
        '60,72' => 'basic_get_empty',
        '60,80' => 'basic_ack',
        '60,90' => 'basic_reject',
        '60,100' => 'basic_recover_async',
        '60,110' => 'basic_recover',
        '60,111' => 'basic_recover_ok',
        '70,10' => 'file_qos',
        '70,11' => 'file_qos_ok',
        '70,20' => 'file_consume',
        '70,21' => 'file_consume_ok',
        '70,30' => 'file_cancel',
        '70,31' => 'file_cancel_ok',
        '70,40' => 'file_open',
        '70,41' => 'file_open_ok',
        '70,50' => 'file_stage',
        '70,60' => 'file_publish',
        '70,70' => 'file_return',
        '70,80' => 'file_deliver',
        '70,90' => 'file_ack',
        '70,100' => 'file_reject',
        '80,10' => 'stream_qos',
        '80,11' => 'stream_qos_ok',
        '80,20' => 'stream_consume',
        '80,21' => 'stream_consume_ok',
        '80,30' => 'stream_cancel',
        '80,31' => 'stream_cancel_ok',
        '80,40' => 'stream_publish',
        '80,50' => 'stream_return',
        '80,60' => 'stream_deliver',
        '90,10' => 'tx_select',
        '90,11' => 'tx_select_ok',
        '90,20' => 'tx_commit',
        '90,21' => 'tx_commit_ok',
        '90,30' => 'tx_rollback',
        '90,31' => 'tx_rollback_ok',
        '100,10' => 'dtx_select',
        '100,11' => 'dtx_select_ok',
        '100,20' => 'dtx_start',
        '100,21' => 'dtx_start_ok',
        '110,10' => 'tunnel_request',
        '120,10' => 'test_integer',
        '120,11' => 'test_integer_ok',
        '120,20' => 'test_string',
        '120,21' => 'test_string_ok',
        '120,30' => 'test_table',
        '120,31' => 'test_table_ok',
        '120,40' => 'test_content',
        '120,41' => 'test_content_ok',
    );

    /**
     * @var string $method_sig
     * @return string
     */
    public function get_method($method_sig)
    {
        return $this->method_map[$method_sig];
    }

    /**
     * @var string $method_sig
     * @return bool
     */
    public function valid_method($method_sig)
    {
        return array_key_exists($method_sig, $this->method_map);
    }
}
<?php

/* This file was autogenerated by spec/parser.php - Do not modify */

namespace PhpAmqpLib\Helper\Protocol;

class Wait080
{
    /**
     * @var array
     */
    protected $wait = array(
        'connection.start' => '10,10',
        'connection.start_ok' => '10,11',
        'connection.secure' => '10,20',
        'connection.secure_ok' => '10,21',
        'connection.tune' => '10,30',
        'connection.tune_ok' => '10,31',
        'connection.open' => '10,40',
        'connection.open_ok' => '10,41',
        'connection.redirect' => '10,50',
        'connection.close' => '10,60',
        'connection.close_ok' => '10,61',
        'channel.open' => '20,10',
        'channel.open_ok' => '20,11',
        'channel.flow' => '20,20',
        'channel.flow_ok' => '20,21',
        'channel.alert' => '20,30',
        'channel.close' => '20,40',
        'channel.close_ok' => '20,41',
        'access.request' => '30,10',
        'access.request_ok' => '30,11',
        'exchange.declare' => '40,10',
        'exchange.declare_ok' => '40,11',
        'exchange.delete' => '40,20',
        'exchange.delete_ok' => '40,21',
        'queue.declare' => '50,10',
        'queue.declare_ok' => '50,11',
        'queue.bind' => '50,20',
        'queue.bind_ok' => '50,21',
        'queue.purge' => '50,30',
        'queue.purge_ok' => '50,31',
        'queue.delete' => '50,40',
        'queue.delete_ok' => '50,41',
        'queue.unbind' => '50,50',
        'queue.unbind_ok' => '50,51',
        'basic.qos' => '60,10',
        'basic.qos_ok' => '60,11',
        'basic.consume' => '60,20',
        'basic.consume_ok' => '60,21',
        'basic.cancel' => '60,30',
        'basic.cancel_ok' => '60,31',
        'basic.publish' => '60,40',
        'basic.return' => '60,50',
        'basic.deliver' => '60,60',
        'basic.get' => '60,70',
        'basic.get_ok' => '60,71',
        'basic.get_empty' => '60,72',
        'basic.ack' => '60,80',
        'basic.reject' => '60,90',
        'basic.recover_async' => '60,100',
        'basic.recover' => '60,110',
        'basic.recover_ok' => '60,111',
        'file.qos' => '70,10',
        'file.qos_ok' => '70,11',
        'file.consume' => '70,20',
        'file.consume_ok' => '70,21',
        'file.cancel' => '70,30',
        'file.cancel_ok' => '70,31',
        'file.open' => '70,40',
        'file.open_ok' => '70,41',
        'file.stage' => '70,50',
        'file.publish' => '70,60',
        'file.return' => '70,70',
        'file.deliver' => '70,80',
        'file.ack' => '70,90',
        'file.reject' => '70,100',
        'stream.qos' => '80,10',
        'stream.qos_ok' => '80,11',
        'stream.consume' => '80,20',
        'stream.consume_ok' => '80,21',
        'stream.cancel' => '80,30',
        'stream.cancel_ok' => '80,31',
        'stream.publish' => '80,40',
        'stream.return' => '80,50',
        'stream.deliver' => '80,60',
        'tx.select' => '90,10',
        'tx.select_ok' => '90,11',
        'tx.commit' => '90,20',
        'tx.commit_ok' => '90,21',
        'tx.rollback' => '90,30',
        'tx.rollback_ok' => '90,31',
        'dtx.select' => '100,10',
        'dtx.select_ok' => '100,11',
        'dtx.start' => '100,20',
        'dtx.start_ok' => '100,21',
        'tunnel.request' => '110,10',
        'test.integer' => '120,10',
        'test.integer_ok' => '120,11',
        'test.string' => '120,20',
        'test.string_ok' => '120,21',
        'test.table' => '120,30',
        'test.table_ok' => '120,31',
        'test.content' => '120,40',
        'test.content_ok' => '120,41',
    );

    /**
     * @var string $method
     * @return string
     */
    public function get_wait($method)
    {
        return $this->wait[$method];
    }
}
<?php
namespace PhpAmqpLib\Helper;

class DebugHelper
{
    /**
     * @var bool
     */
    protected $debug;
    
    /**
     * @var resource
     */
    protected $debug_output;
    
    /**
     * @var string
     */
    protected $PROTOCOL_CONSTANTS_CLASS;

    /**
     * @param string $PROTOCOL_CONSTANTS_CLASS
     */
    public function __construct($PROTOCOL_CONSTANTS_CLASS) {
        if(!defined('STDOUT')) define('STDOUT', fopen('php://stdout', 'w'));

        $this->debug = defined('AMQP_DEBUG') ? AMQP_DEBUG : false;
        $this->debug_output = defined('AMQP_DEBUG_OUTPUT') ? AMQP_DEBUG_OUTPUT : STDOUT;
        $this->PROTOCOL_CONSTANTS_CLASS = $PROTOCOL_CONSTANTS_CLASS;
    }

    /**
     * @param string $msg
     */
    public function debug_msg($msg) {
        if ($this->debug) {
            $this->print_msg($msg);
        }
    }

    /**
     * @param array $allowed_methods
     */
    public function debug_allowed_methods($allowed_methods) {
        if ($allowed_methods) {
            $msg = 'waiting for ' . implode(', ', $allowed_methods);
        } else {
            $msg = 'waiting for any method';
        }
        $this->debug_msg($msg);
    }

    /**
     * @param string $method_sig
     */
    public function debug_method_signature1($method_sig) {
        $this->debug_method_signature('< %s:', $method_sig);
    }

    /**
     * @param string $msg
     * @param string $method_sig
     */
    public function debug_method_signature($msg, $method_sig) {
        if ($this->debug) {
            $protocolClass = $this->PROTOCOL_CONSTANTS_CLASS;
            $this->debug_msg(sprintf(
                $msg . ': %s',
                MiscHelper::methodSig($method_sig),
                $protocolClass::$GLOBAL_METHOD_NAMES[MiscHelper::methodSig($method_sig)]
            ));
        }
    }

    /**
     * @param string $data
     */
    public function debug_hexdump($data) {
        if ($this->debug) {
            $this->debug_msg(sprintf(
                '< [hex]: %s%s',
                PHP_EOL,
                MiscHelper::hexdump($data, $htmloutput = false, $uppercase = true, $return = true)
            ));
        }
    }

    /**
     * @param int $version_major
     * @param int $version_minor
     * @param array $server_properties
     * @param array $mechanisms
     * @param array $locales
     */
    public function debug_connection_start($version_major, $version_minor, $server_properties, $mechanisms, $locales) {
        if ($this->debug) {
            $this->debug_msg(sprintf(
                'Start from server, version: %d.%d, properties: %s, mechanisms: %s, locales: %s',
                $version_major,
                $version_minor,
                MiscHelper::dump_table($server_properties),
                implode(', ', $mechanisms),
                implode(', ', $locales)
            ));
        }
    }

    /**
     * @param string $s
     */
    protected function print_msg($s) {
        fwrite($this->debug_output, $s . PHP_EOL);
    }
}
<?php
namespace PhpAmqpLib\Exception;

interface AMQPExceptionInterface
{
}
<?php
namespace PhpAmqpLib\Exception;

/**
 * When connection was closed by server, proxy or some tunnel due to timeout or network issue.
 */
class AMQPConnectionClosedException extends AMQPRuntimeException
{
}
<?php
namespace PhpAmqpLib\Exception;

class AMQPBasicCancelException extends \Exception implements AMQPExceptionInterface
{
    /** @var string */
    public $consumerTag;

    /**
     * @param string $consumerTag
     */
    public function __construct($consumerTag)
    {
        $this->consumerTag = $consumerTag;
    }
}
<?php
namespace PhpAmqpLib\Exception;

//TODO refactor usage of static methods
use PhpAmqpLib\Channel\AbstractChannel;
use PhpAmqpLib\Helper\MiscHelper;

/**
 * @deprecated use AMQPProtocolException instead
 */
class AMQPException extends \Exception
{
    /** @var string */
    public $amqp_reply_code;

    /** @var int */
    public $amqp_reply_text;

    /** @var \Exception */
    public $amqp_method_sig;

    /** @var array */
    public $args;

    /**
     * @param string $reply_code
     * @param int $reply_text
     * @param array $method_sig
     */
    public function __construct($reply_code, $reply_text, $method_sig)
    {
        parent::__construct($reply_text, $reply_code);

        $this->amqp_reply_code = $reply_code; // redundant, but kept for BC
        $this->amqp_reply_text = $reply_text; // redundant, but kept for BC
        $this->amqp_method_sig = $method_sig;

        $ms = MiscHelper::methodSig($method_sig);
        $protocolClass = AbstractChannel::$PROTOCOL_CONSTANTS_CLASS;
        $mn = isset($protocolClass::$GLOBAL_METHOD_NAMES[$ms])
            ? $protocolClass::$GLOBAL_METHOD_NAMES[$ms]
            : $mn = '';

        $this->args = array($reply_code, $reply_text, $method_sig, $mn);
    }
}
<?php
namespace PhpAmqpLib\Exception;

class AMQPInvalidFrameException extends AMQPRuntimeException
{
}
<?php
namespace PhpAmqpLib\Exception;

class AMQPDataReadException extends AMQPRuntimeException
{
}
<?php
namespace PhpAmqpLib\Exception;

//TODO refactor usage of static methods
use PhpAmqpLib\Channel\AbstractChannel;
use PhpAmqpLib\Helper\MiscHelper;

class AMQPProtocolException extends \Exception implements AMQPExceptionInterface
{
    /** @var string */
    public $amqp_reply_code;

    /** @var int */
    public $amqp_reply_text;

    /** @var \Exception */
    public $amqp_method_sig;

    /** @var array */
    public $args;

    /**
     * @param string $reply_code
     * @param int $reply_text
     * @param array $method_sig
     */
    public function __construct($reply_code, $reply_text, $method_sig)
    {
        parent::__construct($reply_text, $reply_code);

        $this->amqp_reply_code = $reply_code; // redundant, but kept for BC
        $this->amqp_reply_text = $reply_text; // redundant, but kept for BC
        $this->amqp_method_sig = $method_sig;

        $ms = MiscHelper::methodSig($method_sig);

        $protocolClass = AbstractChannel::$PROTOCOL_CONSTANTS_CLASS;
        $mn = isset($protocolClass::$GLOBAL_METHOD_NAMES[$ms])
            ? $protocolClass::$GLOBAL_METHOD_NAMES[$ms]
            : $mn = '';

        $this->args = array($reply_code, $reply_text, $method_sig, $mn);
    }
}
<?php
namespace PhpAmqpLib\Exception;

class AMQPLogicException extends \LogicException implements AMQPExceptionInterface
{

}
<?php

namespace PhpAmqpLib\Exception;

class AMQPTimeoutException extends \RuntimeException implements AMQPExceptionInterface
{
    /**
     * @var int|float|null
     */
    private $timeout;

    public function __construct($message = '', $timeout = 0, $code = 0, \Exception $previous = null)
    {
        parent::__construct($message, $code, $previous);
        $this->timeout = $timeout;
    }

    /**
     * @param int|float|null $timeout
     * @param int $code
     * @return self
     */
    public static function writeTimeout($timeout, $code = 0)
    {
        return new self('Error sending data. Connection timed out.', $timeout, $code);
    }

    /**
     * @return int|float|null
     */
    public function getTimeout()
    {
        return $this->timeout;
    }
}
<?php
namespace PhpAmqpLib\Exception;

class AMQPProtocolChannelException extends AMQPProtocolException
{
}
<?php
namespace PhpAmqpLib\Exception;

class AMQPProtocolConnectionException extends AMQPProtocolException
{
}
<?php
namespace PhpAmqpLib\Exception;

class AMQPInvalidArgumentException extends \RuntimeException implements AMQPExceptionInterface
{
}
<?php
namespace PhpAmqpLib\Exception;

class AMQPIOWaitException extends AMQPRuntimeException
{
}
<?php
namespace PhpAmqpLib\Exception;

class AMQPRuntimeException extends \RuntimeException implements AMQPExceptionInterface
{
}
<?php
namespace PhpAmqpLib\Exception;

class AMQPOutOfBoundsException extends \OutOfBoundsException implements AMQPExceptionInterface
{
}
<?php
namespace PhpAmqpLib\Exception;

class AMQPChannelClosedException extends AMQPRuntimeException
{
}
<?php

namespace PhpAmqpLib\Exception;

/**
 * Used mostly in non-blocking methods when no data is ready for processing.
 */
class AMQPNoDataException extends AMQPRuntimeException
{
}
<?php
namespace PhpAmqpLib\Exception;

class AMQPOutOfRangeException extends \OutOfRangeException implements AMQPExceptionInterface
{

}
<?php
namespace PhpAmqpLib\Exception;

class AMQPNotImplementedException extends AMQPRuntimeException
{
}
<?php
namespace PhpAmqpLib\Exception;

/**
 * @deprecated use AMQPProtocolChannelException instead
 */
class AMQPChannelException extends AMQPException
{
}
<?php
namespace PhpAmqpLib\Exception;

/**
 * @deprecated use AMQPProtocolConnectionException instead
 */
class AMQPConnectionException extends AMQPException
{
}
<?php
namespace PhpAmqpLib\Exception;

class AMQPIOException extends \Exception implements AMQPExceptionInterface
{
}
<?php
namespace PhpAmqpLib\Exception;

class AMQPHeartbeatMissedException extends AMQPConnectionClosedException
{
}
<?php
namespace PhpAmqpLib\Exception;

class AMQPSocketException extends AMQPRuntimeException
{
}
{
    "name": "php-amqplib/php-amqplib",
    "replace": {
        "videlalvaro/php-amqplib": "self.version"
    },
    "type": "library",
    "description": "Formerly videlalvaro/php-amqplib.  This library is a pure PHP implementation of the AMQP protocol. It's been tested against RabbitMQ.",
    "keywords": ["rabbitmq", "message", "queue"],
    "homepage": "https://github.com/php-amqplib/php-amqplib/",
    "authors": [
        {
            "name": "Alvaro Videla",
            "role": "Original Maintainer"
        },
        {
            "name": "John Kelly",
            "email": "johnmkelly86@gmail.com",
            "role": "Maintainer"
        },
        {
            "name": "RaÃºl Araya",
            "email": "nubeiro@gmail.com",
            "role": "Maintainer"
        },
        {
            "name": "Luke Bakken",
            "email": "luke@bakken.io",
            "role": "Maintainer"
        }
    ],
    "require": {
        "php": ">=5.4.0",
        "ext-bcmath": "*",
        "ext-sockets": "*"
    },
    "require-dev": {
        "ext-curl": "*",
        "phpunit/phpunit": "^4.8",
        "squizlabs/php_codesniffer": "^2.5",
        "phpdocumentor/phpdocumentor": "^2.9",
        "nategood/httpful": "^0.2.20"
    },
    "autoload": {
        "psr-4": {
            "PhpAmqpLib\\": "PhpAmqpLib/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "PhpAmqpLib\\Tests\\Functional\\": "tests/Functional",
            "PhpAmqpLib\\Tests\\Unit\\": "tests/Unit"
        }
    },
    "license": "LGPL-2.1-or-later",
    "extra": {
        "branch-alias": {
            "dev-master": "2.8-dev"
        }
    }
}
<?php

// ATTENTION! DO NOT EDIT THIS FILE!
// This file is necessary to run the unit tests and profiling scripts.
// Please copy it to 'test-settings.php' and make the necessary edits.

// Note: The only external library you *need* is SimpleTest; everything else
//       is optional.

// We've got a lot of tests, so we recommend turning the limit off.
set_time_limit(0);

// Turning off output buffering will prevent mysterious errors from core dumps.
$data = @ob_get_clean();
if ($data !== false && $data !== '') {
    echo "Output buffer contains data [".urlencode($data)."]\n";
    exit;
}

// -----------------------------------------------------------------------------
// REQUIRED SETTINGS

// Note on running SimpleTest:
//      You want the Git copy of SimpleTest, found here:
//          https://github.com/simpletest/simpletest/
//
//      If SimpleTest is borked with HTML Purifier, please contact me or
//      the SimpleTest devs; I am a developer for SimpleTest so I should be
//      able to quickly assess a fix. SimpleTest's problem is my problem!

// Where is SimpleTest located? Remember to include a trailing slash!
$simpletest_location = '/path/to/simpletest/';

// -----------------------------------------------------------------------------
// OPTIONAL SETTINGS

// Note on running PHPT:
//      Vanilla PHPT from https://github.com/tswicegood/PHPT_Core should
//      work fine on Linux w/o multitest.
//
//      To do multitest or Windows testing, you'll need some more
//      patches at https://github.com/ezyang/PHPT_Core
//
//      I haven't tested the Windows setup in a while so I don't know if
//      it still works.

// Should PHPT tests be enabled?
$GLOBALS['HTMLPurifierTest']['PHPT'] = false;

// If PHPT isn't in your Path via PEAR, set that here:
// set_include_path('/path/to/phpt/Core/src' . PATH_SEPARATOR . get_include_path());

// Where is CSSTidy located? (Include trailing slash. Leave false to disable.)
$csstidy_location    = false;

// For tests/multitest.php, which versions to test?
$versions_to_test    = array();

// Stable PHP binary to use when invoking maintenance scripts.
$php = 'php';

// For tests/multitest.php, what is the multi-version executable? It must
// accept an extra parameter (version number) before all other arguments
$phpv = false;

// Should PEAR tests be run? If you've got a valid PEAR installation, set this
// to true (or, if it's not in the include path, to its install directory).
$GLOBALS['HTMLPurifierTest']['PEAR'] = false;

// If PEAR is enabled, what PEAR tests should be run? (Note: you will
// need to ensure these libraries are installed)
$GLOBALS['HTMLPurifierTest']['Net_IDNA2'] = true;

// vim: et sw=4 sts=4
<?php

set_time_limit(0);

require_once 'PEAR/PackageFileManager2.php';
require_once 'PEAR/PackageFileManager/File.php';
PEAR::setErrorHandling(PEAR_ERROR_PRINT);
$pkg = new PEAR_PackageFileManager2;

$pkg->setOptions(
    array(
        'baseinstalldir' => '/',
        'packagefile' => 'package.xml',
        'packagedirectory' => realpath(dirname(__FILE__) . '/library'),
        'filelistgenerator' => 'file',
        'include' => array('*'),
        'dir_roles' => array('/' => 'php'), // hack to put *.ser files in the right place
        'ignore' => array(
            'HTMLPurifier.standalone.php',
            'HTMLPurifier.path.php',
            '*.tar.gz',
            '*.tgz',
            'standalone/'
        ),
    )
);

$pkg->setPackage('HTMLPurifier');
$pkg->setLicense('LGPL', 'http://www.gnu.org/licenses/lgpl.html');
$pkg->setSummary('Standards-compliant HTML filter');
$pkg->setDescription(
    'HTML Purifier is an HTML filter that will remove all malicious code
    (better known as XSS) with a thoroughly audited, secure yet permissive
    whitelist and will also make sure your documents are standards
    compliant.'
);

$pkg->addMaintainer('lead', 'ezyang', 'Edward Z. Yang', 'admin@htmlpurifier.org', 'yes');

$version = trim(file_get_contents('VERSION'));
$api_version = substr($version, 0, strrpos($version, '.'));

$pkg->setChannel('htmlpurifier.org');
$pkg->setAPIVersion($api_version);
$pkg->setAPIStability('stable');
$pkg->setReleaseVersion($version);
$pkg->setReleaseStability('stable');

$pkg->addRelease();

$pkg->setNotes(file_get_contents('WHATSNEW'));
$pkg->setPackageType('php');

$pkg->setPhpDep('5.0.0');
$pkg->setPearinstallerDep('1.4.3');

$pkg->generateContents();

$pkg->writePackageFile();

// vim: et sw=4 sts=4
<?php

function phorum_htmlpurifier_show_migrate_sigs_form()
{
    $frm = new PhorumInputForm ('', "post", "Migrate");
    $frm->hidden("module", "modsettings");
    $frm->hidden("mod", "htmlpurifier");
    $frm->hidden("migrate-sigs", "1");
    $frm->addbreak("Migrate user signatures to HTML");
    $frm->addMessage('This operation will migrate your users signatures
        to HTML. <strong>This process is irreversible and must only be performed once.</strong>
        Type in yes in the confirmation field to migrate.');
    if (!file_exists(dirname(__FILE__) . '/../migrate.php')) {
        $frm->addMessage('Migration file does not exist, cannot migrate signatures.
            Please check <tt>migrate.bbcode.php</tt> on how to create an appropriate file.');
    } else {
        $frm->addrow('Confirm:', $frm->text_box("confirmation", ""));
    }
    $frm->show();
}

// vim: et sw=4 sts=4
<?php

function phorum_htmlpurifier_show_form()
{
    if (phorum_htmlpurifier_config_file_exists()) {
        phorum_htmlpurifier_show_config_info();
        return;
    }

    global $PHORUM;

    $config = phorum_htmlpurifier_get_config();

    $frm = new PhorumInputForm ("", "post", "Save");
    $frm->hidden("module", "modsettings");
    $frm->hidden("mod", "htmlpurifier"); // this is the directory name that the Settings file lives in

    if (!empty($error)){
        echo "$error<br />";
    }

    $frm->addbreak("Edit settings for the HTML Purifier module");

    $frm->addMessage('<p>The box below sets <code>$PHORUM[\'mod_htmlpurifier\'][\'wysiwyg\']</code>.
    When checked, contents sent for edit are now purified and the
    informative message is disabled. If your WYSIWYG editor is disabled for
    admin edits, you can safely keep this unchecked.</p>');
    $frm->addRow('Use WYSIWYG?', $frm->checkbox('wysiwyg', '1', '', $PHORUM['mod_htmlpurifier']['wysiwyg']));

    $frm->addMessage('<p>The box below sets <code>$PHORUM[\'mod_htmlpurifier\'][\'suppress_message\']</code>,
    which removes the big how-to use
    HTML Purifier message.</p>');
    $frm->addRow('Suppress information?', $frm->checkbox('suppress_message', '1', '', $PHORUM['mod_htmlpurifier']['suppress_message']));

    $frm->addMessage('<p>Click on directive links to read what each option does
    (links do not open in new windows).</p>
    <p>For more flexibility (for instance, you want to edit the full
    range of configuration directives), you can create a <tt>config.php</tt>
    file in your <tt>mods/htmlpurifier/</tt> directory. Doing so will,
    however, make the web configuration interface unavailable.</p>');

    require_once 'HTMLPurifier/Printer/ConfigForm.php';
    $htmlpurifier_form = new HTMLPurifier_Printer_ConfigForm('config', 'http://htmlpurifier.org/live/configdoc/plain.html#%s');
    $htmlpurifier_form->setTextareaDimensions(23, 7); // widen a little, since we have space

    $frm->addMessage($htmlpurifier_form->render(
        $config, $PHORUM['mod_htmlpurifier']['directives'], false));

    $frm->addMessage("<strong>Warning: Changing HTML Purifier's configuration will invalidate
      the cache. Expect to see a flurry of database activity after you change
      any of these settings.</strong>");

    $frm->addrow('Reset to defaults:', $frm->checkbox("reset", "1", "", false));

    // hack to include extra styling
    echo '<style type="text/css">' . $htmlpurifier_form->getCSS() . '
    .hp-config {margin-left:auto;margin-right:auto;}
    </style>';
    $js = $htmlpurifier_form->getJavaScript();
    echo '<script type="text/javascript">'."<!--\n$js\n//-->".'</script>';

    $frm->show();
}

function phorum_htmlpurifier_show_config_info()
{
    global $PHORUM;

    // update mod_htmlpurifier for housekeeping
    phorum_htmlpurifier_commit_settings();

    // politely tell user how to edit settings manually
?>
        <div class="input-form-td-break">How to edit settings for HTML Purifier module</div>
        <p>
          A <tt>config.php</tt> file exists in your <tt>mods/htmlpurifier/</tt>
          directory. This file contains your custom configuration: in order to
          change it, please navigate to that file and edit it accordingly.
          You can also set <code>$GLOBALS['PHORUM']['mod_htmlpurifier']['wysiwyg']</code>
          or <code>$GLOBALS['PHORUM']['mod_htmlpurifier']['suppress_message']</code>
        </p>
        <p>
          To use the web interface, delete <tt>config.php</tt> (or rename it to
          <tt>config.php.bak</tt>).
        </p>
        <p>
          <strong>Warning: Changing HTML Purifier's configuration will invalidate
          the cache. Expect to see a flurry of database activity after you change
          any of these settings.</strong>
        </p>
<?php

}

// vim: et sw=4 sts=4
<?php

function phorum_htmlpurifier_migrate_sigs_check()
{
    global $PHORUM;
    $offset = 0;
    if (!empty($_POST['migrate-sigs'])) {
        if (!isset($_POST['confirmation']) || strtolower($_POST['confirmation']) !== 'yes') {
            echo 'Invalid confirmation code.';
            exit;
        }
        $PHORUM['mod_htmlpurifier']['migrate-sigs'] = true;
        phorum_db_update_settings(array("mod_htmlpurifier"=>$PHORUM["mod_htmlpurifier"]));
        $offset = 1;
    } elseif (!empty($_GET['migrate-sigs']) && $PHORUM['mod_htmlpurifier']['migrate-sigs']) {
        $offset = (int) $_GET['migrate-sigs'];
    }
    return $offset;
}

function phorum_htmlpurifier_migrate_sigs($offset)
{
    global $PHORUM;

    if(!$offset) return; // bail out quick if $offset == 0

    // theoretically, we could get rid of this multi-request
    // doo-hickery if safe mode is off
    @set_time_limit(0); // attempt to let this run
    $increment = $PHORUM['mod_htmlpurifier']['migrate-sigs-increment'];

    require_once(dirname(__FILE__) . '/../migrate.php');
    // migrate signatures
    // do this in batches so we don't run out of time/space
    $end = $offset + $increment;
    $user_ids = array();
    for ($i = $offset; $i < $end; $i++) {
        $user_ids[] = $i;
    }
    $userinfos = phorum_db_user_get_fields($user_ids, 'signature');
    foreach ($userinfos as $i => $user) {
        if (empty($user['signature'])) continue;
        $sig = $user['signature'];
        // perform standard Phorum processing on the sig
        $sig = str_replace(array("&","<",">"), array("&amp;","&lt;","&gt;"), $sig);
        $sig = preg_replace("/<((http|https|ftp):\/\/[a-z0-9;\/\?:@=\&\$\-_\.\+!*'\(\),~%]+?)>/i", "$1", $sig);
        // prepare fake data to pass to migration function
        $fake_data = array(array("author"=>"", "email"=>"", "subject"=>"", 'body' => $sig));
        list($fake_message) = phorum_htmlpurifier_migrate($fake_data);
        $user['signature'] = $fake_message['body'];
        if (!phorum_api_user_save($user)) {
            exit('Error while saving user data');
        }
    }
    unset($userinfos); // free up memory

    // query for highest ID in database
    $type = $PHORUM['DBCONFIG']['type'];
    $sql = "select MAX(user_id) from {$PHORUM['user_table']}";
    $row = phorum_db_interact(DB_RETURN_ROW, $sql);
    $top_id = (int) $row[0];

    $offset += $increment;
    if ($offset > $top_id) { // test for end condition
        echo 'Migration finished';
        $PHORUM['mod_htmlpurifier']['migrate-sigs'] = false;
        phorum_htmlpurifier_commit_settings();
        return true;
    }
    $host  = $_SERVER['HTTP_HOST'];
    $uri   = rtrim(dirname($_SERVER['PHP_SELF']), '/\\');
    $extra = 'admin.php?module=modsettings&mod=htmlpurifier&migrate-sigs=' . $offset;
    // relies on output buffering to work
    header("Location: http://$host$uri/$extra");
    exit;

}

// vim: et sw=4 sts=4
<?php

function phorum_htmlpurifier_save_settings()
{
    global $PHORUM;
    if (phorum_htmlpurifier_config_file_exists()) {
        echo "Cannot update settings, <code>mods/htmlpurifier/config.php</code> already exists. To change
        settings, edit that file. To use the web form, delete that file.<br />";
    } else {
        $config = phorum_htmlpurifier_get_config(true);
        if (!isset($_POST['reset'])) $config->mergeArrayFromForm($_POST, 'config', $PHORUM['mod_htmlpurifier']['directives']);
        $PHORUM['mod_htmlpurifier']['config'] = $config->getAll();
    }
    $PHORUM['mod_htmlpurifier']['wysiwyg'] = !empty($_POST['wysiwyg']);
    $PHORUM['mod_htmlpurifier']['suppress_message'] = !empty($_POST['suppress_message']);
    if(!phorum_htmlpurifier_commit_settings()){
        $error="Database error while updating settings.";
    } else {
        echo "Settings Updated<br />";
    }
}

function phorum_htmlpurifier_commit_settings()
{
    global $PHORUM;
    return phorum_db_update_settings(array("mod_htmlpurifier"=>$PHORUM["mod_htmlpurifier"]));
}

// vim: et sw=4 sts=4
<?php

// based off of BBCode's settings file

/**
 * HTML Purifier Phorum mod settings configuration. This provides
 * a convenient web-interface for editing the most common HTML Purifier
 * configuration directives. You can also specify custom configuration
 * by creating a 'config.php' file.
 */

if(!defined("PHORUM_ADMIN")) exit;

// error reporting is good!
error_reporting(E_ALL ^ E_NOTICE);

// load library and other paraphenalia
require_once './include/admin/PhorumInputForm.php';
require_once (dirname(__FILE__) . '/htmlpurifier/HTMLPurifier.auto.php');
require_once (dirname(__FILE__) . '/init-config.php');
require_once (dirname(__FILE__) . '/settings/migrate-sigs-form.php');
require_once (dirname(__FILE__) . '/settings/migrate-sigs.php');
require_once (dirname(__FILE__) . '/settings/form.php');
require_once (dirname(__FILE__) . '/settings/save.php');

// define friendly configuration directives. you can expand this array
// to get more web-definable directives
$PHORUM['mod_htmlpurifier']['directives'] = array(
    'URI.Host', // auto-detectable
    'URI.DisableExternal',
    'URI.DisableExternalResources',
    'URI.DisableResources',
    'URI.Munge',
    'URI.HostBlacklist',
    'URI.Disable',
    'HTML.TidyLevel',
    'HTML.Doctype', // auto-detectable
    'HTML.Allowed',
    'AutoFormat',
    '-AutoFormat.Custom',
    'AutoFormatParam',
    'Output.TidyFormat',
);

// lower this setting if you're getting time outs/out of memory
$PHORUM['mod_htmlpurifier']['migrate-sigs-increment'] = 100;

if (isset($_POST['reset'])) {
    unset($PHORUM['mod_htmlpurifier']['config']);
}

if ($offset = phorum_htmlpurifier_migrate_sigs_check()) {
    // migrate signatures
    phorum_htmlpurifier_migrate_sigs($offset);
} elseif(!empty($_POST)){
    // save settings
    phorum_htmlpurifier_save_settings();
}

phorum_htmlpurifier_show_migrate_sigs_form();
echo '<br />';
phorum_htmlpurifier_show_form();

// vim: et sw=4 sts=4
<?php

/**
 * HTML Purifier Phorum Mod. Filter your HTML the Standards-Compliant Way!
 *
 * This Phorum mod enables users to post raw HTML into Phorum.  But never
 * fear: with the help of HTML Purifier, this HTML will be beat into
 * de-XSSed and standards-compliant form, safe for general consumption.
 * It is not recommended, but possible to run this mod in parallel
 * with other formatters (in short, please DISABLE the BBcode mod).
 *
 * For help migrating from your previous markup language to pure HTML
 * please check the migrate.bbcode.php file.
 *
 * If you'd like to use this with a WYSIWYG editor, make sure that
 * editor sets $PHORUM['mod_htmlpurifier']['wysiwyg'] to true. Otherwise,
 * administrators who need to edit other people's comments may be at
 * risk for some nasty attacks.
 *
 * Tested with Phorum 5.2.11.
 */

// Note: Cache data is base64 encoded because Phorum insists on flinging
// to the user and expecting it to come back unharmed, newlines and
// all, which ain't happening. It's slower, it takes up more space, but
// at least it won't get mutilated

/**
 * Purifies a data array
 */
function phorum_htmlpurifier_format($data)
{
    $PHORUM = $GLOBALS["PHORUM"];

    $purifier =& HTMLPurifier::getInstance();
    $cache_serial = $PHORUM['mod_htmlpurifier']['body_cache_serial'];

    foreach($data as $message_id => $message){
        if(isset($message['body'])) {

            if ($message_id) {
                // we're dealing with a real message, not a fake, so
                // there a number of shortcuts that can be taken

                if (isset($message['meta']['htmlpurifier_light'])) {
                    // format hook was called outside of Phorum's normal
                    // functions, do the abridged purification
                    $data[$message_id]['body'] = $purifier->purify($message['body']);
                    continue;
                }

                if (!empty($PHORUM['args']['purge'])) {
                    // purge the cache, must be below the following if
                    unset($message['meta']['body_cache']);
                }

                if (
                    isset($message['meta']['body_cache']) &&
                    isset($message['meta']['body_cache_serial']) &&
                    $message['meta']['body_cache_serial'] == $cache_serial
                ) {
                    // cached version is present, bail out early
                    $data[$message_id]['body'] = base64_decode($message['meta']['body_cache']);
                    continue;
                }
            }

            // migration might edit this array, that's why it's defined
            // so early
            $updated_message = array();

            // create the $body variable
            if (
                $message_id && // message must be real to migrate
                !isset($message['meta']['body_cache_serial'])
            ) {
                // perform migration
                $fake_data = array();
                list($signature, $edit_message) = phorum_htmlpurifier_remove_sig_and_editmessage($message);
                $fake_data[$message_id] = $message;
                $fake_data = phorum_htmlpurifier_migrate($fake_data);
                $body = $fake_data[$message_id]['body'];
                $body = str_replace("<phorum break>\n", "\n", $body);
                $updated_message['body'] = $body; // save it in
                $body .= $signature . $edit_message; // add it back in
            } else {
                // reverse Phorum's pre-processing
                $body = $message['body'];
                // order is important
                $body = str_replace("<phorum break>\n", "\n", $body);
                $body = str_replace(array('&lt;','&gt;','&amp;', '&quot;'), array('<','>','&','"'), $body);
                if (!$message_id && defined('PHORUM_CONTROL_CENTER')) {
                    // we're in control.php, so it was double-escaped
                    $body = str_replace(array('&lt;','&gt;','&amp;', '&quot;'), array('<','>','&','"'), $body);
                }
            }

            $body = $purifier->purify($body);

            // dynamically update the cache (MUST BE DONE HERE!)
            // this is inefficient because it's one db call per
            // cache miss, but once the cache is in place things are
            // a lot zippier.

            if ($message_id) { // make sure it's not a fake id
                $updated_message['meta'] = $message['meta'];
                $updated_message['meta']['body_cache'] = base64_encode($body);
                $updated_message['meta']['body_cache_serial'] = $cache_serial;
                phorum_db_update_message($message_id, $updated_message);
            }

            // must not get overloaded until after we cache it, otherwise
            // we'll inadvertently change the original text
            $data[$message_id]['body'] = $body;

        }
    }

    return $data;
}

// -----------------------------------------------------------------------
// This is fragile code, copied from read.php:596 (Phorum 5.2.6). Please
// keep this code in-sync with Phorum

/**
 * Generates a signature based on a message array
 */
function phorum_htmlpurifier_generate_sig($row)
{
    $phorum_sig = '';
    if(isset($row["user"]["signature"])
       && isset($row['meta']['show_signature']) && $row['meta']['show_signature']==1){
           $phorum_sig=trim($row["user"]["signature"]);
           if(!empty($phorum_sig)){
               $phorum_sig="\n\n$phorum_sig";
           }
    }
    return $phorum_sig;
}

/**
 * Generates an edit message based on a message array
 */
function phorum_htmlpurifier_generate_editmessage($row)
{
    $PHORUM = $GLOBALS['PHORUM'];
    $editmessage = '';
    if(isset($row['meta']['edit_count']) && $row['meta']['edit_count'] > 0) {
        $editmessage = str_replace ("%count%", $row['meta']['edit_count'], $PHORUM["DATA"]["LANG"]["EditedMessage"]);
        $editmessage = str_replace ("%lastedit%", phorum_date($PHORUM["short_date_time"],$row['meta']['edit_date']),  $editmessage);
        $editmessage = str_replace ("%lastuser%", $row['meta']['edit_username'],  $editmessage);
        $editmessage = "\n\n\n\n$editmessage";
    }
    return $editmessage;
}

// End fragile code
// -----------------------------------------------------------------------

/**
 * Removes the signature and edit message from a message
 * @param $row Message passed by reference
 */
function phorum_htmlpurifier_remove_sig_and_editmessage(&$row)
{
    $signature = phorum_htmlpurifier_generate_sig($row);
    $editmessage = phorum_htmlpurifier_generate_editmessage($row);
    $replacements = array();
    // we need to remove add <phorum break> as that is the form these
    // extra bits are in.
    if ($signature) $replacements[str_replace("\n", "<phorum break>\n", $signature)] = '';
    if ($editmessage) $replacements[str_replace("\n", "<phorum break>\n", $editmessage)] = '';
    $row['body'] = strtr($row['body'], $replacements);
    return array($signature, $editmessage);
}

/**
 * Indicate that data is fully HTML and not from migration, invalidate
 * previous caches
 * @note This function could generate the actual cache entries, but
 *       since there's data missing that must be deferred to the first read
 */
function phorum_htmlpurifier_posting($message)
{
    $PHORUM = $GLOBALS["PHORUM"];
    unset($message['meta']['body_cache']); // invalidate the cache
    $message['meta']['body_cache_serial'] = $PHORUM['mod_htmlpurifier']['body_cache_serial'];
    return $message;
}

/**
 * Overload quoting mechanism to prevent default, mail-style quote from happening
 */
function phorum_htmlpurifier_quote($array)
{
    $PHORUM = $GLOBALS["PHORUM"];
    $purifier =& HTMLPurifier::getInstance();
    $text = $purifier->purify($array[1]);
    $source = htmlspecialchars($array[0]);
    return "<blockquote cite=\"$source\">\n$text\n</blockquote>";
}

/**
 * Ensure that our format hook is processed last. Also, loads the library.
 * @credits <http://secretsauce.phorum.org/snippets/make_bbcode_last_formatter.php.txt>
 */
function phorum_htmlpurifier_common()
{
    require_once(dirname(__FILE__).'/htmlpurifier/HTMLPurifier.auto.php');
    require(dirname(__FILE__).'/init-config.php');

    $config = phorum_htmlpurifier_get_config();
    HTMLPurifier::getInstance($config);

    // increment revision.txt if you want to invalidate the cache
    $GLOBALS['PHORUM']['mod_htmlpurifier']['body_cache_serial'] = $config->getSerial();

    // load migration
    if (file_exists(dirname(__FILE__) . '/migrate.php')) {
        include(dirname(__FILE__) . '/migrate.php');
    } else {
        echo '<strong>Error:</strong> No migration path specified for HTML Purifier, please check
        <tt>modes/htmlpurifier/migrate.bbcode.php</tt> for instructions on
        how to migrate from your previous markup language.';
        exit;
    }

    if (!function_exists('phorum_htmlpurifier_migrate')) {
        // Dummy function
        function phorum_htmlpurifier_migrate($data) {return $data;}
    }

}

/**
 * Pre-emptively performs purification if it looks like a WYSIWYG editor
 * is being used
 */
function phorum_htmlpurifier_before_editor($message)
{
    if (!empty($GLOBALS['PHORUM']['mod_htmlpurifier']['wysiwyg'])) {
        if (!empty($message['body'])) {
            $body = $message['body'];
            // de-entity-ize contents
            $body = str_replace(array('&lt;','&gt;','&amp;'), array('<','>','&'), $body);
            $purifier =& HTMLPurifier::getInstance();
            $body = $purifier->purify($body);
            // re-entity-ize contents
            $body = htmlspecialchars($body, ENT_QUOTES, $GLOBALS['PHORUM']['DATA']['CHARSET']);
            $message['body'] = $body;
        }
    }
    return $message;
}

function phorum_htmlpurifier_editor_after_subject()
{
    // don't show this message if it's a WYSIWYG editor, since it will
    // then be handled automatically
    if (!empty($GLOBALS['PHORUM']['mod_htmlpurifier']['wysiwyg'])) {
        $i = $GLOBALS['PHORUM']['DATA']['MODE'];
        if ($i == 'quote' || $i == 'edit' || $i == 'moderation') {
          ?>
          <div>
            <p>
              <strong>Notice:</strong> HTML has been scrubbed for your safety.
              If you would like to see the original, turn off WYSIWYG mode
              (consult your administrator for details.)
            </p>
          </div>
          <?php
        }
        return;
    }
    if (!empty($GLOBALS['PHORUM']['mod_htmlpurifier']['suppress_message'])) return;
    ?><div class="htmlpurifier-help">
    <p>
        <strong>HTML input</strong> is enabled. Make sure you escape all HTML and
        angled brackets with <code>&amp;lt;</code> and <code>&amp;gt;</code>.
    </p><?php
            $purifier =& HTMLPurifier::getInstance();
            $config = $purifier->config;
            if ($config->get('AutoFormat.AutoParagraph')) {
                ?><p>
                    <strong>Auto-paragraphing</strong> is enabled. Double
                    newlines will be converted to paragraphs; for single
                    newlines, use the <code>pre</code> tag.
                </p><?php
            }
            $html_definition = $config->getDefinition('HTML');
            $allowed = array();
            foreach ($html_definition->info as $name => $x) $allowed[] = "<code>$name</code>";
            sort($allowed);
            $allowed_text = implode(', ', $allowed);
            ?><p><strong>Allowed tags:</strong> <?php
            echo $allowed_text;
            ?>.</p><?php
        ?>
    </p>
    <p>
        For inputting literal code such as HTML and PHP for display, use
        CDATA tags to auto-escape your angled brackets, and <code>pre</code>
        to preserve newlines:
    </p>
    <pre>&lt;pre&gt;&lt;![CDATA[
<em>Place code here</em>
]]&gt;&lt;/pre&gt;</pre>
    <p>
        Power users, you can hide this notice with:
        <pre>.htmlpurifier-help {display:none;}</pre>
    </p>
    </div><?php
}

// vim: et sw=4 sts=4
<?php

/**
 * Initializes the appropriate configuration from either a PHP file
 * or a module configuration value
 * @return Instance of HTMLPurifier_Config
 */
function phorum_htmlpurifier_get_config($default = false)
{
    global $PHORUM;
    $config_exists = phorum_htmlpurifier_config_file_exists();
    if ($default || $config_exists || !isset($PHORUM['mod_htmlpurifier']['config'])) {
        $config = HTMLPurifier_Config::createDefault();
        include(dirname(__FILE__) . '/config.default.php');
        if ($config_exists) {
            include(dirname(__FILE__) . '/config.php');
        }
        unset($PHORUM['mod_htmlpurifier']['config']); // unnecessary
    } else {
        $config = HTMLPurifier_Config::create($PHORUM['mod_htmlpurifier']['config']);
    }
    return $config;
}

function phorum_htmlpurifier_config_file_exists()
{
    return file_exists(dirname(__FILE__) . '/config.php');
}

// vim: et sw=4 sts=4
<?php

if(!defined("PHORUM")) exit;

// default HTML Purifier configuration settings
$config->set('HTML.Allowed',
  // alphabetically sorted
'a[href|title]
abbr[title]
acronym[title]
b
blockquote[cite]
br
caption
cite
code
dd
del
dfn
div
dl
dt
em
i
img[src|alt|title|class]
ins
kbd
li
ol
p
pre
s
strike
strong
sub
sup
table
tbody
td
tfoot
th
thead
tr
tt
u
ul
var');
$config->set('AutoFormat.AutoParagraph', true);
$config->set('AutoFormat.Linkify', true);
$config->set('HTML.Doctype', 'XHTML 1.0 Transitional');
$config->set('Core.AggressivelyFixLt', true);
$config->set('Core.Encoding', $GLOBALS['PHORUM']['DATA']['CHARSET']); // we'll change this eventually
if (strtolower($GLOBALS['PHORUM']['DATA']['CHARSET']) !== 'utf-8') {
  $config->set('Core.EscapeNonASCIICharacters', true);
}

// vim: et sw=4 sts=4
<?php

/**
 * This file is responsible for migrating from a specific markup language
 * like BBCode or Markdown to HTML. WARNING: THIS PROCESS IS NOT REVERSIBLE
 *
 * Copy this file to 'migrate.php' and it will automatically work for
 * BBCode; you may need to tweak this a little to get it to work for other
 * languages (usually, just replace the include name and the function name).
 *
 * If you do NOT want to have any migration performed (for instance, you
 * are installing the module on a new forum with no posts), simply remove
 * phorum_htmlpurifier_migrate() function. You still need migrate.php
 * present, otherwise the module won't work. This ensures that the user
 * explicitly says, "No, I do not need to migrate."
 */

if(!defined("PHORUM")) exit;

require_once(dirname(__FILE__) . "/../bbcode/bbcode.php");

/**
 * 'format' hook style function that will be called to convert
 * legacy markup into HTML.
 */
function phorum_htmlpurifier_migrate($data)
{
    return phorum_mod_bbcode_format($data); // bbcode's 'format' hook
}

// vim: et sw=4 sts=4
<?php

// Tags releases

if (php_sapi_name() != 'cli') {
    echo 'Release script cannot be called from web-browser.';
    exit;
}

require 'svn.php';

$svn_info = my_svn_info('.');

$version = trim(file_get_contents('VERSION'));

$trunk_url  = $svn_info['Repository Root'] . '/htmlpurifier/trunk';
$trunk_tag_url  = $svn_info['Repository Root'] . '/htmlpurifier/tags/' . $version;

echo "Tagging trunk to tags/$version...";
passthru("svn copy --message \"Tag $version release.\" $trunk_url $trunk_tag_url");

// vim: et sw=4 sts=4
<?php

// This file is the configuration for Travis testing.

// Note: The only external library you *need* is SimpleTest; everything else
//       is optional.

// We've got a lot of tests, so we recommend turning the limit off.
set_time_limit(0);

// Turning off output buffering will prevent mysterious errors from core dumps.
$data = @ob_get_clean();
if ($data !== false && $data !== '') {
    echo "Output buffer contains data [".urlencode($data)."]\n";
    exit;
}

// -----------------------------------------------------------------------------
// REQUIRED SETTINGS

// Note on running SimpleTest:
//      You want the Git copy of SimpleTest, found here:
//          https://github.com/simpletest/simpletest/
//
//      If SimpleTest is borked with HTML Purifier, please contact me or
//      the SimpleTest devs; I am a developer for SimpleTest so I should be
//      able to quickly assess a fix. SimpleTest's problem is my problem!

// Where is SimpleTest located? Remember to include a trailing slash!
$simpletest_location = dirname(__FILE__) . '/simpletest/';

// -----------------------------------------------------------------------------
// OPTIONAL SETTINGS

// Note on running PHPT:
//      Vanilla PHPT from https://github.com/tswicegood/PHPT_Core should
//      work fine on Linux w/o multitest.
//
//      To do multitest or Windows testing, you'll need some more
//      patches at https://github.com/ezyang/PHPT_Core
//
//      I haven't tested the Windows setup in a while so I don't know if
//      it still works.

// Should PHPT tests be enabled?
$GLOBALS['HTMLPurifierTest']['PHPT'] = false;

// If PHPT isn't in your Path via PEAR, set that here:
// set_include_path('/path/to/phpt/Core/src' . PATH_SEPARATOR . get_include_path());

// Where is CSSTidy located? (Include trailing slash. Leave false to disable.)
$csstidy_location    = false;

// For tests/multitest.php, which versions to test?
$versions_to_test    = array();

// Stable PHP binary to use when invoking maintenance scripts.
$php = 'php';

// For tests/multitest.php, what is the multi-version executable? It must
// accept an extra parameter (version number) before all other arguments
$phpv = false;

// Should PEAR tests be run? If you've got a valid PEAR installation, set this
// to true (or, if it's not in the include path, to its install directory).
$GLOBALS['HTMLPurifierTest']['PEAR'] = false;

// If PEAR is enabled, what PEAR tests should be run? (Note: you will
// need to ensure these libraries are installed)
$GLOBALS['HTMLPurifierTest']['Net_IDNA2'] = true;

// vim: et sw=4 sts=4
<?php

/**
 * This is a stub include that automatically configures the include path.
 */

set_include_path(dirname(__FILE__) . PATH_SEPARATOR . get_include_path() );
require_once 'HTMLPurifier/Bootstrap.php';
require_once 'HTMLPurifier.autoload.php';

// vim: et sw=4 sts=4
<?php

/*! @mainpage
 *
 * HTML Purifier is an HTML filter that will take an arbitrary snippet of
 * HTML and rigorously test, validate and filter it into a version that
 * is safe for output onto webpages. It achieves this by:
 *
 *  -# Lexing (parsing into tokens) the document,
 *  -# Executing various strategies on the tokens:
 *      -# Removing all elements not in the whitelist,
 *      -# Making the tokens well-formed,
 *      -# Fixing the nesting of the nodes, and
 *      -# Validating attributes of the nodes; and
 *  -# Generating HTML from the purified tokens.
 *
 * However, most users will only need to interface with the HTMLPurifier
 * and HTMLPurifier_Config.
 */

/*
    HTML Purifier 4.10.0 - Standards Compliant HTML Filtering
    Copyright (C) 2006-2008 Edward Z. Yang

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

/**
 * Facade that coordinates HTML Purifier's subsystems in order to purify HTML.
 *
 * @note There are several points in which configuration can be specified
 *       for HTML Purifier.  The precedence of these (from lowest to
 *       highest) is as follows:
 *          -# Instance: new HTMLPurifier($config)
 *          -# Invocation: purify($html, $config)
 *       These configurations are entirely independent of each other and
 *       are *not* merged (this behavior may change in the future).
 *
 * @todo We need an easier way to inject strategies using the configuration
 *       object.
 */
class HTMLPurifier
{

    /**
     * Version of HTML Purifier.
     * @type string
     */
    public $version = '4.10.0';

    /**
     * Constant with version of HTML Purifier.
     */
    const VERSION = '4.10.0';

    /**
     * Global configuration object.
     * @type HTMLPurifier_Config
     */
    public $config;

    /**
     * Array of extra filter objects to run on HTML,
     * for backwards compatibility.
     * @type HTMLPurifier_Filter[]
     */
    private $filters = array();

    /**
     * Single instance of HTML Purifier.
     * @type HTMLPurifier
     */
    private static $instance;

    /**
     * @type HTMLPurifier_Strategy_Core
     */
    protected $strategy;

    /**
     * @type HTMLPurifier_Generator
     */
    protected $generator;

    /**
     * Resultant context of last run purification.
     * Is an array of contexts if the last called method was purifyArray().
     * @type HTMLPurifier_Context
     */
    public $context;

    /**
     * Initializes the purifier.
     *
     * @param HTMLPurifier_Config|mixed $config Optional HTMLPurifier_Config object
     *                for all instances of the purifier, if omitted, a default
     *                configuration is supplied (which can be overridden on a
     *                per-use basis).
     *                The parameter can also be any type that
     *                HTMLPurifier_Config::create() supports.
     */
    public function __construct($config = null)
    {
        $this->config = HTMLPurifier_Config::create($config);
        $this->strategy = new HTMLPurifier_Strategy_Core();
    }

    /**
     * Adds a filter to process the output. First come first serve
     *
     * @param HTMLPurifier_Filter $filter HTMLPurifier_Filter object
     */
    public function addFilter($filter)
    {
        trigger_error(
            'HTMLPurifier->addFilter() is deprecated, use configuration directives' .
            ' in the Filter namespace or Filter.Custom',
            E_USER_WARNING
        );
        $this->filters[] = $filter;
    }

    /**
     * Filters an HTML snippet/document to be XSS-free and standards-compliant.
     *
     * @param string $html String of HTML to purify
     * @param HTMLPurifier_Config $config Config object for this operation,
     *                if omitted, defaults to the config object specified during this
     *                object's construction. The parameter can also be any type
     *                that HTMLPurifier_Config::create() supports.
     *
     * @return string Purified HTML
     */
    public function purify($html, $config = null)
    {
        // :TODO: make the config merge in, instead of replace
        $config = $config ? HTMLPurifier_Config::create($config) : $this->config;

        // implementation is partially environment dependant, partially
        // configuration dependant
        $lexer = HTMLPurifier_Lexer::create($config);

        $context = new HTMLPurifier_Context();

        // setup HTML generator
        $this->generator = new HTMLPurifier_Generator($config, $context);
        $context->register('Generator', $this->generator);

        // set up global context variables
        if ($config->get('Core.CollectErrors')) {
            // may get moved out if other facilities use it
            $language_factory = HTMLPurifier_LanguageFactory::instance();
            $language = $language_factory->create($config, $context);
            $context->register('Locale', $language);

            $error_collector = new HTMLPurifier_ErrorCollector($context);
            $context->register('ErrorCollector', $error_collector);
        }

        // setup id_accumulator context, necessary due to the fact that
        // AttrValidator can be called from many places
        $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context);
        $context->register('IDAccumulator', $id_accumulator);

        $html = HTMLPurifier_Encoder::convertToUTF8($html, $config, $context);

        // setup filters
        $filter_flags = $config->getBatch('Filter');
        $custom_filters = $filter_flags['Custom'];
        unset($filter_flags['Custom']);
        $filters = array();
        foreach ($filter_flags as $filter => $flag) {
            if (!$flag) {
                continue;
            }
            if (strpos($filter, '.') !== false) {
                continue;
            }
            $class = "HTMLPurifier_Filter_$filter";
            $filters[] = new $class;
        }
        foreach ($custom_filters as $filter) {
            // maybe "HTMLPurifier_Filter_$filter", but be consistent with AutoFormat
            $filters[] = $filter;
        }
        $filters = array_merge($filters, $this->filters);
        // maybe prepare(), but later

        for ($i = 0, $filter_size = count($filters); $i < $filter_size; $i++) {
            $html = $filters[$i]->preFilter($html, $config, $context);
        }

        // purified HTML
        $html =
            $this->generator->generateFromTokens(
                // list of tokens
                $this->strategy->execute(
                    // list of un-purified tokens
                    $lexer->tokenizeHTML(
                        // un-purified HTML
                        $html,
                        $config,
                        $context
                    ),
                    $config,
                    $context
                )
            );

        for ($i = $filter_size - 1; $i >= 0; $i--) {
            $html = $filters[$i]->postFilter($html, $config, $context);
        }

        $html = HTMLPurifier_Encoder::convertFromUTF8($html, $config, $context);
        $this->context =& $context;
        return $html;
    }

    /**
     * Filters an array of HTML snippets
     *
     * @param string[] $array_of_html Array of html snippets
     * @param HTMLPurifier_Config $config Optional config object for this operation.
     *                See HTMLPurifier::purify() for more details.
     *
     * @return string[] Array of purified HTML
     */
    public function purifyArray($array_of_html, $config = null)
    {
        $context_array = array();
        foreach ($array_of_html as $key => $html) {
            $array_of_html[$key] = $this->purify($html, $config);
            $context_array[$key] = $this->context;
        }
        $this->context = $context_array;
        return $array_of_html;
    }

    /**
     * Singleton for enforcing just one HTML Purifier in your system
     *
     * @param HTMLPurifier|HTMLPurifier_Config $prototype Optional prototype
     *                   HTMLPurifier instance to overload singleton with,
     *                   or HTMLPurifier_Config instance to configure the
     *                   generated version with.
     *
     * @return HTMLPurifier
     */
    public static function instance($prototype = null)
    {
        if (!self::$instance || $prototype) {
            if ($prototype instanceof HTMLPurifier) {
                self::$instance = $prototype;
            } elseif ($prototype) {
                self::$instance = new HTMLPurifier($prototype);
            } else {
                self::$instance = new HTMLPurifier();
            }
        }
        return self::$instance;
    }

    /**
     * Singleton for enforcing just one HTML Purifier in your system
     *
     * @param HTMLPurifier|HTMLPurifier_Config $prototype Optional prototype
     *                   HTMLPurifier instance to overload singleton with,
     *                   or HTMLPurifier_Config instance to configure the
     *                   generated version with.
     *
     * @return HTMLPurifier
     * @note Backwards compatibility, see instance()
     */
    public static function getInstance($prototype = null)
    {
        return HTMLPurifier::instance($prototype);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * @file
 * Convenience file that registers autoload handler for HTML Purifier.
 * It also does some sanity checks.
 */

if (function_exists('spl_autoload_register') && function_exists('spl_autoload_unregister')) {
    // We need unregister for our pre-registering functionality
    HTMLPurifier_Bootstrap::registerAutoload();
    if (function_exists('__autoload')) {
        // Be polite and ensure that userland autoload gets retained
        spl_autoload_register('__autoload');
    }
} elseif (!function_exists('__autoload')) {
    require dirname(__FILE__) . '/HTMLPurifier.autoload-legacy.php';
}

if (ini_get('zend.ze1_compatibility_mode')) {
    trigger_error("HTML Purifier is not compatible with zend.ze1_compatibility_mode; please turn it off", E_USER_ERROR);
}

// vim: et sw=4 sts=4
<?php

/**
 * @file
 * Emulation layer for code that used kses(), substituting in HTML Purifier.
 */

require_once dirname(__FILE__) . '/HTMLPurifier.auto.php';

function kses($string, $allowed_html, $allowed_protocols = null)
{
    $config = HTMLPurifier_Config::createDefault();
    $allowed_elements = array();
    $allowed_attributes = array();
    foreach ($allowed_html as $element => $attributes) {
        $allowed_elements[$element] = true;
        foreach ($attributes as $attribute => $x) {
            $allowed_attributes["$element.$attribute"] = true;
        }
    }
    $config->set('HTML.AllowedElements', $allowed_elements);
    $config->set('HTML.AllowedAttributes', $allowed_attributes);
    if ($allowed_protocols !== null) {
        $config->set('URI.AllowedSchemes', $allowed_protocols);
    }
    $purifier = new HTMLPurifier($config);
    return $purifier->purify($string);
}

// vim: et sw=4 sts=4
<?php

/**
 * @file
 * This file was auto-generated by generate-includes.php and includes all of
 * the core files required by HTML Purifier. This is a convenience stub that
 * includes all files using dirname(__FILE__) and require_once. PLEASE DO NOT
 * EDIT THIS FILE, changes will be overwritten the next time the script is run.
 *
 * Changes to include_path are not necessary.
 */

$__dir = dirname(__FILE__);

require_once $__dir . '/HTMLPurifier.php';
require_once $__dir . '/HTMLPurifier/Arborize.php';
require_once $__dir . '/HTMLPurifier/AttrCollections.php';
require_once $__dir . '/HTMLPurifier/AttrDef.php';
require_once $__dir . '/HTMLPurifier/AttrTransform.php';
require_once $__dir . '/HTMLPurifier/AttrTypes.php';
require_once $__dir . '/HTMLPurifier/AttrValidator.php';
require_once $__dir . '/HTMLPurifier/Bootstrap.php';
require_once $__dir . '/HTMLPurifier/Definition.php';
require_once $__dir . '/HTMLPurifier/CSSDefinition.php';
require_once $__dir . '/HTMLPurifier/ChildDef.php';
require_once $__dir . '/HTMLPurifier/Config.php';
require_once $__dir . '/HTMLPurifier/ConfigSchema.php';
require_once $__dir . '/HTMLPurifier/ContentSets.php';
require_once $__dir . '/HTMLPurifier/Context.php';
require_once $__dir . '/HTMLPurifier/DefinitionCache.php';
require_once $__dir . '/HTMLPurifier/DefinitionCacheFactory.php';
require_once $__dir . '/HTMLPurifier/Doctype.php';
require_once $__dir . '/HTMLPurifier/DoctypeRegistry.php';
require_once $__dir . '/HTMLPurifier/ElementDef.php';
require_once $__dir . '/HTMLPurifier/Encoder.php';
require_once $__dir . '/HTMLPurifier/EntityLookup.php';
require_once $__dir . '/HTMLPurifier/EntityParser.php';
require_once $__dir . '/HTMLPurifier/ErrorCollector.php';
require_once $__dir . '/HTMLPurifier/ErrorStruct.php';
require_once $__dir . '/HTMLPurifier/Exception.php';
require_once $__dir . '/HTMLPurifier/Filter.php';
require_once $__dir . '/HTMLPurifier/Generator.php';
require_once $__dir . '/HTMLPurifier/HTMLDefinition.php';
require_once $__dir . '/HTMLPurifier/HTMLModule.php';
require_once $__dir . '/HTMLPurifier/HTMLModuleManager.php';
require_once $__dir . '/HTMLPurifier/IDAccumulator.php';
require_once $__dir . '/HTMLPurifier/Injector.php';
require_once $__dir . '/HTMLPurifier/Language.php';
require_once $__dir . '/HTMLPurifier/LanguageFactory.php';
require_once $__dir . '/HTMLPurifier/Length.php';
require_once $__dir . '/HTMLPurifier/Lexer.php';
require_once $__dir . '/HTMLPurifier/Node.php';
require_once $__dir . '/HTMLPurifier/PercentEncoder.php';
require_once $__dir . '/HTMLPurifier/PropertyList.php';
require_once $__dir . '/HTMLPurifier/PropertyListIterator.php';
require_once $__dir . '/HTMLPurifier/Queue.php';
require_once $__dir . '/HTMLPurifier/Strategy.php';
require_once $__dir . '/HTMLPurifier/StringHash.php';
require_once $__dir . '/HTMLPurifier/StringHashParser.php';
require_once $__dir . '/HTMLPurifier/TagTransform.php';
require_once $__dir . '/HTMLPurifier/Token.php';
require_once $__dir . '/HTMLPurifier/TokenFactory.php';
require_once $__dir . '/HTMLPurifier/URI.php';
require_once $__dir . '/HTMLPurifier/URIDefinition.php';
require_once $__dir . '/HTMLPurifier/URIFilter.php';
require_once $__dir . '/HTMLPurifier/URIParser.php';
require_once $__dir . '/HTMLPurifier/URIScheme.php';
require_once $__dir . '/HTMLPurifier/URISchemeRegistry.php';
require_once $__dir . '/HTMLPurifier/UnitConverter.php';
require_once $__dir . '/HTMLPurifier/VarParser.php';
require_once $__dir . '/HTMLPurifier/VarParserException.php';
require_once $__dir . '/HTMLPurifier/Zipper.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS.php';
require_once $__dir . '/HTMLPurifier/AttrDef/Clone.php';
require_once $__dir . '/HTMLPurifier/AttrDef/Enum.php';
require_once $__dir . '/HTMLPurifier/AttrDef/Integer.php';
require_once $__dir . '/HTMLPurifier/AttrDef/Lang.php';
require_once $__dir . '/HTMLPurifier/AttrDef/Switch.php';
require_once $__dir . '/HTMLPurifier/AttrDef/Text.php';
require_once $__dir . '/HTMLPurifier/AttrDef/URI.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Number.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/AlphaValue.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Background.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Border.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Color.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Composite.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Filter.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Font.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/FontFamily.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Ident.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Length.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/ListStyle.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Multiple.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Percentage.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/TextDecoration.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/URI.php';
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/Bool.php';
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/Nmtokens.php';
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/Class.php';
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/Color.php';
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/FrameTarget.php';
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/ID.php';
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/Pixels.php';
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/Length.php';
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/LinkTypes.php';
require_once $__dir . '/HTMLPurifier/AttrDef/HTML/MultiLength.php';
require_once $__dir . '/HTMLPurifier/AttrDef/URI/Email.php';
require_once $__dir . '/HTMLPurifier/AttrDef/URI/Host.php';
require_once $__dir . '/HTMLPurifier/AttrDef/URI/IPv4.php';
require_once $__dir . '/HTMLPurifier/AttrDef/URI/IPv6.php';
require_once $__dir . '/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/Background.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/BdoDir.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/BgColor.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/BoolToCSS.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/Border.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/EnumToCSS.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/ImgRequired.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/ImgSpace.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/Input.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/Lang.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/Length.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/Name.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/NameSync.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/Nofollow.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/SafeEmbed.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/SafeObject.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/SafeParam.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/ScriptRequired.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/TargetBlank.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/TargetNoopener.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/TargetNoreferrer.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/Textarea.php';
require_once $__dir . '/HTMLPurifier/ChildDef/Chameleon.php';
require_once $__dir . '/HTMLPurifier/ChildDef/Custom.php';
require_once $__dir . '/HTMLPurifier/ChildDef/Empty.php';
require_once $__dir . '/HTMLPurifier/ChildDef/List.php';
require_once $__dir . '/HTMLPurifier/ChildDef/Required.php';
require_once $__dir . '/HTMLPurifier/ChildDef/Optional.php';
require_once $__dir . '/HTMLPurifier/ChildDef/StrictBlockquote.php';
require_once $__dir . '/HTMLPurifier/ChildDef/Table.php';
require_once $__dir . '/HTMLPurifier/DefinitionCache/Decorator.php';
require_once $__dir . '/HTMLPurifier/DefinitionCache/Null.php';
require_once $__dir . '/HTMLPurifier/DefinitionCache/Serializer.php';
require_once $__dir . '/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php';
require_once $__dir . '/HTMLPurifier/DefinitionCache/Decorator/Memory.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Bdo.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/CommonAttributes.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Edit.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Forms.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Hypertext.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Iframe.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Image.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Legacy.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/List.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Name.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Nofollow.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Object.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Presentation.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Proprietary.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Ruby.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/SafeEmbed.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/SafeObject.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/SafeScripting.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Scripting.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/StyleAttribute.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Tables.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Target.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/TargetBlank.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/TargetNoopener.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/TargetNoreferrer.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Text.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/XMLCommonAttributes.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy/Name.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy/Proprietary.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy/Strict.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy/Transitional.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy/XHTML.php';
require_once $__dir . '/HTMLPurifier/Injector/AutoParagraph.php';
require_once $__dir . '/HTMLPurifier/Injector/DisplayLinkURI.php';
require_once $__dir . '/HTMLPurifier/Injector/Linkify.php';
require_once $__dir . '/HTMLPurifier/Injector/PurifierLinkify.php';
require_once $__dir . '/HTMLPurifier/Injector/RemoveEmpty.php';
require_once $__dir . '/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php';
require_once $__dir . '/HTMLPurifier/Injector/SafeObject.php';
require_once $__dir . '/HTMLPurifier/Lexer/DOMLex.php';
require_once $__dir . '/HTMLPurifier/Lexer/DirectLex.php';
require_once $__dir . '/HTMLPurifier/Node/Comment.php';
require_once $__dir . '/HTMLPurifier/Node/Element.php';
require_once $__dir . '/HTMLPurifier/Node/Text.php';
require_once $__dir . '/HTMLPurifier/Strategy/Composite.php';
require_once $__dir . '/HTMLPurifier/Strategy/Core.php';
require_once $__dir . '/HTMLPurifier/Strategy/FixNesting.php';
require_once $__dir . '/HTMLPurifier/Strategy/MakeWellFormed.php';
require_once $__dir . '/HTMLPurifier/Strategy/RemoveForeignElements.php';
require_once $__dir . '/HTMLPurifier/Strategy/ValidateAttributes.php';
require_once $__dir . '/HTMLPurifier/TagTransform/Font.php';
require_once $__dir . '/HTMLPurifier/TagTransform/Simple.php';
require_once $__dir . '/HTMLPurifier/Token/Comment.php';
require_once $__dir . '/HTMLPurifier/Token/Tag.php';
require_once $__dir . '/HTMLPurifier/Token/Empty.php';
require_once $__dir . '/HTMLPurifier/Token/End.php';
require_once $__dir . '/HTMLPurifier/Token/Start.php';
require_once $__dir . '/HTMLPurifier/Token/Text.php';
require_once $__dir . '/HTMLPurifier/URIFilter/DisableExternal.php';
require_once $__dir . '/HTMLPurifier/URIFilter/DisableExternalResources.php';
require_once $__dir . '/HTMLPurifier/URIFilter/DisableResources.php';
require_once $__dir . '/HTMLPurifier/URIFilter/HostBlacklist.php';
require_once $__dir . '/HTMLPurifier/URIFilter/MakeAbsolute.php';
require_once $__dir . '/HTMLPurifier/URIFilter/Munge.php';
require_once $__dir . '/HTMLPurifier/URIFilter/SafeIframe.php';
require_once $__dir . '/HTMLPurifier/URIScheme/data.php';
require_once $__dir . '/HTMLPurifier/URIScheme/file.php';
require_once $__dir . '/HTMLPurifier/URIScheme/ftp.php';
require_once $__dir . '/HTMLPurifier/URIScheme/http.php';
require_once $__dir . '/HTMLPurifier/URIScheme/https.php';
require_once $__dir . '/HTMLPurifier/URIScheme/mailto.php';
require_once $__dir . '/HTMLPurifier/URIScheme/news.php';
require_once $__dir . '/HTMLPurifier/URIScheme/nntp.php';
require_once $__dir . '/HTMLPurifier/URIScheme/tel.php';
require_once $__dir . '/HTMLPurifier/VarParser/Flexible.php';
require_once $__dir . '/HTMLPurifier/VarParser/Native.php';
<?php

/**
 * @file
 * Convenience stub file that adds HTML Purifier's library file to the path
 * without any other side-effects.
 */

set_include_path(dirname(__FILE__) . PATH_SEPARATOR . get_include_path() );

// vim: et sw=4 sts=4
<?php

/**
 * @file
 * Legacy autoloader for systems lacking spl_autoload_register
 *
 * Must be separate to prevent deprecation warning on PHP 7.2
 */

function __autoload($class)
{
    return HTMLPurifier_Bootstrap::autoload($class);
}

// vim: et sw=4 sts=4
<?php

/**
 * @file
 * This file was auto-generated by generate-includes.php and includes all of
 * the core files required by HTML Purifier. Use this if performance is a
 * primary concern and you are using an opcode cache. PLEASE DO NOT EDIT THIS
 * FILE, changes will be overwritten the next time the script is run.
 *
 * @version 4.10.0
 *
 * @warning
 *      You must *not* include any other HTML Purifier files before this file,
 *      because 'require' not 'require_once' is used.
 *
 * @warning
 *      This file requires that the include path contains the HTML Purifier
 *      library directory; this is not auto-set.
 */

require 'HTMLPurifier.php';
require 'HTMLPurifier/Arborize.php';
require 'HTMLPurifier/AttrCollections.php';
require 'HTMLPurifier/AttrDef.php';
require 'HTMLPurifier/AttrTransform.php';
require 'HTMLPurifier/AttrTypes.php';
require 'HTMLPurifier/AttrValidator.php';
require 'HTMLPurifier/Bootstrap.php';
require 'HTMLPurifier/Definition.php';
require 'HTMLPurifier/CSSDefinition.php';
require 'HTMLPurifier/ChildDef.php';
require 'HTMLPurifier/Config.php';
require 'HTMLPurifier/ConfigSchema.php';
require 'HTMLPurifier/ContentSets.php';
require 'HTMLPurifier/Context.php';
require 'HTMLPurifier/DefinitionCache.php';
require 'HTMLPurifier/DefinitionCacheFactory.php';
require 'HTMLPurifier/Doctype.php';
require 'HTMLPurifier/DoctypeRegistry.php';
require 'HTMLPurifier/ElementDef.php';
require 'HTMLPurifier/Encoder.php';
require 'HTMLPurifier/EntityLookup.php';
require 'HTMLPurifier/EntityParser.php';
require 'HTMLPurifier/ErrorCollector.php';
require 'HTMLPurifier/ErrorStruct.php';
require 'HTMLPurifier/Exception.php';
require 'HTMLPurifier/Filter.php';
require 'HTMLPurifier/Generator.php';
require 'HTMLPurifier/HTMLDefinition.php';
require 'HTMLPurifier/HTMLModule.php';
require 'HTMLPurifier/HTMLModuleManager.php';
require 'HTMLPurifier/IDAccumulator.php';
require 'HTMLPurifier/Injector.php';
require 'HTMLPurifier/Language.php';
require 'HTMLPurifier/LanguageFactory.php';
require 'HTMLPurifier/Length.php';
require 'HTMLPurifier/Lexer.php';
require 'HTMLPurifier/Node.php';
require 'HTMLPurifier/PercentEncoder.php';
require 'HTMLPurifier/PropertyList.php';
require 'HTMLPurifier/PropertyListIterator.php';
require 'HTMLPurifier/Queue.php';
require 'HTMLPurifier/Strategy.php';
require 'HTMLPurifier/StringHash.php';
require 'HTMLPurifier/StringHashParser.php';
require 'HTMLPurifier/TagTransform.php';
require 'HTMLPurifier/Token.php';
require 'HTMLPurifier/TokenFactory.php';
require 'HTMLPurifier/URI.php';
require 'HTMLPurifier/URIDefinition.php';
require 'HTMLPurifier/URIFilter.php';
require 'HTMLPurifier/URIParser.php';
require 'HTMLPurifier/URIScheme.php';
require 'HTMLPurifier/URISchemeRegistry.php';
require 'HTMLPurifier/UnitConverter.php';
require 'HTMLPurifier/VarParser.php';
require 'HTMLPurifier/VarParserException.php';
require 'HTMLPurifier/Zipper.php';
require 'HTMLPurifier/AttrDef/CSS.php';
require 'HTMLPurifier/AttrDef/Clone.php';
require 'HTMLPurifier/AttrDef/Enum.php';
require 'HTMLPurifier/AttrDef/Integer.php';
require 'HTMLPurifier/AttrDef/Lang.php';
require 'HTMLPurifier/AttrDef/Switch.php';
require 'HTMLPurifier/AttrDef/Text.php';
require 'HTMLPurifier/AttrDef/URI.php';
require 'HTMLPurifier/AttrDef/CSS/Number.php';
require 'HTMLPurifier/AttrDef/CSS/AlphaValue.php';
require 'HTMLPurifier/AttrDef/CSS/Background.php';
require 'HTMLPurifier/AttrDef/CSS/BackgroundPosition.php';
require 'HTMLPurifier/AttrDef/CSS/Border.php';
require 'HTMLPurifier/AttrDef/CSS/Color.php';
require 'HTMLPurifier/AttrDef/CSS/Composite.php';
require 'HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php';
require 'HTMLPurifier/AttrDef/CSS/Filter.php';
require 'HTMLPurifier/AttrDef/CSS/Font.php';
require 'HTMLPurifier/AttrDef/CSS/FontFamily.php';
require 'HTMLPurifier/AttrDef/CSS/Ident.php';
require 'HTMLPurifier/AttrDef/CSS/ImportantDecorator.php';
require 'HTMLPurifier/AttrDef/CSS/Length.php';
require 'HTMLPurifier/AttrDef/CSS/ListStyle.php';
require 'HTMLPurifier/AttrDef/CSS/Multiple.php';
require 'HTMLPurifier/AttrDef/CSS/Percentage.php';
require 'HTMLPurifier/AttrDef/CSS/TextDecoration.php';
require 'HTMLPurifier/AttrDef/CSS/URI.php';
require 'HTMLPurifier/AttrDef/HTML/Bool.php';
require 'HTMLPurifier/AttrDef/HTML/Nmtokens.php';
require 'HTMLPurifier/AttrDef/HTML/Class.php';
require 'HTMLPurifier/AttrDef/HTML/Color.php';
require 'HTMLPurifier/AttrDef/HTML/FrameTarget.php';
require 'HTMLPurifier/AttrDef/HTML/ID.php';
require 'HTMLPurifier/AttrDef/HTML/Pixels.php';
require 'HTMLPurifier/AttrDef/HTML/Length.php';
require 'HTMLPurifier/AttrDef/HTML/LinkTypes.php';
require 'HTMLPurifier/AttrDef/HTML/MultiLength.php';
require 'HTMLPurifier/AttrDef/URI/Email.php';
require 'HTMLPurifier/AttrDef/URI/Host.php';
require 'HTMLPurifier/AttrDef/URI/IPv4.php';
require 'HTMLPurifier/AttrDef/URI/IPv6.php';
require 'HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php';
require 'HTMLPurifier/AttrTransform/Background.php';
require 'HTMLPurifier/AttrTransform/BdoDir.php';
require 'HTMLPurifier/AttrTransform/BgColor.php';
require 'HTMLPurifier/AttrTransform/BoolToCSS.php';
require 'HTMLPurifier/AttrTransform/Border.php';
require 'HTMLPurifier/AttrTransform/EnumToCSS.php';
require 'HTMLPurifier/AttrTransform/ImgRequired.php';
require 'HTMLPurifier/AttrTransform/ImgSpace.php';
require 'HTMLPurifier/AttrTransform/Input.php';
require 'HTMLPurifier/AttrTransform/Lang.php';
require 'HTMLPurifier/AttrTransform/Length.php';
require 'HTMLPurifier/AttrTransform/Name.php';
require 'HTMLPurifier/AttrTransform/NameSync.php';
require 'HTMLPurifier/AttrTransform/Nofollow.php';
require 'HTMLPurifier/AttrTransform/SafeEmbed.php';
require 'HTMLPurifier/AttrTransform/SafeObject.php';
require 'HTMLPurifier/AttrTransform/SafeParam.php';
require 'HTMLPurifier/AttrTransform/ScriptRequired.php';
require 'HTMLPurifier/AttrTransform/TargetBlank.php';
require 'HTMLPurifier/AttrTransform/TargetNoopener.php';
require 'HTMLPurifier/AttrTransform/TargetNoreferrer.php';
require 'HTMLPurifier/AttrTransform/Textarea.php';
require 'HTMLPurifier/ChildDef/Chameleon.php';
require 'HTMLPurifier/ChildDef/Custom.php';
require 'HTMLPurifier/ChildDef/Empty.php';
require 'HTMLPurifier/ChildDef/List.php';
require 'HTMLPurifier/ChildDef/Required.php';
require 'HTMLPurifier/ChildDef/Optional.php';
require 'HTMLPurifier/ChildDef/StrictBlockquote.php';
require 'HTMLPurifier/ChildDef/Table.php';
require 'HTMLPurifier/DefinitionCache/Decorator.php';
require 'HTMLPurifier/DefinitionCache/Null.php';
require 'HTMLPurifier/DefinitionCache/Serializer.php';
require 'HTMLPurifier/DefinitionCache/Decorator/Cleanup.php';
require 'HTMLPurifier/DefinitionCache/Decorator/Memory.php';
require 'HTMLPurifier/HTMLModule/Bdo.php';
require 'HTMLPurifier/HTMLModule/CommonAttributes.php';
require 'HTMLPurifier/HTMLModule/Edit.php';
require 'HTMLPurifier/HTMLModule/Forms.php';
require 'HTMLPurifier/HTMLModule/Hypertext.php';
require 'HTMLPurifier/HTMLModule/Iframe.php';
require 'HTMLPurifier/HTMLModule/Image.php';
require 'HTMLPurifier/HTMLModule/Legacy.php';
require 'HTMLPurifier/HTMLModule/List.php';
require 'HTMLPurifier/HTMLModule/Name.php';
require 'HTMLPurifier/HTMLModule/Nofollow.php';
require 'HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php';
require 'HTMLPurifier/HTMLModule/Object.php';
require 'HTMLPurifier/HTMLModule/Presentation.php';
require 'HTMLPurifier/HTMLModule/Proprietary.php';
require 'HTMLPurifier/HTMLModule/Ruby.php';
require 'HTMLPurifier/HTMLModule/SafeEmbed.php';
require 'HTMLPurifier/HTMLModule/SafeObject.php';
require 'HTMLPurifier/HTMLModule/SafeScripting.php';
require 'HTMLPurifier/HTMLModule/Scripting.php';
require 'HTMLPurifier/HTMLModule/StyleAttribute.php';
require 'HTMLPurifier/HTMLModule/Tables.php';
require 'HTMLPurifier/HTMLModule/Target.php';
require 'HTMLPurifier/HTMLModule/TargetBlank.php';
require 'HTMLPurifier/HTMLModule/TargetNoopener.php';
require 'HTMLPurifier/HTMLModule/TargetNoreferrer.php';
require 'HTMLPurifier/HTMLModule/Text.php';
require 'HTMLPurifier/HTMLModule/Tidy.php';
require 'HTMLPurifier/HTMLModule/XMLCommonAttributes.php';
require 'HTMLPurifier/HTMLModule/Tidy/Name.php';
require 'HTMLPurifier/HTMLModule/Tidy/Proprietary.php';
require 'HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php';
require 'HTMLPurifier/HTMLModule/Tidy/Strict.php';
require 'HTMLPurifier/HTMLModule/Tidy/Transitional.php';
require 'HTMLPurifier/HTMLModule/Tidy/XHTML.php';
require 'HTMLPurifier/Injector/AutoParagraph.php';
require 'HTMLPurifier/Injector/DisplayLinkURI.php';
require 'HTMLPurifier/Injector/Linkify.php';
require 'HTMLPurifier/Injector/PurifierLinkify.php';
require 'HTMLPurifier/Injector/RemoveEmpty.php';
require 'HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php';
require 'HTMLPurifier/Injector/SafeObject.php';
require 'HTMLPurifier/Lexer/DOMLex.php';
require 'HTMLPurifier/Lexer/DirectLex.php';
require 'HTMLPurifier/Node/Comment.php';
require 'HTMLPurifier/Node/Element.php';
require 'HTMLPurifier/Node/Text.php';
require 'HTMLPurifier/Strategy/Composite.php';
require 'HTMLPurifier/Strategy/Core.php';
require 'HTMLPurifier/Strategy/FixNesting.php';
require 'HTMLPurifier/Strategy/MakeWellFormed.php';
require 'HTMLPurifier/Strategy/RemoveForeignElements.php';
require 'HTMLPurifier/Strategy/ValidateAttributes.php';
require 'HTMLPurifier/TagTransform/Font.php';
require 'HTMLPurifier/TagTransform/Simple.php';
require 'HTMLPurifier/Token/Comment.php';
require 'HTMLPurifier/Token/Tag.php';
require 'HTMLPurifier/Token/Empty.php';
require 'HTMLPurifier/Token/End.php';
require 'HTMLPurifier/Token/Start.php';
require 'HTMLPurifier/Token/Text.php';
require 'HTMLPurifier/URIFilter/DisableExternal.php';
require 'HTMLPurifier/URIFilter/DisableExternalResources.php';
require 'HTMLPurifier/URIFilter/DisableResources.php';
require 'HTMLPurifier/URIFilter/HostBlacklist.php';
require 'HTMLPurifier/URIFilter/MakeAbsolute.php';
require 'HTMLPurifier/URIFilter/Munge.php';
require 'HTMLPurifier/URIFilter/SafeIframe.php';
require 'HTMLPurifier/URIScheme/data.php';
require 'HTMLPurifier/URIScheme/file.php';
require 'HTMLPurifier/URIScheme/ftp.php';
require 'HTMLPurifier/URIScheme/http.php';
require 'HTMLPurifier/URIScheme/https.php';
require 'HTMLPurifier/URIScheme/mailto.php';
require 'HTMLPurifier/URIScheme/news.php';
require 'HTMLPurifier/URIScheme/nntp.php';
require 'HTMLPurifier/URIScheme/tel.php';
require 'HTMLPurifier/VarParser/Flexible.php';
require 'HTMLPurifier/VarParser/Native.php';
<?php
if (!defined('HTMLPURIFIER_PREFIX')) {
    define('HTMLPURIFIER_PREFIX', dirname(__FILE__));
}
<?php

/**
 * Concrete empty token class.
 */
class HTMLPurifier_Token_Empty extends HTMLPurifier_Token_Tag
{
    public function toNode() {
        $n = parent::toNode();
        $n->empty = true;
        return $n;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Abstract class of a tag token (start, end or empty), and its behavior.
 */
abstract class HTMLPurifier_Token_Tag extends HTMLPurifier_Token
{
    /**
     * Static bool marker that indicates the class is a tag.
     *
     * This allows us to check objects with <tt>!empty($obj->is_tag)</tt>
     * without having to use a function call <tt>is_a()</tt>.
     * @type bool
     */
    public $is_tag = true;

    /**
     * The lower-case name of the tag, like 'a', 'b' or 'blockquote'.
     *
     * @note Strictly speaking, XML tags are case sensitive, so we shouldn't
     * be lower-casing them, but these tokens cater to HTML tags, which are
     * insensitive.
     * @type string
     */
    public $name;

    /**
     * Associative array of the tag's attributes.
     * @type array
     */
    public $attr = array();

    /**
     * Non-overloaded constructor, which lower-cases passed tag name.
     *
     * @param string $name String name.
     * @param array $attr Associative array of attributes.
     * @param int $line
     * @param int $col
     * @param array $armor
     */
    public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array())
    {
        $this->name = ctype_lower($name) ? $name : strtolower($name);
        foreach ($attr as $key => $value) {
            // normalization only necessary when key is not lowercase
            if (!ctype_lower($key)) {
                $new_key = strtolower($key);
                if (!isset($attr[$new_key])) {
                    $attr[$new_key] = $attr[$key];
                }
                if ($new_key !== $key) {
                    unset($attr[$key]);
                }
            }
        }
        $this->attr = $attr;
        $this->line = $line;
        $this->col = $col;
        $this->armor = $armor;
    }

    public function toNode() {
        return new HTMLPurifier_Node_Element($this->name, $this->attr, $this->line, $this->col, $this->armor);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Concrete end token class.
 *
 * @warning This class accepts attributes even though end tags cannot. This
 * is for optimization reasons, as under normal circumstances, the Lexers
 * do not pass attributes.
 */
class HTMLPurifier_Token_End extends HTMLPurifier_Token_Tag
{
    /**
     * Token that started this node.
     * Added by MakeWellFormed. Please do not edit this!
     * @type HTMLPurifier_Token
     */
    public $start;

    public function toNode() {
        throw new Exception("HTMLPurifier_Token_End->toNode not supported!");
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Concrete comment token class. Generally will be ignored.
 */
class HTMLPurifier_Token_Comment extends HTMLPurifier_Token
{
    /**
     * Character data within comment.
     * @type string
     */
    public $data;

    /**
     * @type bool
     */
    public $is_whitespace = true;

    /**
     * Transparent constructor.
     *
     * @param string $data String comment data.
     * @param int $line
     * @param int $col
     */
    public function __construct($data, $line = null, $col = null)
    {
        $this->data = $data;
        $this->line = $line;
        $this->col = $col;
    }

    public function toNode() {
        return new HTMLPurifier_Node_Comment($this->data, $this->line, $this->col);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Concrete text token class.
 *
 * Text tokens comprise of regular parsed character data (PCDATA) and raw
 * character data (from the CDATA sections). Internally, their
 * data is parsed with all entities expanded. Surprisingly, the text token
 * does have a "tag name" called #PCDATA, which is how the DTD represents it
 * in permissible child nodes.
 */
class HTMLPurifier_Token_Text extends HTMLPurifier_Token
{

    /**
     * @type string
     */
    public $name = '#PCDATA';
    /**< PCDATA tag name compatible with DTD. */

    /**
     * @type string
     */
    public $data;
    /**< Parsed character data of text. */

    /**
     * @type bool
     */
    public $is_whitespace;

    /**< Bool indicating if node is whitespace. */

    /**
     * Constructor, accepts data and determines if it is whitespace.
     * @param string $data String parsed character data.
     * @param int $line
     * @param int $col
     */
    public function __construct($data, $line = null, $col = null)
    {
        $this->data = $data;
        $this->is_whitespace = ctype_space($data);
        $this->line = $line;
        $this->col = $col;
    }

    public function toNode() {
        return new HTMLPurifier_Node_Text($this->data, $this->is_whitespace, $this->line, $this->col);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Concrete start token class.
 */
class HTMLPurifier_Token_Start extends HTMLPurifier_Token_Tag
{
}

// vim: et sw=4 sts=4
<?php

/**
 * Defines allowed child nodes and validates nodes against it.
 */
abstract class HTMLPurifier_ChildDef
{
    /**
     * Type of child definition, usually right-most part of class name lowercase.
     * Used occasionally in terms of context.
     * @type string
     */
    public $type;

    /**
     * Indicates whether or not an empty array of children is okay.
     *
     * This is necessary for redundant checking when changes affecting
     * a child node may cause a parent node to now be disallowed.
     * @type bool
     */
    public $allow_empty;

    /**
     * Lookup array of all elements that this definition could possibly allow.
     * @type array
     */
    public $elements = array();

    /**
     * Get lookup of tag names that should not close this element automatically.
     * All other elements will do so.
     * @param HTMLPurifier_Config $config HTMLPurifier_Config object
     * @return array
     */
    public function getAllowedElements($config)
    {
        return $this->elements;
    }

    /**
     * Validates nodes according to definition and returns modification.
     *
     * @param HTMLPurifier_Node[] $children Array of HTMLPurifier_Node
     * @param HTMLPurifier_Config $config HTMLPurifier_Config object
     * @param HTMLPurifier_Context $context HTMLPurifier_Context object
     * @return bool|array true to leave nodes as is, false to remove parent node, array of replacement children
     */
    abstract public function validateChildren($children, $config, $context);
}

// vim: et sw=4 sts=4
<?php

/**
 * Provides lookup array of attribute types to HTMLPurifier_AttrDef objects
 */
class HTMLPurifier_AttrTypes
{
    /**
     * Lookup array of attribute string identifiers to concrete implementations.
     * @type HTMLPurifier_AttrDef[]
     */
    protected $info = array();

    /**
     * Constructs the info array, supplying default implementations for attribute
     * types.
     */
    public function __construct()
    {
        // XXX This is kind of poor, since we don't actually /clone/
        // instances; instead, we use the supplied make() attribute. So,
        // the underlying class must know how to deal with arguments.
        // With the old implementation of Enum, that ignored its
        // arguments when handling a make dispatch, the IAlign
        // definition wouldn't work.

        // pseudo-types, must be instantiated via shorthand
        $this->info['Enum']    = new HTMLPurifier_AttrDef_Enum();
        $this->info['Bool']    = new HTMLPurifier_AttrDef_HTML_Bool();

        $this->info['CDATA']    = new HTMLPurifier_AttrDef_Text();
        $this->info['ID']       = new HTMLPurifier_AttrDef_HTML_ID();
        $this->info['Length']   = new HTMLPurifier_AttrDef_HTML_Length();
        $this->info['MultiLength'] = new HTMLPurifier_AttrDef_HTML_MultiLength();
        $this->info['NMTOKENS'] = new HTMLPurifier_AttrDef_HTML_Nmtokens();
        $this->info['Pixels']   = new HTMLPurifier_AttrDef_HTML_Pixels();
        $this->info['Text']     = new HTMLPurifier_AttrDef_Text();
        $this->info['URI']      = new HTMLPurifier_AttrDef_URI();
        $this->info['LanguageCode'] = new HTMLPurifier_AttrDef_Lang();
        $this->info['Color']    = new HTMLPurifier_AttrDef_HTML_Color();
        $this->info['IAlign']   = self::makeEnum('top,middle,bottom,left,right');
        $this->info['LAlign']   = self::makeEnum('top,bottom,left,right');
        $this->info['FrameTarget'] = new HTMLPurifier_AttrDef_HTML_FrameTarget();

        // unimplemented aliases
        $this->info['ContentType'] = new HTMLPurifier_AttrDef_Text();
        $this->info['ContentTypes'] = new HTMLPurifier_AttrDef_Text();
        $this->info['Charsets'] = new HTMLPurifier_AttrDef_Text();
        $this->info['Character'] = new HTMLPurifier_AttrDef_Text();

        // "proprietary" types
        $this->info['Class'] = new HTMLPurifier_AttrDef_HTML_Class();

        // number is really a positive integer (one or more digits)
        // FIXME: ^^ not always, see start and value of list items
        $this->info['Number']   = new HTMLPurifier_AttrDef_Integer(false, false, true);
    }

    private static function makeEnum($in)
    {
        return new HTMLPurifier_AttrDef_Clone(new HTMLPurifier_AttrDef_Enum(explode(',', $in)));
    }

    /**
     * Retrieves a type
     * @param string $type String type name
     * @return HTMLPurifier_AttrDef Object AttrDef for type
     */
    public function get($type)
    {
        // determine if there is any extra info tacked on
        if (strpos($type, '#') !== false) {
            list($type, $string) = explode('#', $type, 2);
        } else {
            $string = '';
        }

        if (!isset($this->info[$type])) {
            trigger_error('Cannot retrieve undefined attribute type ' . $type, E_USER_ERROR);
            return;
        }
        return $this->info[$type]->make($string);
    }

    /**
     * Sets a new implementation for a type
     * @param string $type String type name
     * @param HTMLPurifier_AttrDef $impl Object AttrDef for type
     */
    public function set($type, $impl)
    {
        $this->info[$type] = $impl;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validator for the components of a URI for a specific scheme
 */
abstract class HTMLPurifier_URIScheme
{

    /**
     * Scheme's default port (integer). If an explicit port number is
     * specified that coincides with the default port, it will be
     * elided.
     * @type int
     */
    public $default_port = null;

    /**
     * Whether or not URIs of this scheme are locatable by a browser
     * http and ftp are accessible, while mailto and news are not.
     * @type bool
     */
    public $browsable = false;

    /**
     * Whether or not data transmitted over this scheme is encrypted.
     * https is secure, http is not.
     * @type bool
     */
    public $secure = false;

    /**
     * Whether or not the URI always uses <hier_part>, resolves edge cases
     * with making relative URIs absolute
     * @type bool
     */
    public $hierarchical = false;

    /**
     * Whether or not the URI may omit a hostname when the scheme is
     * explicitly specified, ala file:///path/to/file. As of writing,
     * 'file' is the only scheme that browsers support his properly.
     * @type bool
     */
    public $may_omit_host = false;

    /**
     * Validates the components of a URI for a specific scheme.
     * @param HTMLPurifier_URI $uri Reference to a HTMLPurifier_URI object
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool success or failure
     */
    abstract public function doValidate(&$uri, $config, $context);

    /**
     * Public interface for validating components of a URI.  Performs a
     * bunch of default actions. Don't overload this method.
     * @param HTMLPurifier_URI $uri Reference to a HTMLPurifier_URI object
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool success or failure
     */
    public function validate(&$uri, $config, $context)
    {
        if ($this->default_port == $uri->port) {
            $uri->port = null;
        }
        // kludge: browsers do funny things when the scheme but not the
        // authority is set
        if (!$this->may_omit_host &&
            // if the scheme is present, a missing host is always in error
            (!is_null($uri->scheme) && ($uri->host === '' || is_null($uri->host))) ||
            // if the scheme is not present, a *blank* host is in error,
            // since this translates into '///path' which most browsers
            // interpret as being 'http://path'.
            (is_null($uri->scheme) && $uri->host === '')
        ) {
            do {
                if (is_null($uri->scheme)) {
                    if (substr($uri->path, 0, 2) != '//') {
                        $uri->host = null;
                        break;
                    }
                    // URI is '////path', so we cannot nullify the
                    // host to preserve semantics.  Try expanding the
                    // hostname instead (fall through)
                }
                // first see if we can manually insert a hostname
                $host = $config->get('URI.Host');
                if (!is_null($host)) {
                    $uri->host = $host;
                } else {
                    // we can't do anything sensible, reject the URL.
                    return false;
                }
            } while (false);
        }
        return $this->doValidate($uri, $config, $context);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Processes an entire attribute array for corrections needing multiple values.
 *
 * Occasionally, a certain attribute will need to be removed and popped onto
 * another value.  Instead of creating a complex return syntax for
 * HTMLPurifier_AttrDef, we just pass the whole attribute array to a
 * specialized object and have that do the special work.  That is the
 * family of HTMLPurifier_AttrTransform.
 *
 * An attribute transformation can be assigned to run before or after
 * HTMLPurifier_AttrDef validation.  See HTMLPurifier_HTMLDefinition for
 * more details.
 */

abstract class HTMLPurifier_AttrTransform
{

    /**
     * Abstract: makes changes to the attributes dependent on multiple values.
     *
     * @param array $attr Assoc array of attributes, usually from
     *              HTMLPurifier_Token_Tag::$attr
     * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object.
     * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object
     * @return array Processed attribute array.
     */
    abstract public function transform($attr, $config, $context);

    /**
     * Prepends CSS properties to the style attribute, creating the
     * attribute if it doesn't exist.
     * @param array &$attr Attribute array to process (passed by reference)
     * @param string $css CSS to prepend
     */
    public function prependCSS(&$attr, $css)
    {
        $attr['style'] = isset($attr['style']) ? $attr['style'] : '';
        $attr['style'] = $css . $attr['style'];
    }

    /**
     * Retrieves and removes an attribute
     * @param array &$attr Attribute array to process (passed by reference)
     * @param mixed $key Key of attribute to confiscate
     * @return mixed
     */
    public function confiscateAttr(&$attr, $key)
    {
        if (!isset($attr[$key])) {
            return null;
        }
        $value = $attr[$key];
        unset($attr[$key]);
        return $value;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Records errors for particular segments of an HTML document such as tokens,
 * attributes or CSS properties. They can contain error structs (which apply
 * to components of what they represent), but their main purpose is to hold
 * errors applying to whatever struct is being used.
 */
class HTMLPurifier_ErrorStruct
{

    /**
     * Possible values for $children first-key. Note that top-level structures
     * are automatically token-level.
     */
    const TOKEN     = 0;
    const ATTR      = 1;
    const CSSPROP   = 2;

    /**
     * Type of this struct.
     * @type string
     */
    public $type;

    /**
     * Value of the struct we are recording errors for. There are various
     * values for this:
     *  - TOKEN: Instance of HTMLPurifier_Token
     *  - ATTR: array('attr-name', 'value')
     *  - CSSPROP: array('prop-name', 'value')
     * @type mixed
     */
    public $value;

    /**
     * Errors registered for this structure.
     * @type array
     */
    public $errors = array();

    /**
     * Child ErrorStructs that are from this structure. For example, a TOKEN
     * ErrorStruct would contain ATTR ErrorStructs. This is a multi-dimensional
     * array in structure: [TYPE]['identifier']
     * @type array
     */
    public $children = array();

    /**
     * @param string $type
     * @param string $id
     * @return mixed
     */
    public function getChild($type, $id)
    {
        if (!isset($this->children[$type][$id])) {
            $this->children[$type][$id] = new HTMLPurifier_ErrorStruct();
            $this->children[$type][$id]->type = $type;
        }
        return $this->children[$type][$id];
    }

    /**
     * @param int $severity
     * @param string $message
     */
    public function addError($severity, $message)
    {
        $this->errors[] = array($severity, $message);
    }
}

// vim: et sw=4 sts=4
<?php

class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCache
{

    /**
     * @param HTMLPurifier_Definition $def
     * @param HTMLPurifier_Config $config
     * @return int|bool
     */
    public function add($def, $config)
    {
        if (!$this->checkDefType($def)) {
            return;
        }
        $file = $this->generateFilePath($config);
        if (file_exists($file)) {
            return false;
        }
        if (!$this->_prepareDir($config)) {
            return false;
        }
        return $this->_write($file, serialize($def), $config);
    }

    /**
     * @param HTMLPurifier_Definition $def
     * @param HTMLPurifier_Config $config
     * @return int|bool
     */
    public function set($def, $config)
    {
        if (!$this->checkDefType($def)) {
            return;
        }
        $file = $this->generateFilePath($config);
        if (!$this->_prepareDir($config)) {
            return false;
        }
        return $this->_write($file, serialize($def), $config);
    }

    /**
     * @param HTMLPurifier_Definition $def
     * @param HTMLPurifier_Config $config
     * @return int|bool
     */
    public function replace($def, $config)
    {
        if (!$this->checkDefType($def)) {
            return;
        }
        $file = $this->generateFilePath($config);
        if (!file_exists($file)) {
            return false;
        }
        if (!$this->_prepareDir($config)) {
            return false;
        }
        return $this->_write($file, serialize($def), $config);
    }

    /**
     * @param HTMLPurifier_Config $config
     * @return bool|HTMLPurifier_Config
     */
    public function get($config)
    {
        $file = $this->generateFilePath($config);
        if (!file_exists($file)) {
            return false;
        }
        return unserialize(file_get_contents($file));
    }

    /**
     * @param HTMLPurifier_Config $config
     * @return bool
     */
    public function remove($config)
    {
        $file = $this->generateFilePath($config);
        if (!file_exists($file)) {
            return false;
        }
        return unlink($file);
    }

    /**
     * @param HTMLPurifier_Config $config
     * @return bool
     */
    public function flush($config)
    {
        if (!$this->_prepareDir($config)) {
            return false;
        }
        $dir = $this->generateDirectoryPath($config);
        $dh = opendir($dir);
        // Apparently, on some versions of PHP, readdir will return
        // an empty string if you pass an invalid argument to readdir.
        // So you need this test.  See #49.
        if (false === $dh) {
            return false;
        }
        while (false !== ($filename = readdir($dh))) {
            if (empty($filename)) {
                continue;
            }
            if ($filename[0] === '.') {
                continue;
            }
            unlink($dir . '/' . $filename);
        }
        closedir($dh);
        return true;
    }

    /**
     * @param HTMLPurifier_Config $config
     * @return bool
     */
    public function cleanup($config)
    {
        if (!$this->_prepareDir($config)) {
            return false;
        }
        $dir = $this->generateDirectoryPath($config);
        $dh = opendir($dir);
        // See #49 (and above).
        if (false === $dh) {
            return false;
        }
        while (false !== ($filename = readdir($dh))) {
            if (empty($filename)) {
                continue;
            }
            if ($filename[0] === '.') {
                continue;
            }
            $key = substr($filename, 0, strlen($filename) - 4);
            if ($this->isOld($key, $config)) {
                unlink($dir . '/' . $filename);
            }
        }
        closedir($dh);
        return true;
    }

    /**
     * Generates the file path to the serial file corresponding to
     * the configuration and definition name
     * @param HTMLPurifier_Config $config
     * @return string
     * @todo Make protected
     */
    public function generateFilePath($config)
    {
        $key = $this->generateKey($config);
        return $this->generateDirectoryPath($config) . '/' . $key . '.ser';
    }

    /**
     * Generates the path to the directory contain this cache's serial files
     * @param HTMLPurifier_Config $config
     * @return string
     * @note No trailing slash
     * @todo Make protected
     */
    public function generateDirectoryPath($config)
    {
        $base = $this->generateBaseDirectoryPath($config);
        return $base . '/' . $this->type;
    }

    /**
     * Generates path to base directory that contains all definition type
     * serials
     * @param HTMLPurifier_Config $config
     * @return mixed|string
     * @todo Make protected
     */
    public function generateBaseDirectoryPath($config)
    {
        $base = $config->get('Cache.SerializerPath');
        $base = is_null($base) ? HTMLPURIFIER_PREFIX . '/HTMLPurifier/DefinitionCache/Serializer' : $base;
        return $base;
    }

    /**
     * Convenience wrapper function for file_put_contents
     * @param string $file File name to write to
     * @param string $data Data to write into file
     * @param HTMLPurifier_Config $config
     * @return int|bool Number of bytes written if success, or false if failure.
     */
    private function _write($file, $data, $config)
    {
        $result = file_put_contents($file, $data);
        if ($result !== false) {
            // set permissions of the new file (no execute)
            $chmod = $config->get('Cache.SerializerPermissions');
            if ($chmod !== null) {
                chmod($file, $chmod & 0666);
            }
        }
        return $result;
    }

    /**
     * Prepares the directory that this type stores the serials in
     * @param HTMLPurifier_Config $config
     * @return bool True if successful
     */
    private function _prepareDir($config)
    {
        $directory = $this->generateDirectoryPath($config);
        $chmod = $config->get('Cache.SerializerPermissions');
        if ($chmod === null) {
            if (!@mkdir($directory) && !is_dir($directory)) {
                trigger_error(
                    'Could not create directory ' . $directory . '',
                    E_USER_WARNING
                );
                return false;
            }
            return true;
        }
        if (!is_dir($directory)) {
            $base = $this->generateBaseDirectoryPath($config);
            if (!is_dir($base)) {
                trigger_error(
                    'Base directory ' . $base . ' does not exist,
                    please create or change using %Cache.SerializerPath',
                    E_USER_WARNING
                );
                return false;
            } elseif (!$this->_testPermissions($base, $chmod)) {
                return false;
            }
            if (!@mkdir($directory, $chmod) && !is_dir($directory)) {
                trigger_error(
                    'Could not create directory ' . $directory . '',
                    E_USER_WARNING
                );
                return false;
            }
            if (!$this->_testPermissions($directory, $chmod)) {
                return false;
            }
        } elseif (!$this->_testPermissions($directory, $chmod)) {
            return false;
        }
        return true;
    }

    /**
     * Tests permissions on a directory and throws out friendly
     * error messages and attempts to chmod it itself if possible
     * @param string $dir Directory path
     * @param int $chmod Permissions
     * @return bool True if directory is writable
     */
    private function _testPermissions($dir, $chmod)
    {
        // early abort, if it is writable, everything is hunky-dory
        if (is_writable($dir)) {
            return true;
        }
        if (!is_dir($dir)) {
            // generally, you'll want to handle this beforehand
            // so a more specific error message can be given
            trigger_error(
                'Directory ' . $dir . ' does not exist',
                E_USER_WARNING
            );
            return false;
        }
        if (function_exists('posix_getuid') && $chmod !== null) {
            // POSIX system, we can give more specific advice
            if (fileowner($dir) === posix_getuid()) {
                // we can chmod it ourselves
                $chmod = $chmod | 0700;
                if (chmod($dir, $chmod)) {
                    return true;
                }
            } elseif (filegroup($dir) === posix_getgid()) {
                $chmod = $chmod | 0070;
            } else {
                // PHP's probably running as nobody, so we'll
                // need to give global permissions
                $chmod = $chmod | 0777;
            }
            trigger_error(
                'Directory ' . $dir . ' not writable, ' .
                'please chmod to ' . decoct($chmod),
                E_USER_WARNING
            );
        } else {
            // generic error message
            trigger_error(
                'Directory ' . $dir . ' not writable, ' .
                'please alter file permissions',
                E_USER_WARNING
            );
        }
        return false;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Definition cache decorator class that cleans up the cache
 * whenever there is a cache miss.
 */
class HTMLPurifier_DefinitionCache_Decorator_Cleanup extends HTMLPurifier_DefinitionCache_Decorator
{
    /**
     * @type string
     */
    public $name = 'Cleanup';

    /**
     * @return HTMLPurifier_DefinitionCache_Decorator_Cleanup
     */
    public function copy()
    {
        return new HTMLPurifier_DefinitionCache_Decorator_Cleanup();
    }

    /**
     * @param HTMLPurifier_Definition $def
     * @param HTMLPurifier_Config $config
     * @return mixed
     */
    public function add($def, $config)
    {
        $status = parent::add($def, $config);
        if (!$status) {
            parent::cleanup($config);
        }
        return $status;
    }

    /**
     * @param HTMLPurifier_Definition $def
     * @param HTMLPurifier_Config $config
     * @return mixed
     */
    public function set($def, $config)
    {
        $status = parent::set($def, $config);
        if (!$status) {
            parent::cleanup($config);
        }
        return $status;
    }

    /**
     * @param HTMLPurifier_Definition $def
     * @param HTMLPurifier_Config $config
     * @return mixed
     */
    public function replace($def, $config)
    {
        $status = parent::replace($def, $config);
        if (!$status) {
            parent::cleanup($config);
        }
        return $status;
    }

    /**
     * @param HTMLPurifier_Config $config
     * @return mixed
     */
    public function get($config)
    {
        $ret = parent::get($config);
        if (!$ret) {
            parent::cleanup($config);
        }
        return $ret;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Definition cache decorator class that saves all cache retrievals
 * to PHP's memory; good for unit tests or circumstances where
 * there are lots of configuration objects floating around.
 */
class HTMLPurifier_DefinitionCache_Decorator_Memory extends HTMLPurifier_DefinitionCache_Decorator
{
    /**
     * @type array
     */
    protected $definitions;

    /**
     * @type string
     */
    public $name = 'Memory';

    /**
     * @return HTMLPurifier_DefinitionCache_Decorator_Memory
     */
    public function copy()
    {
        return new HTMLPurifier_DefinitionCache_Decorator_Memory();
    }

    /**
     * @param HTMLPurifier_Definition $def
     * @param HTMLPurifier_Config $config
     * @return mixed
     */
    public function add($def, $config)
    {
        $status = parent::add($def, $config);
        if ($status) {
            $this->definitions[$this->generateKey($config)] = $def;
        }
        return $status;
    }

    /**
     * @param HTMLPurifier_Definition $def
     * @param HTMLPurifier_Config $config
     * @return mixed
     */
    public function set($def, $config)
    {
        $status = parent::set($def, $config);
        if ($status) {
            $this->definitions[$this->generateKey($config)] = $def;
        }
        return $status;
    }

    /**
     * @param HTMLPurifier_Definition $def
     * @param HTMLPurifier_Config $config
     * @return mixed
     */
    public function replace($def, $config)
    {
        $status = parent::replace($def, $config);
        if ($status) {
            $this->definitions[$this->generateKey($config)] = $def;
        }
        return $status;
    }

    /**
     * @param HTMLPurifier_Config $config
     * @return mixed
     */
    public function get($config)
    {
        $key = $this->generateKey($config);
        if (isset($this->definitions[$key])) {
            return $this->definitions[$key];
        }
        $this->definitions[$key] = parent::get($config);
        return $this->definitions[$key];
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Null cache object to use when no caching is on.
 */
class HTMLPurifier_DefinitionCache_Null extends HTMLPurifier_DefinitionCache
{

    /**
     * @param HTMLPurifier_Definition $def
     * @param HTMLPurifier_Config $config
     * @return bool
     */
    public function add($def, $config)
    {
        return false;
    }

    /**
     * @param HTMLPurifier_Definition $def
     * @param HTMLPurifier_Config $config
     * @return bool
     */
    public function set($def, $config)
    {
        return false;
    }

    /**
     * @param HTMLPurifier_Definition $def
     * @param HTMLPurifier_Config $config
     * @return bool
     */
    public function replace($def, $config)
    {
        return false;
    }

    /**
     * @param HTMLPurifier_Config $config
     * @return bool
     */
    public function remove($config)
    {
        return false;
    }

    /**
     * @param HTMLPurifier_Config $config
     * @return bool
     */
    public function get($config)
    {
        return false;
    }

    /**
     * @param HTMLPurifier_Config $config
     * @return bool
     */
    public function flush($config)
    {
        return false;
    }

    /**
     * @param HTMLPurifier_Config $config
     * @return bool
     */
    public function cleanup($config)
    {
        return false;
    }
}

// vim: et sw=4 sts=4
<?php

class HTMLPurifier_DefinitionCache_Decorator extends HTMLPurifier_DefinitionCache
{

    /**
     * Cache object we are decorating
     * @type HTMLPurifier_DefinitionCache
     */
    public $cache;

    /**
     * The name of the decorator
     * @var string
     */
    public $name;

    public function __construct()
    {
    }

    /**
     * Lazy decorator function
     * @param HTMLPurifier_DefinitionCache $cache Reference to cache object to decorate
     * @return HTMLPurifier_DefinitionCache_Decorator
     */
    public function decorate(&$cache)
    {
        $decorator = $this->copy();
        // reference is necessary for mocks in PHP 4
        $decorator->cache =& $cache;
        $decorator->type = $cache->type;
        return $decorator;
    }

    /**
     * Cross-compatible clone substitute
     * @return HTMLPurifier_DefinitionCache_Decorator
     */
    public function copy()
    {
        return new HTMLPurifier_DefinitionCache_Decorator();
    }

    /**
     * @param HTMLPurifier_Definition $def
     * @param HTMLPurifier_Config $config
     * @return mixed
     */
    public function add($def, $config)
    {
        return $this->cache->add($def, $config);
    }

    /**
     * @param HTMLPurifier_Definition $def
     * @param HTMLPurifier_Config $config
     * @return mixed
     */
    public function set($def, $config)
    {
        return $this->cache->set($def, $config);
    }

    /**
     * @param HTMLPurifier_Definition $def
     * @param HTMLPurifier_Config $config
     * @return mixed
     */
    public function replace($def, $config)
    {
        return $this->cache->replace($def, $config);
    }

    /**
     * @param HTMLPurifier_Config $config
     * @return mixed
     */
    public function get($config)
    {
        return $this->cache->get($config);
    }

    /**
     * @param HTMLPurifier_Config $config
     * @return mixed
     */
    public function remove($config)
    {
        return $this->cache->remove($config);
    }

    /**
     * @param HTMLPurifier_Config $config
     * @return mixed
     */
    public function flush($config)
    {
        return $this->cache->flush($config);
    }

    /**
     * @param HTMLPurifier_Config $config
     * @return mixed
     */
    public function cleanup($config)
    {
        return $this->cache->cleanup($config);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Abstract base token class that all others inherit from.
 */
abstract class HTMLPurifier_Token
{
    /**
     * Line number node was on in source document. Null if unknown.
     * @type int
     */
    public $line;

    /**
     * Column of line node was on in source document. Null if unknown.
     * @type int
     */
    public $col;

    /**
     * Lookup array of processing that this token is exempt from.
     * Currently, valid values are "ValidateAttributes" and
     * "MakeWellFormed_TagClosedError"
     * @type array
     */
    public $armor = array();

    /**
     * Used during MakeWellFormed.  See Note [Injector skips]
     * @type
     */
    public $skip;

    /**
     * @type
     */
    public $rewind;

    /**
     * @type
     */
    public $carryover;

    /**
     * @param string $n
     * @return null|string
     */
    public function __get($n)
    {
        if ($n === 'type') {
            trigger_error('Deprecated type property called; use instanceof', E_USER_NOTICE);
            switch (get_class($this)) {
                case 'HTMLPurifier_Token_Start':
                    return 'start';
                case 'HTMLPurifier_Token_Empty':
                    return 'empty';
                case 'HTMLPurifier_Token_End':
                    return 'end';
                case 'HTMLPurifier_Token_Text':
                    return 'text';
                case 'HTMLPurifier_Token_Comment':
                    return 'comment';
                default:
                    return null;
            }
        }
    }

    /**
     * Sets the position of the token in the source document.
     * @param int $l
     * @param int $c
     */
    public function position($l = null, $c = null)
    {
        $this->line = $l;
        $this->col = $c;
    }

    /**
     * Convenience function for DirectLex settings line/col position.
     * @param int $l
     * @param int $c
     */
    public function rawPosition($l, $c)
    {
        if ($c === -1) {
            $l++;
        }
        $this->line = $l;
        $this->col = $c;
    }

    /**
     * Converts a token into its corresponding node.
     */
    abstract public function toNode();
}

// vim: et sw=4 sts=4
<?php

/**
 * A UTF-8 specific character encoder that handles cleaning and transforming.
 * @note All functions in this class should be static.
 */
class HTMLPurifier_Encoder
{

    /**
     * Constructor throws fatal error if you attempt to instantiate class
     */
    private function __construct()
    {
        trigger_error('Cannot instantiate encoder, call methods statically', E_USER_ERROR);
    }

    /**
     * Error-handler that mutes errors, alternative to shut-up operator.
     */
    public static function muteErrorHandler()
    {
    }

    /**
     * iconv wrapper which mutes errors, but doesn't work around bugs.
     * @param string $in Input encoding
     * @param string $out Output encoding
     * @param string $text The text to convert
     * @return string
     */
    public static function unsafeIconv($in, $out, $text)
    {
        set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler'));
        $r = iconv($in, $out, $text);
        restore_error_handler();
        return $r;
    }

    /**
     * iconv wrapper which mutes errors and works around bugs.
     * @param string $in Input encoding
     * @param string $out Output encoding
     * @param string $text The text to convert
     * @param int $max_chunk_size
     * @return string
     */
    public static function iconv($in, $out, $text, $max_chunk_size = 8000)
    {
        $code = self::testIconvTruncateBug();
        if ($code == self::ICONV_OK) {
            return self::unsafeIconv($in, $out, $text);
        } elseif ($code == self::ICONV_TRUNCATES) {
            // we can only work around this if the input character set
            // is utf-8
            if ($in == 'utf-8') {
                if ($max_chunk_size < 4) {
                    trigger_error('max_chunk_size is too small', E_USER_WARNING);
                    return false;
                }
                // split into 8000 byte chunks, but be careful to handle
                // multibyte boundaries properly
                if (($c = strlen($text)) <= $max_chunk_size) {
                    return self::unsafeIconv($in, $out, $text);
                }
                $r = '';
                $i = 0;
                while (true) {
                    if ($i + $max_chunk_size >= $c) {
                        $r .= self::unsafeIconv($in, $out, substr($text, $i));
                        break;
                    }
                    // wibble the boundary
                    if (0x80 != (0xC0 & ord($text[$i + $max_chunk_size]))) {
                        $chunk_size = $max_chunk_size;
                    } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 1]))) {
                        $chunk_size = $max_chunk_size - 1;
                    } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 2]))) {
                        $chunk_size = $max_chunk_size - 2;
                    } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 3]))) {
                        $chunk_size = $max_chunk_size - 3;
                    } else {
                        return false; // rather confusing UTF-8...
                    }
                    $chunk = substr($text, $i, $chunk_size); // substr doesn't mind overlong lengths
                    $r .= self::unsafeIconv($in, $out, $chunk);
                    $i += $chunk_size;
                }
                return $r;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    /**
     * Cleans a UTF-8 string for well-formedness and SGML validity
     *
     * It will parse according to UTF-8 and return a valid UTF8 string, with
     * non-SGML codepoints excluded.
     *
     * Specifically, it will permit:
     * \x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}
     * Source: https://www.w3.org/TR/REC-xml/#NT-Char
     * Arguably this function should be modernized to the HTML5 set
     * of allowed characters:
     * https://www.w3.org/TR/html5/syntax.html#preprocessing-the-input-stream
     * which simultaneously expand and restrict the set of allowed characters.
     *
     * @param string $str The string to clean
     * @param bool $force_php
     * @return string
     *
     * @note Just for reference, the non-SGML code points are 0 to 31 and
     *       127 to 159, inclusive.  However, we allow code points 9, 10
     *       and 13, which are the tab, line feed and carriage return
     *       respectively. 128 and above the code points map to multibyte
     *       UTF-8 representations.
     *
     * @note Fallback code adapted from utf8ToUnicode by Henri Sivonen and
     *       hsivonen@iki.fi at <http://iki.fi/hsivonen/php-utf8/> under the
     *       LGPL license.  Notes on what changed are inside, but in general,
     *       the original code transformed UTF-8 text into an array of integer
     *       Unicode codepoints. Understandably, transforming that back to
     *       a string would be somewhat expensive, so the function was modded to
     *       directly operate on the string.  However, this discourages code
     *       reuse, and the logic enumerated here would be useful for any
     *       function that needs to be able to understand UTF-8 characters.
     *       As of right now, only smart lossless character encoding converters
     *       would need that, and I'm probably not going to implement them.
     */
    public static function cleanUTF8($str, $force_php = false)
    {
        // UTF-8 validity is checked since PHP 4.3.5
        // This is an optimization: if the string is already valid UTF-8, no
        // need to do PHP stuff. 99% of the time, this will be the case.
        if (preg_match(
            '/^[\x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]*$/Du',
            $str
        )) {
            return $str;
        }

        $mState = 0; // cached expected number of octets after the current octet
                     // until the beginning of the next UTF8 character sequence
        $mUcs4  = 0; // cached Unicode character
        $mBytes = 1; // cached expected number of octets in the current sequence

        // original code involved an $out that was an array of Unicode
        // codepoints.  Instead of having to convert back into UTF-8, we've
        // decided to directly append valid UTF-8 characters onto a string
        // $out once they're done.  $char accumulates raw bytes, while $mUcs4
        // turns into the Unicode code point, so there's some redundancy.

        $out = '';
        $char = '';

        $len = strlen($str);
        for ($i = 0; $i < $len; $i++) {
            $in = ord($str{$i});
            $char .= $str[$i]; // append byte to char
            if (0 == $mState) {
                // When mState is zero we expect either a US-ASCII character
                // or a multi-octet sequence.
                if (0 == (0x80 & ($in))) {
                    // US-ASCII, pass straight through.
                    if (($in <= 31 || $in == 127) &&
                        !($in == 9 || $in == 13 || $in == 10) // save \r\t\n
                    ) {
                        // control characters, remove
                    } else {
                        $out .= $char;
                    }
                    // reset
                    $char = '';
                    $mBytes = 1;
                } elseif (0xC0 == (0xE0 & ($in))) {
                    // First octet of 2 octet sequence
                    $mUcs4 = ($in);
                    $mUcs4 = ($mUcs4 & 0x1F) << 6;
                    $mState = 1;
                    $mBytes = 2;
                } elseif (0xE0 == (0xF0 & ($in))) {
                    // First octet of 3 octet sequence
                    $mUcs4 = ($in);
                    $mUcs4 = ($mUcs4 & 0x0F) << 12;
                    $mState = 2;
                    $mBytes = 3;
                } elseif (0xF0 == (0xF8 & ($in))) {
                    // First octet of 4 octet sequence
                    $mUcs4 = ($in);
                    $mUcs4 = ($mUcs4 & 0x07) << 18;
                    $mState = 3;
                    $mBytes = 4;
                } elseif (0xF8 == (0xFC & ($in))) {
                    // First octet of 5 octet sequence.
                    //
                    // This is illegal because the encoded codepoint must be
                    // either:
                    // (a) not the shortest form or
                    // (b) outside the Unicode range of 0-0x10FFFF.
                    // Rather than trying to resynchronize, we will carry on
                    // until the end of the sequence and let the later error
                    // handling code catch it.
                    $mUcs4 = ($in);
                    $mUcs4 = ($mUcs4 & 0x03) << 24;
                    $mState = 4;
                    $mBytes = 5;
                } elseif (0xFC == (0xFE & ($in))) {
                    // First octet of 6 octet sequence, see comments for 5
                    // octet sequence.
                    $mUcs4 = ($in);
                    $mUcs4 = ($mUcs4 & 1) << 30;
                    $mState = 5;
                    $mBytes = 6;
                } else {
                    // Current octet is neither in the US-ASCII range nor a
                    // legal first octet of a multi-octet sequence.
                    $mState = 0;
                    $mUcs4  = 0;
                    $mBytes = 1;
                    $char = '';
                }
            } else {
                // When mState is non-zero, we expect a continuation of the
                // multi-octet sequence
                if (0x80 == (0xC0 & ($in))) {
                    // Legal continuation.
                    $shift = ($mState - 1) * 6;
                    $tmp = $in;
                    $tmp = ($tmp & 0x0000003F) << $shift;
                    $mUcs4 |= $tmp;

                    if (0 == --$mState) {
                        // End of the multi-octet sequence. mUcs4 now contains
                        // the final Unicode codepoint to be output

                        // Check for illegal sequences and codepoints.

                        // From Unicode 3.1, non-shortest form is illegal
                        if (((2 == $mBytes) && ($mUcs4 < 0x0080)) ||
                            ((3 == $mBytes) && ($mUcs4 < 0x0800)) ||
                            ((4 == $mBytes) && ($mUcs4 < 0x10000)) ||
                            (4 < $mBytes) ||
                            // From Unicode 3.2, surrogate characters = illegal
                            (($mUcs4 & 0xFFFFF800) == 0xD800) ||
                            // Codepoints outside the Unicode range are illegal
                            ($mUcs4 > 0x10FFFF)
                        ) {

                        } elseif (0xFEFF != $mUcs4 && // omit BOM
                            // check for valid Char unicode codepoints
                            (
                                0x9 == $mUcs4 ||
                                0xA == $mUcs4 ||
                                0xD == $mUcs4 ||
                                (0x20 <= $mUcs4 && 0x7E >= $mUcs4) ||
                                // 7F-9F is not strictly prohibited by XML,
                                // but it is non-SGML, and thus we don't allow it
                                (0xA0 <= $mUcs4 && 0xD7FF >= $mUcs4) ||
                                (0xE000 <= $mUcs4 && 0xFFFD >= $mUcs4) ||
                                (0x10000 <= $mUcs4 && 0x10FFFF >= $mUcs4)
                            )
                        ) {
                            $out .= $char;
                        }
                        // initialize UTF8 cache (reset)
                        $mState = 0;
                        $mUcs4  = 0;
                        $mBytes = 1;
                        $char = '';
                    }
                } else {
                    // ((0xC0 & (*in) != 0x80) && (mState != 0))
                    // Incomplete multi-octet sequence.
                    // used to result in complete fail, but we'll reset
                    $mState = 0;
                    $mUcs4  = 0;
                    $mBytes = 1;
                    $char ='';
                }
            }
        }
        return $out;
    }

    /**
     * Translates a Unicode codepoint into its corresponding UTF-8 character.
     * @note Based on Feyd's function at
     *       <http://forums.devnetwork.net/viewtopic.php?p=191404#191404>,
     *       which is in public domain.
     * @note While we're going to do code point parsing anyway, a good
     *       optimization would be to refuse to translate code points that
     *       are non-SGML characters.  However, this could lead to duplication.
     * @note This is very similar to the unichr function in
     *       maintenance/generate-entity-file.php (although this is superior,
     *       due to its sanity checks).
     */

    // +----------+----------+----------+----------+
    // | 33222222 | 22221111 | 111111   |          |
    // | 10987654 | 32109876 | 54321098 | 76543210 | bit
    // +----------+----------+----------+----------+
    // |          |          |          | 0xxxxxxx | 1 byte 0x00000000..0x0000007F
    // |          |          | 110yyyyy | 10xxxxxx | 2 byte 0x00000080..0x000007FF
    // |          | 1110zzzz | 10yyyyyy | 10xxxxxx | 3 byte 0x00000800..0x0000FFFF
    // | 11110www | 10wwzzzz | 10yyyyyy | 10xxxxxx | 4 byte 0x00010000..0x0010FFFF
    // +----------+----------+----------+----------+
    // | 00000000 | 00011111 | 11111111 | 11111111 | Theoretical upper limit of legal scalars: 2097151 (0x001FFFFF)
    // | 00000000 | 00010000 | 11111111 | 11111111 | Defined upper limit of legal scalar codes
    // +----------+----------+----------+----------+

    public static function unichr($code)
    {
        if ($code > 1114111 or $code < 0 or
          ($code >= 55296 and $code <= 57343) ) {
            // bits are set outside the "valid" range as defined
            // by UNICODE 4.1.0
            return '';
        }

        $x = $y = $z = $w = 0;
        if ($code < 128) {
            // regular ASCII character
            $x = $code;
        } else {
            // set up bits for UTF-8
            $x = ($code & 63) | 128;
            if ($code < 2048) {
                $y = (($code & 2047) >> 6) | 192;
            } else {
                $y = (($code & 4032) >> 6) | 128;
                if ($code < 65536) {
                    $z = (($code >> 12) & 15) | 224;
                } else {
                    $z = (($code >> 12) & 63) | 128;
                    $w = (($code >> 18) & 7)  | 240;
                }
            }
        }
        // set up the actual character
        $ret = '';
        if ($w) {
            $ret .= chr($w);
        }
        if ($z) {
            $ret .= chr($z);
        }
        if ($y) {
            $ret .= chr($y);
        }
        $ret .= chr($x);

        return $ret;
    }

    /**
     * @return bool
     */
    public static function iconvAvailable()
    {
        static $iconv = null;
        if ($iconv === null) {
            $iconv = function_exists('iconv') && self::testIconvTruncateBug() != self::ICONV_UNUSABLE;
        }
        return $iconv;
    }

    /**
     * Convert a string to UTF-8 based on configuration.
     * @param string $str The string to convert
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return string
     */
    public static function convertToUTF8($str, $config, $context)
    {
        $encoding = $config->get('Core.Encoding');
        if ($encoding === 'utf-8') {
            return $str;
        }
        static $iconv = null;
        if ($iconv === null) {
            $iconv = self::iconvAvailable();
        }
        if ($iconv && !$config->get('Test.ForceNoIconv')) {
            // unaffected by bugs, since UTF-8 support all characters
            $str = self::unsafeIconv($encoding, 'utf-8//IGNORE', $str);
            if ($str === false) {
                // $encoding is not a valid encoding
                trigger_error('Invalid encoding ' . $encoding, E_USER_ERROR);
                return '';
            }
            // If the string is bjorked by Shift_JIS or a similar encoding
            // that doesn't support all of ASCII, convert the naughty
            // characters to their true byte-wise ASCII/UTF-8 equivalents.
            $str = strtr($str, self::testEncodingSupportsASCII($encoding));
            return $str;
        } elseif ($encoding === 'iso-8859-1') {
            $str = utf8_encode($str);
            return $str;
        }
        $bug = HTMLPurifier_Encoder::testIconvTruncateBug();
        if ($bug == self::ICONV_OK) {
            trigger_error('Encoding not supported, please install iconv', E_USER_ERROR);
        } else {
            trigger_error(
                'You have a buggy version of iconv, see https://bugs.php.net/bug.php?id=48147 ' .
                'and http://sourceware.org/bugzilla/show_bug.cgi?id=13541',
                E_USER_ERROR
            );
        }
    }

    /**
     * Converts a string from UTF-8 based on configuration.
     * @param string $str The string to convert
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return string
     * @note Currently, this is a lossy conversion, with unexpressable
     *       characters being omitted.
     */
    public static function convertFromUTF8($str, $config, $context)
    {
        $encoding = $config->get('Core.Encoding');
        if ($escape = $config->get('Core.EscapeNonASCIICharacters')) {
            $str = self::convertToASCIIDumbLossless($str);
        }
        if ($encoding === 'utf-8') {
            return $str;
        }
        static $iconv = null;
        if ($iconv === null) {
            $iconv = self::iconvAvailable();
        }
        if ($iconv && !$config->get('Test.ForceNoIconv')) {
            // Undo our previous fix in convertToUTF8, otherwise iconv will barf
            $ascii_fix = self::testEncodingSupportsASCII($encoding);
            if (!$escape && !empty($ascii_fix)) {
                $clear_fix = array();
                foreach ($ascii_fix as $utf8 => $native) {
                    $clear_fix[$utf8] = '';
                }
                $str = strtr($str, $clear_fix);
            }
            $str = strtr($str, array_flip($ascii_fix));
            // Normal stuff
            $str = self::iconv('utf-8', $encoding . '//IGNORE', $str);
            return $str;
        } elseif ($encoding === 'iso-8859-1') {
            $str = utf8_decode($str);
            return $str;
        }
        trigger_error('Encoding not supported', E_USER_ERROR);
        // You might be tempted to assume that the ASCII representation
        // might be OK, however, this is *not* universally true over all
        // encodings.  So we take the conservative route here, rather
        // than forcibly turn on %Core.EscapeNonASCIICharacters
    }

    /**
     * Lossless (character-wise) conversion of HTML to ASCII
     * @param string $str UTF-8 string to be converted to ASCII
     * @return string ASCII encoded string with non-ASCII character entity-ized
     * @warning Adapted from MediaWiki, claiming fair use: this is a common
     *       algorithm. If you disagree with this license fudgery,
     *       implement it yourself.
     * @note Uses decimal numeric entities since they are best supported.
     * @note This is a DUMB function: it has no concept of keeping
     *       character entities that the projected character encoding
     *       can allow. We could possibly implement a smart version
     *       but that would require it to also know which Unicode
     *       codepoints the charset supported (not an easy task).
     * @note Sort of with cleanUTF8() but it assumes that $str is
     *       well-formed UTF-8
     */
    public static function convertToASCIIDumbLossless($str)
    {
        $bytesleft = 0;
        $result = '';
        $working = 0;
        $len = strlen($str);
        for ($i = 0; $i < $len; $i++) {
            $bytevalue = ord($str[$i]);
            if ($bytevalue <= 0x7F) { //0xxx xxxx
                $result .= chr($bytevalue);
                $bytesleft = 0;
            } elseif ($bytevalue <= 0xBF) { //10xx xxxx
                $working = $working << 6;
                $working += ($bytevalue & 0x3F);
                $bytesleft--;
                if ($bytesleft <= 0) {
                    $result .= "&#" . $working . ";";
                }
            } elseif ($bytevalue <= 0xDF) { //110x xxxx
                $working = $bytevalue & 0x1F;
                $bytesleft = 1;
            } elseif ($bytevalue <= 0xEF) { //1110 xxxx
                $working = $bytevalue & 0x0F;
                $bytesleft = 2;
            } else { //1111 0xxx
                $working = $bytevalue & 0x07;
                $bytesleft = 3;
            }
        }
        return $result;
    }

    /** No bugs detected in iconv. */
    const ICONV_OK = 0;

    /** Iconv truncates output if converting from UTF-8 to another
     *  character set with //IGNORE, and a non-encodable character is found */
    const ICONV_TRUNCATES = 1;

    /** Iconv does not support //IGNORE, making it unusable for
     *  transcoding purposes */
    const ICONV_UNUSABLE = 2;

    /**
     * glibc iconv has a known bug where it doesn't handle the magic
     * //IGNORE stanza correctly.  In particular, rather than ignore
     * characters, it will return an EILSEQ after consuming some number
     * of characters, and expect you to restart iconv as if it were
     * an E2BIG.  Old versions of PHP did not respect the errno, and
     * returned the fragment, so as a result you would see iconv
     * mysteriously truncating output. We can work around this by
     * manually chopping our input into segments of about 8000
     * characters, as long as PHP ignores the error code.  If PHP starts
     * paying attention to the error code, iconv becomes unusable.
     *
     * @return int Error code indicating severity of bug.
     */
    public static function testIconvTruncateBug()
    {
        static $code = null;
        if ($code === null) {
            // better not use iconv, otherwise infinite loop!
            $r = self::unsafeIconv('utf-8', 'ascii//IGNORE', "\xCE\xB1" . str_repeat('a', 9000));
            if ($r === false) {
                $code = self::ICONV_UNUSABLE;
            } elseif (($c = strlen($r)) < 9000) {
                $code = self::ICONV_TRUNCATES;
            } elseif ($c > 9000) {
                trigger_error(
                    'Your copy of iconv is extremely buggy. Please notify HTML Purifier maintainers: ' .
                    'include your iconv version as per phpversion()',
                    E_USER_ERROR
                );
            } else {
                $code = self::ICONV_OK;
            }
        }
        return $code;
    }

    /**
     * This expensive function tests whether or not a given character
     * encoding supports ASCII. 7/8-bit encodings like Shift_JIS will
     * fail this test, and require special processing. Variable width
     * encodings shouldn't ever fail.
     *
     * @param string $encoding Encoding name to test, as per iconv format
     * @param bool $bypass Whether or not to bypass the precompiled arrays.
     * @return Array of UTF-8 characters to their corresponding ASCII,
     *      which can be used to "undo" any overzealous iconv action.
     */
    public static function testEncodingSupportsASCII($encoding, $bypass = false)
    {
        // All calls to iconv here are unsafe, proof by case analysis:
        // If ICONV_OK, no difference.
        // If ICONV_TRUNCATE, all calls involve one character inputs,
        // so bug is not triggered.
        // If ICONV_UNUSABLE, this call is irrelevant
        static $encodings = array();
        if (!$bypass) {
            if (isset($encodings[$encoding])) {
                return $encodings[$encoding];
            }
            $lenc = strtolower($encoding);
            switch ($lenc) {
                case 'shift_jis':
                    return array("\xC2\xA5" => '\\', "\xE2\x80\xBE" => '~');
                case 'johab':
                    return array("\xE2\x82\xA9" => '\\');
            }
            if (strpos($lenc, 'iso-8859-') === 0) {
                return array();
            }
        }
        $ret = array();
        if (self::unsafeIconv('UTF-8', $encoding, 'a') === false) {
            return false;
        }
        for ($i = 0x20; $i <= 0x7E; $i++) { // all printable ASCII chars
            $c = chr($i); // UTF-8 char
            $r = self::unsafeIconv('UTF-8', "$encoding//IGNORE", $c); // initial conversion
            if ($r === '' ||
                // This line is needed for iconv implementations that do not
                // omit characters that do not exist in the target character set
                ($r === $c && self::unsafeIconv($encoding, 'UTF-8//IGNORE', $r) !== $c)
            ) {
                // Reverse engineer: what's the UTF-8 equiv of this byte
                // sequence? This assumes that there's no variable width
                // encoding that doesn't support ASCII.
                $ret[self::unsafeIconv($encoding, 'UTF-8//IGNORE', $c)] = $c;
            }
        }
        $encodings[$encoding] = $ret;
        return $ret;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Property list iterator. Do not instantiate this class directly.
 */
class HTMLPurifier_PropertyListIterator extends FilterIterator
{

    /**
     * @type int
     */
    protected $l;
    /**
     * @type string
     */
    protected $filter;

    /**
     * @param Iterator $iterator Array of data to iterate over
     * @param string $filter Optional prefix to only allow values of
     */
    public function __construct(Iterator $iterator, $filter = null)
    {
        parent::__construct($iterator);
        $this->l = strlen($filter);
        $this->filter = $filter;
    }

    /**
     * @return bool
     */
    public function accept()
    {
        $key = $this->getInnerIterator()->key();
        if (strncmp($key, $this->filter, $this->l) !== 0) {
            return false;
        }
        return true;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Post-transform that copies lang's value to xml:lang (and vice-versa)
 * @note Theoretically speaking, this could be a pre-transform, but putting
 *       post is more efficient.
 */
class HTMLPurifier_AttrTransform_Lang extends HTMLPurifier_AttrTransform
{

    /**
     * @param array $attr
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    public function transform($attr, $config, $context)
    {
        $lang = isset($attr['lang']) ? $attr['lang'] : false;
        $xml_lang = isset($attr['xml:lang']) ? $attr['xml:lang'] : false;

        if ($lang !== false && $xml_lang === false) {
            $attr['xml:lang'] = $lang;
        } elseif ($xml_lang !== false) {
            $attr['lang'] = $xml_lang;
        }
        return $attr;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Pre-transform that changes deprecated border attribute to CSS.
 */
class HTMLPurifier_AttrTransform_Border extends HTMLPurifier_AttrTransform
{
    /**
     * @param array $attr
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    public function transform($attr, $config, $context)
    {
        if (!isset($attr['border'])) {
            return $attr;
        }
        $border_width = $this->confiscateAttr($attr, 'border');
        // some validation should happen here
        $this->prependCSS($attr, "border:{$border_width}px solid;");
        return $attr;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Writes default type for all objects. Currently only supports flash.
 */
class HTMLPurifier_AttrTransform_SafeObject extends HTMLPurifier_AttrTransform
{
    /**
     * @type string
     */
    public $name = "SafeObject";

    /**
     * @param array $attr
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    public function transform($attr, $config, $context)
    {
        if (!isset($attr['type'])) {
            $attr['type'] = 'application/x-shockwave-flash';
        }
        return $attr;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Pre-transform that changes deprecated hspace and vspace attributes to CSS
 */
class HTMLPurifier_AttrTransform_ImgSpace extends HTMLPurifier_AttrTransform
{
    /**
     * @type string
     */
    protected $attr;

    /**
     * @type array
     */
    protected $css = array(
        'hspace' => array('left', 'right'),
        'vspace' => array('top', 'bottom')
    );

    /**
     * @param string $attr
     */
    public function __construct($attr)
    {
        $this->attr = $attr;
        if (!isset($this->css[$attr])) {
            trigger_error(htmlspecialchars($attr) . ' is not valid space attribute');
        }
    }

    /**
     * @param array $attr
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    public function transform($attr, $config, $context)
    {
        if (!isset($attr[$this->attr])) {
            return $attr;
        }

        $width = $this->confiscateAttr($attr, $this->attr);
        // some validation could happen here

        if (!isset($this->css[$this->attr])) {
            return $attr;
        }

        $style = '';
        foreach ($this->css[$this->attr] as $suffix) {
            $property = "margin-$suffix";
            $style .= "$property:{$width}px;";
        }
        $this->prependCSS($attr, $style);
        return $attr;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Implements required attribute stipulation for <script>
 */
class HTMLPurifier_AttrTransform_ScriptRequired extends HTMLPurifier_AttrTransform
{
    /**
     * @param array $attr
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    public function transform($attr, $config, $context)
    {
        if (!isset($attr['type'])) {
            $attr['type'] = 'text/javascript';
        }
        return $attr;
    }
}

// vim: et sw=4 sts=4
<?php

// must be called POST validation

/**
 * Adds rel="nofollow" to all outbound links.  This transform is
 * only attached if Attr.Nofollow is TRUE.
 */
class HTMLPurifier_AttrTransform_Nofollow extends HTMLPurifier_AttrTransform
{
    /**
     * @type HTMLPurifier_URIParser
     */
    private $parser;

    public function __construct()
    {
        $this->parser = new HTMLPurifier_URIParser();
    }

    /**
     * @param array $attr
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    public function transform($attr, $config, $context)
    {
        if (!isset($attr['href'])) {
            return $attr;
        }

        // XXX Kind of inefficient
        $url = $this->parser->parse($attr['href']);
        $scheme = $url->getSchemeObj($config, $context);

        if ($scheme->browsable && !$url->isLocal($config, $context)) {
            if (isset($attr['rel'])) {
                $rels = explode(' ', $attr['rel']);
                if (!in_array('nofollow', $rels)) {
                    $rels[] = 'nofollow';
                }
                $attr['rel'] = implode(' ', $rels);
            } else {
                $attr['rel'] = 'nofollow';
            }
        }
        return $attr;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates name/value pairs in param tags to be used in safe objects. This
 * will only allow name values it recognizes, and pre-fill certain attributes
 * with required values.
 *
 * @note
 *      This class only supports Flash. In the future, Quicktime support
 *      may be added.
 *
 * @warning
 *      This class expects an injector to add the necessary parameters tags.
 */
class HTMLPurifier_AttrTransform_SafeParam extends HTMLPurifier_AttrTransform
{
    /**
     * @type string
     */
    public $name = "SafeParam";

    /**
     * @type HTMLPurifier_AttrDef_URI
     */
    private $uri;

    public function __construct()
    {
        $this->uri = new HTMLPurifier_AttrDef_URI(true); // embedded
        $this->wmode = new HTMLPurifier_AttrDef_Enum(array('window', 'opaque', 'transparent'));
    }

    /**
     * @param array $attr
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    public function transform($attr, $config, $context)
    {
        // If we add support for other objects, we'll need to alter the
        // transforms.
        switch ($attr['name']) {
            // application/x-shockwave-flash
            // Keep this synchronized with Injector/SafeObject.php
            case 'allowScriptAccess':
                $attr['value'] = 'never';
                break;
            case 'allowNetworking':
                $attr['value'] = 'internal';
                break;
            case 'allowFullScreen':
                if ($config->get('HTML.FlashAllowFullScreen')) {
                    $attr['value'] = ($attr['value'] == 'true') ? 'true' : 'false';
                } else {
                    $attr['value'] = 'false';
                }
                break;
            case 'wmode':
                $attr['value'] = $this->wmode->validate($attr['value'], $config, $context);
                break;
            case 'movie':
            case 'src':
                $attr['name'] = "movie";
                $attr['value'] = $this->uri->validate($attr['value'], $config, $context);
                break;
            case 'flashvars':
                // we're going to allow arbitrary inputs to the SWF, on
                // the reasoning that it could only hack the SWF, not us.
                break;
            // add other cases to support other param name/value pairs
            default:
                $attr['name'] = $attr['value'] = null;
        }
        return $attr;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Generic pre-transform that converts an attribute with a fixed number of
 * values (enumerated) to CSS.
 */
class HTMLPurifier_AttrTransform_EnumToCSS extends HTMLPurifier_AttrTransform
{
    /**
     * Name of attribute to transform from.
     * @type string
     */
    protected $attr;

    /**
     * Lookup array of attribute values to CSS.
     * @type array
     */
    protected $enumToCSS = array();

    /**
     * Case sensitivity of the matching.
     * @type bool
     * @warning Currently can only be guaranteed to work with ASCII
     *          values.
     */
    protected $caseSensitive = false;

    /**
     * @param string $attr Attribute name to transform from
     * @param array $enum_to_css Lookup array of attribute values to CSS
     * @param bool $case_sensitive Case sensitivity indicator, default false
     */
    public function __construct($attr, $enum_to_css, $case_sensitive = false)
    {
        $this->attr = $attr;
        $this->enumToCSS = $enum_to_css;
        $this->caseSensitive = (bool)$case_sensitive;
    }

    /**
     * @param array $attr
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    public function transform($attr, $config, $context)
    {
        if (!isset($attr[$this->attr])) {
            return $attr;
        }

        $value = trim($attr[$this->attr]);
        unset($attr[$this->attr]);

        if (!$this->caseSensitive) {
            $value = strtolower($value);
        }

        if (!isset($this->enumToCSS[$value])) {
            return $attr;
        }
        $this->prependCSS($attr, $this->enumToCSS[$value]);
        return $attr;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Pre-transform that changes proprietary background attribute to CSS.
 */
class HTMLPurifier_AttrTransform_Background extends HTMLPurifier_AttrTransform
{
    /**
     * @param array $attr
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    public function transform($attr, $config, $context)
    {
        if (!isset($attr['background'])) {
            return $attr;
        }

        $background = $this->confiscateAttr($attr, 'background');
        // some validation should happen here

        $this->prependCSS($attr, "background-image:url($background);");
        return $attr;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Pre-transform that changes deprecated bgcolor attribute to CSS.
 */
class HTMLPurifier_AttrTransform_BgColor extends HTMLPurifier_AttrTransform
{
    /**
     * @param array $attr
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    public function transform($attr, $config, $context)
    {
        if (!isset($attr['bgcolor'])) {
            return $attr;
        }

        $bgcolor = $this->confiscateAttr($attr, 'bgcolor');
        // some validation should happen here

        $this->prependCSS($attr, "background-color:$bgcolor;");
        return $attr;
    }
}

// vim: et sw=4 sts=4
<?php

// this MUST be placed in post, as it assumes that any value in dir is valid

/**
 * Post-trasnform that ensures that bdo tags have the dir attribute set.
 */
class HTMLPurifier_AttrTransform_BdoDir extends HTMLPurifier_AttrTransform
{

    /**
     * @param array $attr
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    public function transform($attr, $config, $context)
    {
        if (isset($attr['dir'])) {
            return $attr;
        }
        $attr['dir'] = $config->get('Attr.DefaultTextDir');
        return $attr;
    }
}

// vim: et sw=4 sts=4
<?php

// must be called POST validation

/**
 * Adds rel="noopener" to any links which target a different window
 * than the current one.  This is used to prevent malicious websites
 * from silently replacing the original window, which could be used
 * to do phishing.
 * This transform is controlled by %HTML.TargetNoopener.
 */
class HTMLPurifier_AttrTransform_TargetNoopener extends HTMLPurifier_AttrTransform
{
    /**
     * @param array $attr
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    public function transform($attr, $config, $context)
    {
        if (isset($attr['rel'])) {
            $rels = explode(' ', $attr['rel']);
        } else {
            $rels = array();
        }
        if (isset($attr['target']) && !in_array('noopener', $rels)) {
            $rels[] = 'noopener';
        }
        if (!empty($rels) || isset($attr['rel'])) {
            $attr['rel'] = implode(' ', $rels);
        }

        return $attr;
    }
}

<?php

/**
 * Post-transform that performs validation to the name attribute; if
 * it is present with an equivalent id attribute, it is passed through;
 * otherwise validation is performed.
 */
class HTMLPurifier_AttrTransform_NameSync extends HTMLPurifier_AttrTransform
{

    public function __construct()
    {
        $this->idDef = new HTMLPurifier_AttrDef_HTML_ID();
    }

    /**
     * @param array $attr
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    public function transform($attr, $config, $context)
    {
        if (!isset($attr['name'])) {
            return $attr;
        }
        $name = $attr['name'];
        if (isset($attr['id']) && $attr['id'] === $name) {
            return $attr;
        }
        $result = $this->idDef->validate($name, $config, $context);
        if ($result === false) {
            unset($attr['name']);
        } else {
            $attr['name'] = $result;
        }
        return $attr;
    }
}

// vim: et sw=4 sts=4
<?php

// must be called POST validation

/**
 * Transform that supplies default values for the src and alt attributes
 * in img tags, as well as prevents the img tag from being removed
 * because of a missing alt tag. This needs to be registered as both
 * a pre and post attribute transform.
 */
class HTMLPurifier_AttrTransform_ImgRequired extends HTMLPurifier_AttrTransform
{

    /**
     * @param array $attr
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    public function transform($attr, $config, $context)
    {
        $src = true;
        if (!isset($attr['src'])) {
            if ($config->get('Core.RemoveInvalidImg')) {
                return $attr;
            }
            $attr['src'] = $config->get('Attr.DefaultInvalidImage');
            $src = false;
        }

        if (!isset($attr['alt'])) {
            if ($src) {
                $alt = $config->get('Attr.DefaultImageAlt');
                if ($alt === null) {
                    $attr['alt'] = basename($attr['src']);
                } else {
                    $attr['alt'] = $alt;
                }
            } else {
                $attr['alt'] = $config->get('Attr.DefaultInvalidImageAlt');
            }
        }
        return $attr;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Pre-transform that changes converts a boolean attribute to fixed CSS
 */
class HTMLPurifier_AttrTransform_BoolToCSS extends HTMLPurifier_AttrTransform
{
    /**
     * Name of boolean attribute that is trigger.
     * @type string
     */
    protected $attr;

    /**
     * CSS declarations to add to style, needs trailing semicolon.
     * @type string
     */
    protected $css;

    /**
     * @param string $attr attribute name to convert from
     * @param string $css CSS declarations to add to style (needs semicolon)
     */
    public function __construct($attr, $css)
    {
        $this->attr = $attr;
        $this->css = $css;
    }

    /**
     * @param array $attr
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    public function transform($attr, $config, $context)
    {
        if (!isset($attr[$this->attr])) {
            return $attr;
        }
        unset($attr[$this->attr]);
        $this->prependCSS($attr, $this->css);
        return $attr;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Performs miscellaneous cross attribute validation and filtering for
 * input elements. This is meant to be a post-transform.
 */
class HTMLPurifier_AttrTransform_Input extends HTMLPurifier_AttrTransform
{
    /**
     * @type HTMLPurifier_AttrDef_HTML_Pixels
     */
    protected $pixels;

    public function __construct()
    {
        $this->pixels = new HTMLPurifier_AttrDef_HTML_Pixels();
    }

    /**
     * @param array $attr
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    public function transform($attr, $config, $context)
    {
        if (!isset($attr['type'])) {
            $t = 'text';
        } else {
            $t = strtolower($attr['type']);
        }
        if (isset($attr['checked']) && $t !== 'radio' && $t !== 'checkbox') {
            unset($attr['checked']);
        }
        if (isset($attr['maxlength']) && $t !== 'text' && $t !== 'password') {
            unset($attr['maxlength']);
        }
        if (isset($attr['size']) && $t !== 'text' && $t !== 'password') {
            $result = $this->pixels->validate($attr['size'], $config, $context);
            if ($result === false) {
                unset($attr['size']);
            } else {
                $attr['size'] = $result;
            }
        }
        if (isset($attr['src']) && $t !== 'image') {
            unset($attr['src']);
        }
        if (!isset($attr['value']) && ($t === 'radio' || $t === 'checkbox')) {
            $attr['value'] = '';
        }
        return $attr;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Sets height/width defaults for <textarea>
 */
class HTMLPurifier_AttrTransform_Textarea extends HTMLPurifier_AttrTransform
{
    /**
     * @param array $attr
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    public function transform($attr, $config, $context)
    {
        // Calculated from Firefox
        if (!isset($attr['cols'])) {
            $attr['cols'] = '22';
        }
        if (!isset($attr['rows'])) {
            $attr['rows'] = '3';
        }
        return $attr;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Class for handling width/height length attribute transformations to CSS
 */
class HTMLPurifier_AttrTransform_Length extends HTMLPurifier_AttrTransform
{

    /**
     * @type string
     */
    protected $name;

    /**
     * @type string
     */
    protected $cssName;

    public function __construct($name, $css_name = null)
    {
        $this->name = $name;
        $this->cssName = $css_name ? $css_name : $name;
    }

    /**
     * @param array $attr
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    public function transform($attr, $config, $context)
    {
        if (!isset($attr[$this->name])) {
            return $attr;
        }
        $length = $this->confiscateAttr($attr, $this->name);
        if (ctype_digit($length)) {
            $length .= 'px';
        }
        $this->prependCSS($attr, $this->cssName . ":$length;");
        return $attr;
    }
}

// vim: et sw=4 sts=4
<?php

// must be called POST validation

/**
 * Adds rel="noreferrer" to any links which target a different window
 * than the current one.  This is used to prevent malicious websites
 * from silently replacing the original window, which could be used
 * to do phishing.
 * This transform is controlled by %HTML.TargetNoreferrer.
 */
class HTMLPurifier_AttrTransform_TargetNoreferrer extends HTMLPurifier_AttrTransform
{
    /**
     * @param array $attr
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    public function transform($attr, $config, $context)
    {
        if (isset($attr['rel'])) {
            $rels = explode(' ', $attr['rel']);
        } else {
            $rels = array();
        }
        if (isset($attr['target']) && !in_array('noreferrer', $rels)) {
            $rels[] = 'noreferrer';
        }
        if (!empty($rels) || isset($attr['rel'])) {
            $attr['rel'] = implode(' ', $rels);
        }

        return $attr;
    }
}

<?php

/**
 * Pre-transform that changes deprecated name attribute to ID if necessary
 */
class HTMLPurifier_AttrTransform_Name extends HTMLPurifier_AttrTransform
{

    /**
     * @param array $attr
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    public function transform($attr, $config, $context)
    {
        // Abort early if we're using relaxed definition of name
        if ($config->get('HTML.Attr.Name.UseCDATA')) {
            return $attr;
        }
        if (!isset($attr['name'])) {
            return $attr;
        }
        $id = $this->confiscateAttr($attr, 'name');
        if (isset($attr['id'])) {
            return $attr;
        }
        $attr['id'] = $id;
        return $attr;
    }
}

// vim: et sw=4 sts=4
<?php

// must be called POST validation

/**
 * Adds target="blank" to all outbound links.  This transform is
 * only attached if Attr.TargetBlank is TRUE.  This works regardless
 * of whether or not Attr.AllowedFrameTargets
 */
class HTMLPurifier_AttrTransform_TargetBlank extends HTMLPurifier_AttrTransform
{
    /**
     * @type HTMLPurifier_URIParser
     */
    private $parser;

    public function __construct()
    {
        $this->parser = new HTMLPurifier_URIParser();
    }

    /**
     * @param array $attr
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    public function transform($attr, $config, $context)
    {
        if (!isset($attr['href'])) {
            return $attr;
        }

        // XXX Kind of inefficient
        $url = $this->parser->parse($attr['href']);
        $scheme = $url->getSchemeObj($config, $context);

        if ($scheme->browsable && !$url->isBenign($config, $context)) {
            $attr['target'] = '_blank';
        }
        return $attr;
    }
}

// vim: et sw=4 sts=4
<?php

class HTMLPurifier_AttrTransform_SafeEmbed extends HTMLPurifier_AttrTransform
{
    /**
     * @type string
     */
    public $name = "SafeEmbed";

    /**
     * @param array $attr
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    public function transform($attr, $config, $context)
    {
        $attr['allowscriptaccess'] = 'never';
        $attr['allownetworking'] = 'internal';
        $attr['type'] = 'application/x-shockwave-flash';
        return $attr;
    }
}

// vim: et sw=4 sts=4
<?php

class HTMLPurifier_HTMLModule_XMLCommonAttributes extends HTMLPurifier_HTMLModule
{
    /**
     * @type string
     */
    public $name = 'XMLCommonAttributes';

    /**
     * @type array
     */
    public $attr_collections = array(
        'Lang' => array(
            'xml:lang' => 'LanguageCode',
        )
    );
}

// vim: et sw=4 sts=4
<?php

/**
 * A "safe" object module. In theory, objects permitted by this module will
 * be safe, and untrusted users can be allowed to embed arbitrary flash objects
 * (maybe other types too, but only Flash is supported as of right now).
 * Highly experimental.
 */
class HTMLPurifier_HTMLModule_SafeObject extends HTMLPurifier_HTMLModule
{
    /**
     * @type string
     */
    public $name = 'SafeObject';

    /**
     * @param HTMLPurifier_Config $config
     */
    public function setup($config)
    {
        // These definitions are not intrinsically safe: the attribute transforms
        // are a vital part of ensuring safety.

        $max = $config->get('HTML.MaxImgLength');
        $object = $this->addElement(
            'object',
            'Inline',
            'Optional: param | Flow | #PCDATA',
            'Common',
            array(
                // While technically not required by the spec, we're forcing
                // it to this value.
                'type' => 'Enum#application/x-shockwave-flash',
                'width' => 'Pixels#' . $max,
                'height' => 'Pixels#' . $max,
                'data' => 'URI#embedded',
                'codebase' => new HTMLPurifier_AttrDef_Enum(
                    array(
                        'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0'
                    )
                ),
            )
        );
        $object->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeObject();

        $param = $this->addElement(
            'param',
            false,
            'Empty',
            false,
            array(
                'id' => 'ID',
                'name*' => 'Text',
                'value' => 'Text'
            )
        );
        $param->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeParam();
        $this->info_injector[] = 'SafeObject';
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Module adds the nofollow attribute transformation to a tags.  It
 * is enabled by HTML.Nofollow
 */
class HTMLPurifier_HTMLModule_Nofollow extends HTMLPurifier_HTMLModule
{

    /**
     * @type string
     */
    public $name = 'Nofollow';

    /**
     * @param HTMLPurifier_Config $config
     */
    public function setup($config)
    {
        $a = $this->addBlankElement('a');
        $a->attr_transform_post[] = new HTMLPurifier_AttrTransform_Nofollow();
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * XHTML 1.1 Ruby Annotation Module, defines elements that indicate
 * short runs of text alongside base text for annotation or pronounciation.
 */
class HTMLPurifier_HTMLModule_Ruby extends HTMLPurifier_HTMLModule
{

    /**
     * @type string
     */
    public $name = 'Ruby';

    /**
     * @param HTMLPurifier_Config $config
     */
    public function setup($config)
    {
        $this->addElement(
            'ruby',
            'Inline',
            'Custom: ((rb, (rt | (rp, rt, rp))) | (rbc, rtc, rtc?))',
            'Common'
        );
        $this->addElement('rbc', false, 'Required: rb', 'Common');
        $this->addElement('rtc', false, 'Required: rt', 'Common');
        $rb = $this->addElement('rb', false, 'Inline', 'Common');
        $rb->excludes = array('ruby' => true);
        $rt = $this->addElement('rt', false, 'Inline', 'Common', array('rbspan' => 'Number'));
        $rt->excludes = array('ruby' => true);
        $this->addElement('rp', false, 'Optional: #PCDATA', 'Common');
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Abstract class for a set of proprietary modules that clean up (tidy)
 * poorly written HTML.
 * @todo Figure out how to protect some of these methods/properties
 */
class HTMLPurifier_HTMLModule_Tidy extends HTMLPurifier_HTMLModule
{
    /**
     * List of supported levels.
     * Index zero is a special case "no fixes" level.
     * @type array
     */
    public $levels = array(0 => 'none', 'light', 'medium', 'heavy');

    /**
     * Default level to place all fixes in.
     * Disabled by default.
     * @type string
     */
    public $defaultLevel = null;

    /**
     * Lists of fixes used by getFixesForLevel().
     * Format is:
     *      HTMLModule_Tidy->fixesForLevel[$level] = array('fix-1', 'fix-2');
     * @type array
     */
    public $fixesForLevel = array(
        'light' => array(),
        'medium' => array(),
        'heavy' => array()
    );

    /**
     * Lazy load constructs the module by determining the necessary
     * fixes to create and then delegating to the populate() function.
     * @param HTMLPurifier_Config $config
     * @todo Wildcard matching and error reporting when an added or
     *       subtracted fix has no effect.
     */
    public function setup($config)
    {
        // create fixes, initialize fixesForLevel
        $fixes = $this->makeFixes();
        $this->makeFixesForLevel($fixes);

        // figure out which fixes to use
        $level = $config->get('HTML.TidyLevel');
        $fixes_lookup = $this->getFixesForLevel($level);

        // get custom fix declarations: these need namespace processing
        $add_fixes = $config->get('HTML.TidyAdd');
        $remove_fixes = $config->get('HTML.TidyRemove');

        foreach ($fixes as $name => $fix) {
            // needs to be refactored a little to implement globbing
            if (isset($remove_fixes[$name]) ||
                (!isset($add_fixes[$name]) && !isset($fixes_lookup[$name]))) {
                unset($fixes[$name]);
            }
        }

        // populate this module with necessary fixes
        $this->populate($fixes);
    }

    /**
     * Retrieves all fixes per a level, returning fixes for that specific
     * level as well as all levels below it.
     * @param string $level level identifier, see $levels for valid values
     * @return array Lookup up table of fixes
     */
    public function getFixesForLevel($level)
    {
        if ($level == $this->levels[0]) {
            return array();
        }
        $activated_levels = array();
        for ($i = 1, $c = count($this->levels); $i < $c; $i++) {
            $activated_levels[] = $this->levels[$i];
            if ($this->levels[$i] == $level) {
                break;
            }
        }
        if ($i == $c) {
            trigger_error(
                'Tidy level ' . htmlspecialchars($level) . ' not recognized',
                E_USER_WARNING
            );
            return array();
        }
        $ret = array();
        foreach ($activated_levels as $level) {
            foreach ($this->fixesForLevel[$level] as $fix) {
                $ret[$fix] = true;
            }
        }
        return $ret;
    }

    /**
     * Dynamically populates the $fixesForLevel member variable using
     * the fixes array. It may be custom overloaded, used in conjunction
     * with $defaultLevel, or not used at all.
     * @param array $fixes
     */
    public function makeFixesForLevel($fixes)
    {
        if (!isset($this->defaultLevel)) {
            return;
        }
        if (!isset($this->fixesForLevel[$this->defaultLevel])) {
            trigger_error(
                'Default level ' . $this->defaultLevel . ' does not exist',
                E_USER_ERROR
            );
            return;
        }
        $this->fixesForLevel[$this->defaultLevel] = array_keys($fixes);
    }

    /**
     * Populates the module with transforms and other special-case code
     * based on a list of fixes passed to it
     * @param array $fixes Lookup table of fixes to activate
     */
    public function populate($fixes)
    {
        foreach ($fixes as $name => $fix) {
            // determine what the fix is for
            list($type, $params) = $this->getFixType($name);
            switch ($type) {
                case 'attr_transform_pre':
                case 'attr_transform_post':
                    $attr = $params['attr'];
                    if (isset($params['element'])) {
                        $element = $params['element'];
                        if (empty($this->info[$element])) {
                            $e = $this->addBlankElement($element);
                        } else {
                            $e = $this->info[$element];
                        }
                    } else {
                        $type = "info_$type";
                        $e = $this;
                    }
                    // PHP does some weird parsing when I do
                    // $e->$type[$attr], so I have to assign a ref.
                    $f =& $e->$type;
                    $f[$attr] = $fix;
                    break;
                case 'tag_transform':
                    $this->info_tag_transform[$params['element']] = $fix;
                    break;
                case 'child':
                case 'content_model_type':
                    $element = $params['element'];
                    if (empty($this->info[$element])) {
                        $e = $this->addBlankElement($element);
                    } else {
                        $e = $this->info[$element];
                    }
                    $e->$type = $fix;
                    break;
                default:
                    trigger_error("Fix type $type not supported", E_USER_ERROR);
                    break;
            }
        }
    }

    /**
     * Parses a fix name and determines what kind of fix it is, as well
     * as other information defined by the fix
     * @param $name String name of fix
     * @return array(string $fix_type, array $fix_parameters)
     * @note $fix_parameters is type dependant, see populate() for usage
     *       of these parameters
     */
    public function getFixType($name)
    {
        // parse it
        $property = $attr = null;
        if (strpos($name, '#') !== false) {
            list($name, $property) = explode('#', $name);
        }
        if (strpos($name, '@') !== false) {
            list($name, $attr) = explode('@', $name);
        }

        // figure out the parameters
        $params = array();
        if ($name !== '') {
            $params['element'] = $name;
        }
        if (!is_null($attr)) {
            $params['attr'] = $attr;
        }

        // special case: attribute transform
        if (!is_null($attr)) {
            if (is_null($property)) {
                $property = 'pre';
            }
            $type = 'attr_transform_' . $property;
            return array($type, $params);
        }

        // special case: tag transform
        if (is_null($property)) {
            return array('tag_transform', $params);
        }

        return array($property, $params);

    }

    /**
     * Defines all fixes the module will perform in a compact
     * associative array of fix name to fix implementation.
     * @return array
     */
    public function makeFixes()
    {
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * XHTML 1.1 Presentation Module, defines simple presentation-related
 * markup. Text Extension Module.
 * @note The official XML Schema and DTD specs further divide this into
 *       two modules:
 *          - Block Presentation (hr)
 *          - Inline Presentation (b, big, i, small, sub, sup, tt)
 *       We have chosen not to heed this distinction, as content_sets
 *       provides satisfactory disambiguation.
 */
class HTMLPurifier_HTMLModule_Presentation extends HTMLPurifier_HTMLModule
{

    /**
     * @type string
     */
    public $name = 'Presentation';

    /**
     * @param HTMLPurifier_Config $config
     */
    public function setup($config)
    {
        $this->addElement('hr', 'Block', 'Empty', 'Common');
        $this->addElement('sub', 'Inline', 'Inline', 'Common');
        $this->addElement('sup', 'Inline', 'Inline', 'Common');
        $b = $this->addElement('b', 'Inline', 'Inline', 'Common');
        $b->formatting = true;
        $big = $this->addElement('big', 'Inline', 'Inline', 'Common');
        $big->formatting = true;
        $i = $this->addElement('i', 'Inline', 'Inline', 'Common');
        $i->formatting = true;
        $small = $this->addElement('small', 'Inline', 'Inline', 'Common');
        $small->formatting = true;
        $tt = $this->addElement('tt', 'Inline', 'Inline', 'Common');
        $tt->formatting = true;
    }
}

// vim: et sw=4 sts=4
<?php

class HTMLPurifier_HTMLModule_NonXMLCommonAttributes extends HTMLPurifier_HTMLModule
{
    /**
     * @type string
     */
    public $name = 'NonXMLCommonAttributes';

    /**
     * @type array
     */
    public $attr_collections = array(
        'Lang' => array(
            'lang' => 'LanguageCode',
        )
    );
}

// vim: et sw=4 sts=4
<?php

/**
 * XHTML 1.1 Hypertext Module, defines hypertext links. Core Module.
 */
class HTMLPurifier_HTMLModule_Hypertext extends HTMLPurifier_HTMLModule
{

    /**
     * @type string
     */
    public $name = 'Hypertext';

    /**
     * @param HTMLPurifier_Config $config
     */
    public function setup($config)
    {
        $a = $this->addElement(
            'a',
            'Inline',
            'Inline',
            'Common',
            array(
                // 'accesskey' => 'Character',
                // 'charset' => 'Charset',
                'href' => 'URI',
                // 'hreflang' => 'LanguageCode',
                'rel' => new HTMLPurifier_AttrDef_HTML_LinkTypes('rel'),
                'rev' => new HTMLPurifier_AttrDef_HTML_LinkTypes('rev'),
                // 'tabindex' => 'Number',
                // 'type' => 'ContentType',
            )
        );
        $a->formatting = true;
        $a->excludes = array('a' => true);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * XHTML 1.1 Target Module, defines target attribute in link elements.
 */
class HTMLPurifier_HTMLModule_Target extends HTMLPurifier_HTMLModule
{
    /**
     * @type string
     */
    public $name = 'Target';

    /**
     * @param HTMLPurifier_Config $config
     */
    public function setup($config)
    {
        $elements = array('a');
        foreach ($elements as $name) {
            $e = $this->addBlankElement($name);
            $e->attr = array(
                'target' => new HTMLPurifier_AttrDef_HTML_FrameTarget()
            );
        }
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * XHTML 1.1 List Module, defines list-oriented elements. Core Module.
 */
class HTMLPurifier_HTMLModule_List extends HTMLPurifier_HTMLModule
{
    /**
     * @type string
     */
    public $name = 'List';

    // According to the abstract schema, the List content set is a fully formed
    // one or more expr, but it invariably occurs in an optional declaration
    // so we're not going to do that subtlety. It might cause trouble
    // if a user defines "List" and expects that multiple lists are
    // allowed to be specified, but then again, that's not very intuitive.
    // Furthermore, the actual XML Schema may disagree. Regardless,
    // we don't have support for such nested expressions without using
    // the incredibly inefficient and draconic Custom ChildDef.

    /**
     * @type array
     */
    public $content_sets = array('Flow' => 'List');

    /**
     * @param HTMLPurifier_Config $config
     */
    public function setup($config)
    {
        $ol = $this->addElement('ol', 'List', new HTMLPurifier_ChildDef_List(), 'Common');
        $ul = $this->addElement('ul', 'List', new HTMLPurifier_ChildDef_List(), 'Common');
        // XXX The wrap attribute is handled by MakeWellFormed.  This is all
        // quite unsatisfactory, because we generated this
        // *specifically* for lists, and now a big chunk of the handling
        // is done properly by the List ChildDef.  So actually, we just
        // want enough information to make autoclosing work properly,
        // and then hand off the tricky stuff to the ChildDef.
        $ol->wrap = 'li';
        $ul->wrap = 'li';
        $this->addElement('dl', 'List', 'Required: dt | dd', 'Common');

        $this->addElement('li', false, 'Flow', 'Common');

        $this->addElement('dd', false, 'Flow', 'Common');
        $this->addElement('dt', false, 'Inline', 'Common');
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * XHTML 1.1 Object Module, defines elements for generic object inclusion
 * @warning Users will commonly use <embed> to cater to legacy browsers: this
 *      module does not allow this sort of behavior
 */
class HTMLPurifier_HTMLModule_Object extends HTMLPurifier_HTMLModule
{
    /**
     * @type string
     */
    public $name = 'Object';

    /**
     * @type bool
     */
    public $safe = false;

    /**
     * @param HTMLPurifier_Config $config
     */
    public function setup($config)
    {
        $this->addElement(
            'object',
            'Inline',
            'Optional: #PCDATA | Flow | param',
            'Common',
            array(
                'archive' => 'URI',
                'classid' => 'URI',
                'codebase' => 'URI',
                'codetype' => 'Text',
                'data' => 'URI',
                'declare' => 'Bool#declare',
                'height' => 'Length',
                'name' => 'CDATA',
                'standby' => 'Text',
                'tabindex' => 'Number',
                'type' => 'ContentType',
                'width' => 'Length'
            )
        );

        $this->addElement(
            'param',
            false,
            'Empty',
            null,
            array(
                'id' => 'ID',
                'name*' => 'Text',
                'type' => 'Text',
                'value' => 'Text',
                'valuetype' => 'Enum#data,ref,object'
            )
        );
    }
}

// vim: et sw=4 sts=4
<?php

class HTMLPurifier_HTMLModule_CommonAttributes extends HTMLPurifier_HTMLModule
{
    /**
     * @type string
     */
    public $name = 'CommonAttributes';

    /**
     * @type array
     */
    public $attr_collections = array(
        'Core' => array(
            0 => array('Style'),
            // 'xml:space' => false,
            'class' => 'Class',
            'id' => 'ID',
            'title' => 'CDATA',
        ),
        'Lang' => array(),
        'I18N' => array(
            0 => array('Lang'), // proprietary, for xml:lang/lang
        ),
        'Common' => array(
            0 => array('Core', 'I18N')
        )
    );
}

// vim: et sw=4 sts=4
<?php

/**
 * XHTML 1.1 Bi-directional Text Module, defines elements that
 * declare directionality of content. Text Extension Module.
 */
class HTMLPurifier_HTMLModule_Bdo extends HTMLPurifier_HTMLModule
{

    /**
     * @type string
     */
    public $name = 'Bdo';

    /**
     * @type array
     */
    public $attr_collections = array(
        'I18N' => array('dir' => false)
    );

    /**
     * @param HTMLPurifier_Config $config
     */
    public function setup($config)
    {
        $bdo = $this->addElement(
            'bdo',
            'Inline',
            'Inline',
            array('Core', 'Lang'),
            array(
                'dir' => 'Enum#ltr,rtl', // required
                // The Abstract Module specification has the attribute
                // inclusions wrong for bdo: bdo allows Lang
            )
        );
        $bdo->attr_transform_post[] = new HTMLPurifier_AttrTransform_BdoDir();

        $this->attr_collections['I18N']['dir'] = 'Enum#ltr,rtl';
    }
}

// vim: et sw=4 sts=4
<?php

class HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4 extends HTMLPurifier_HTMLModule_Tidy
{

    /**
     * @return array
     */
    public function makeFixes()
    {
        $r = array();

        // == deprecated tag transforms ===================================

        $r['font'] = new HTMLPurifier_TagTransform_Font();
        $r['menu'] = new HTMLPurifier_TagTransform_Simple('ul');
        $r['dir'] = new HTMLPurifier_TagTransform_Simple('ul');
        $r['center'] = new HTMLPurifier_TagTransform_Simple('div', 'text-align:center;');
        $r['u'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:underline;');
        $r['s'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:line-through;');
        $r['strike'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:line-through;');

        // == deprecated attribute transforms =============================

        $r['caption@align'] =
            new HTMLPurifier_AttrTransform_EnumToCSS(
                'align',
                array(
                    // we're following IE's behavior, not Firefox's, due
                    // to the fact that no one supports caption-side:right,
                    // W3C included (with CSS 2.1). This is a slightly
                    // unreasonable attribute!
                    'left' => 'text-align:left;',
                    'right' => 'text-align:right;',
                    'top' => 'caption-side:top;',
                    'bottom' => 'caption-side:bottom;' // not supported by IE
                )
            );

        // @align for img -------------------------------------------------
        $r['img@align'] =
            new HTMLPurifier_AttrTransform_EnumToCSS(
                'align',
                array(
                    'left' => 'float:left;',
                    'right' => 'float:right;',
                    'top' => 'vertical-align:top;',
                    'middle' => 'vertical-align:middle;',
                    'bottom' => 'vertical-align:baseline;',
                )
            );

        // @align for table -----------------------------------------------
        $r['table@align'] =
            new HTMLPurifier_AttrTransform_EnumToCSS(
                'align',
                array(
                    'left' => 'float:left;',
                    'center' => 'margin-left:auto;margin-right:auto;',
                    'right' => 'float:right;'
                )
            );

        // @align for hr -----------------------------------------------
        $r['hr@align'] =
            new HTMLPurifier_AttrTransform_EnumToCSS(
                'align',
                array(
                    // we use both text-align and margin because these work
                    // for different browsers (IE and Firefox, respectively)
                    // and the melange makes for a pretty cross-compatible
                    // solution
                    'left' => 'margin-left:0;margin-right:auto;text-align:left;',
                    'center' => 'margin-left:auto;margin-right:auto;text-align:center;',
                    'right' => 'margin-left:auto;margin-right:0;text-align:right;'
                )
            );

        // @align for h1, h2, h3, h4, h5, h6, p, div ----------------------
        // {{{
        $align_lookup = array();
        $align_values = array('left', 'right', 'center', 'justify');
        foreach ($align_values as $v) {
            $align_lookup[$v] = "text-align:$v;";
        }
        // }}}
        $r['h1@align'] =
        $r['h2@align'] =
        $r['h3@align'] =
        $r['h4@align'] =
        $r['h5@align'] =
        $r['h6@align'] =
        $r['p@align'] =
        $r['div@align'] =
            new HTMLPurifier_AttrTransform_EnumToCSS('align', $align_lookup);

        // @bgcolor for table, tr, td, th ---------------------------------
        $r['table@bgcolor'] =
        $r['td@bgcolor'] =
        $r['th@bgcolor'] =
            new HTMLPurifier_AttrTransform_BgColor();

        // @border for img ------------------------------------------------
        $r['img@border'] = new HTMLPurifier_AttrTransform_Border();

        // @clear for br --------------------------------------------------
        $r['br@clear'] =
            new HTMLPurifier_AttrTransform_EnumToCSS(
                'clear',
                array(
                    'left' => 'clear:left;',
                    'right' => 'clear:right;',
                    'all' => 'clear:both;',
                    'none' => 'clear:none;',
                )
            );

        // @height for td, th ---------------------------------------------
        $r['td@height'] =
        $r['th@height'] =
            new HTMLPurifier_AttrTransform_Length('height');

        // @hspace for img ------------------------------------------------
        $r['img@hspace'] = new HTMLPurifier_AttrTransform_ImgSpace('hspace');

        // @noshade for hr ------------------------------------------------
        // this transformation is not precise but often good enough.
        // different browsers use different styles to designate noshade
        $r['hr@noshade'] =
            new HTMLPurifier_AttrTransform_BoolToCSS(
                'noshade',
                'color:#808080;background-color:#808080;border:0;'
            );

        // @nowrap for td, th ---------------------------------------------
        $r['td@nowrap'] =
        $r['th@nowrap'] =
            new HTMLPurifier_AttrTransform_BoolToCSS(
                'nowrap',
                'white-space:nowrap;'
            );

        // @size for hr  --------------------------------------------------
        $r['hr@size'] = new HTMLPurifier_AttrTransform_Length('size', 'height');

        // @type for li, ol, ul -------------------------------------------
        // {{{
        $ul_types = array(
            'disc' => 'list-style-type:disc;',
            'square' => 'list-style-type:square;',
            'circle' => 'list-style-type:circle;'
        );
        $ol_types = array(
            '1' => 'list-style-type:decimal;',
            'i' => 'list-style-type:lower-roman;',
            'I' => 'list-style-type:upper-roman;',
            'a' => 'list-style-type:lower-alpha;',
            'A' => 'list-style-type:upper-alpha;'
        );
        $li_types = $ul_types + $ol_types;
        // }}}

        $r['ul@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $ul_types);
        $r['ol@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $ol_types, true);
        $r['li@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $li_types, true);

        // @vspace for img ------------------------------------------------
        $r['img@vspace'] = new HTMLPurifier_AttrTransform_ImgSpace('vspace');

        // @width for hr, td, th ------------------------------------------
        $r['td@width'] =
        $r['th@width'] =
        $r['hr@width'] = new HTMLPurifier_AttrTransform_Length('width');

        return $r;
    }
}

// vim: et sw=4 sts=4
<?php

class HTMLPurifier_HTMLModule_Tidy_Transitional extends HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4
{
    /**
     * @type string
     */
    public $name = 'Tidy_Transitional';

    /**
     * @type string
     */
    public $defaultLevel = 'heavy';
}

// vim: et sw=4 sts=4
<?php

class HTMLPurifier_HTMLModule_Tidy_Strict extends HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4
{
    /**
     * @type string
     */
    public $name = 'Tidy_Strict';

    /**
     * @type string
     */
    public $defaultLevel = 'light';

    /**
     * @return array
     */
    public function makeFixes()
    {
        $r = parent::makeFixes();
        $r['blockquote#content_model_type'] = 'strictblockquote';
        return $r;
    }

    /**
     * @type bool
     */
    public $defines_child_def = true;

    /**
     * @param HTMLPurifier_ElementDef $def
     * @return HTMLPurifier_ChildDef_StrictBlockquote
     */
    public function getChildDef($def)
    {
        if ($def->content_model_type != 'strictblockquote') {
            return parent::getChildDef($def);
        }
        return new HTMLPurifier_ChildDef_StrictBlockquote($def->content_model);
    }
}

// vim: et sw=4 sts=4
<?php

class HTMLPurifier_HTMLModule_Tidy_XHTML extends HTMLPurifier_HTMLModule_Tidy
{
    /**
     * @type string
     */
    public $name = 'Tidy_XHTML';

    /**
     * @type string
     */
    public $defaultLevel = 'medium';

    /**
     * @return array
     */
    public function makeFixes()
    {
        $r = array();
        $r['@lang'] = new HTMLPurifier_AttrTransform_Lang();
        return $r;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Name is deprecated, but allowed in strict doctypes, so onl
 */
class HTMLPurifier_HTMLModule_Tidy_Name extends HTMLPurifier_HTMLModule_Tidy
{
    /**
     * @type string
     */
    public $name = 'Tidy_Name';

    /**
     * @type string
     */
    public $defaultLevel = 'heavy';

    /**
     * @return array
     */
    public function makeFixes()
    {
        $r = array();
        // @name for img, a -----------------------------------------------
        // Technically, it's allowed even on strict, so we allow authors to use
        // it. However, it's deprecated in future versions of XHTML.
        $r['img@name'] =
        $r['a@name'] = new HTMLPurifier_AttrTransform_Name();
        return $r;
    }
}

// vim: et sw=4 sts=4
<?php

class HTMLPurifier_HTMLModule_Tidy_Proprietary extends HTMLPurifier_HTMLModule_Tidy
{

    /**
     * @type string
     */
    public $name = 'Tidy_Proprietary';

    /**
     * @type string
     */
    public $defaultLevel = 'light';

    /**
     * @return array
     */
    public function makeFixes()
    {
        $r = array();
        $r['table@background'] = new HTMLPurifier_AttrTransform_Background();
        $r['td@background']    = new HTMLPurifier_AttrTransform_Background();
        $r['th@background']    = new HTMLPurifier_AttrTransform_Background();
        $r['tr@background']    = new HTMLPurifier_AttrTransform_Background();
        $r['thead@background'] = new HTMLPurifier_AttrTransform_Background();
        $r['tfoot@background'] = new HTMLPurifier_AttrTransform_Background();
        $r['tbody@background'] = new HTMLPurifier_AttrTransform_Background();
        $r['table@height']     = new HTMLPurifier_AttrTransform_Length('height');
        return $r;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Module adds the target-based noopener attribute transformation to a tags.  It
 * is enabled by HTML.TargetNoopener
 */
class HTMLPurifier_HTMLModule_TargetNoopener extends HTMLPurifier_HTMLModule
{
    /**
     * @type string
     */
    public $name = 'TargetNoopener';

    /**
     * @param HTMLPurifier_Config $config
     */
    public function setup($config) {
        $a = $this->addBlankElement('a');
        $a->attr_transform_post[] = new HTMLPurifier_AttrTransform_TargetNoopener();
    }
}
<?php

/**
 * XHTML 1.1 Tables Module, fully defines accessible table elements.
 */
class HTMLPurifier_HTMLModule_Tables extends HTMLPurifier_HTMLModule
{
    /**
     * @type string
     */
    public $name = 'Tables';

    /**
     * @param HTMLPurifier_Config $config
     */
    public function setup($config)
    {
        $this->addElement('caption', false, 'Inline', 'Common');

        $this->addElement(
            'table',
            'Block',
            new HTMLPurifier_ChildDef_Table(),
            'Common',
            array(
                'border' => 'Pixels',
                'cellpadding' => 'Length',
                'cellspacing' => 'Length',
                'frame' => 'Enum#void,above,below,hsides,lhs,rhs,vsides,box,border',
                'rules' => 'Enum#none,groups,rows,cols,all',
                'summary' => 'Text',
                'width' => 'Length'
            )
        );

        // common attributes
        $cell_align = array(
            'align' => 'Enum#left,center,right,justify,char',
            'charoff' => 'Length',
            'valign' => 'Enum#top,middle,bottom,baseline',
        );

        $cell_t = array_merge(
            array(
                'abbr' => 'Text',
                'colspan' => 'Number',
                'rowspan' => 'Number',
                // Apparently, as of HTML5 this attribute only applies
                // to 'th' elements.
                'scope' => 'Enum#row,col,rowgroup,colgroup',
            ),
            $cell_align
        );
        $this->addElement('td', false, 'Flow', 'Common', $cell_t);
        $this->addElement('th', false, 'Flow', 'Common', $cell_t);

        $this->addElement('tr', false, 'Required: td | th', 'Common', $cell_align);

        $cell_col = array_merge(
            array(
                'span' => 'Number',
                'width' => 'MultiLength',
            ),
            $cell_align
        );
        $this->addElement('col', false, 'Empty', 'Common', $cell_col);
        $this->addElement('colgroup', false, 'Optional: col', 'Common', $cell_col);

        $this->addElement('tbody', false, 'Required: tr', 'Common', $cell_align);
        $this->addElement('thead', false, 'Required: tr', 'Common', $cell_align);
        $this->addElement('tfoot', false, 'Required: tr', 'Common', $cell_align);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * XHTML 1.1 Edit Module, defines editing-related elements. Text Extension
 * Module.
 */
class HTMLPurifier_HTMLModule_StyleAttribute extends HTMLPurifier_HTMLModule
{
    /**
     * @type string
     */
    public $name = 'StyleAttribute';

    /**
     * @type array
     */
    public $attr_collections = array(
        // The inclusion routine differs from the Abstract Modules but
        // is in line with the DTD and XML Schemas.
        'Style' => array('style' => false), // see constructor
        'Core' => array(0 => array('Style'))
    );

    /**
     * @param HTMLPurifier_Config $config
     */
    public function setup($config)
    {
        $this->attr_collections['Style']['style'] = new HTMLPurifier_AttrDef_CSS();
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * XHTML 1.1 Edit Module, defines editing-related elements. Text Extension
 * Module.
 */
class HTMLPurifier_HTMLModule_Edit extends HTMLPurifier_HTMLModule
{

    /**
     * @type string
     */
    public $name = 'Edit';

    /**
     * @param HTMLPurifier_Config $config
     */
    public function setup($config)
    {
        $contents = 'Chameleon: #PCDATA | Inline ! #PCDATA | Flow';
        $attr = array(
            'cite' => 'URI',
            // 'datetime' => 'Datetime', // not implemented
        );
        $this->addElement('del', 'Inline', $contents, 'Common', $attr);
        $this->addElement('ins', 'Inline', $contents, 'Common', $attr);
    }

    // HTML 4.01 specifies that ins/del must not contain block
    // elements when used in an inline context, chameleon is
    // a complicated workaround to acheive this effect

    // Inline context ! Block context (exclamation mark is
    // separator, see getChildDef for parsing)

    /**
     * @type bool
     */
    public $defines_child_def = true;

    /**
     * @param HTMLPurifier_ElementDef $def
     * @return HTMLPurifier_ChildDef_Chameleon
     */
    public function getChildDef($def)
    {
        if ($def->content_model_type != 'chameleon') {
            return false;
        }
        $value = explode('!', $def->content_model);
        return new HTMLPurifier_ChildDef_Chameleon($value[0], $value[1]);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Module adds the target-based noreferrer attribute transformation to a tags.  It
 * is enabled by HTML.TargetNoreferrer
 */
class HTMLPurifier_HTMLModule_TargetNoreferrer extends HTMLPurifier_HTMLModule
{
    /**
     * @type string
     */
    public $name = 'TargetNoreferrer';

    /**
     * @param HTMLPurifier_Config $config
     */
    public function setup($config) {
        $a = $this->addBlankElement('a');
        $a->attr_transform_post[] = new HTMLPurifier_AttrTransform_TargetNoreferrer();
    }
}
<?php

class HTMLPurifier_HTMLModule_Name extends HTMLPurifier_HTMLModule
{
    /**
     * @type string
     */
    public $name = 'Name';

    /**
     * @param HTMLPurifier_Config $config
     */
    public function setup($config)
    {
        $elements = array('a', 'applet', 'form', 'frame', 'iframe', 'img', 'map');
        foreach ($elements as $name) {
            $element = $this->addBlankElement($name);
            $element->attr['name'] = 'CDATA';
            if (!$config->get('HTML.Attr.Name.UseCDATA')) {
                $element->attr_transform_post[] = new HTMLPurifier_AttrTransform_NameSync();
            }
        }
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * A "safe" script module. No inline JS is allowed, and pointed to JS
 * files must match whitelist.
 */
class HTMLPurifier_HTMLModule_SafeScripting extends HTMLPurifier_HTMLModule
{
    /**
     * @type string
     */
    public $name = 'SafeScripting';

    /**
     * @param HTMLPurifier_Config $config
     */
    public function setup($config)
    {
        // These definitions are not intrinsically safe: the attribute transforms
        // are a vital part of ensuring safety.

        $allowed = $config->get('HTML.SafeScripting');
        $script = $this->addElement(
            'script',
            'Inline',
            'Empty',
            null,
            array(
                // While technically not required by the spec, we're forcing
                // it to this value.
                'type' => 'Enum#text/javascript',
                'src*' => new HTMLPurifier_AttrDef_Enum(array_keys($allowed))
            )
        );
        $script->attr_transform_pre[] =
        $script->attr_transform_post[] = new HTMLPurifier_AttrTransform_ScriptRequired();
    }
}

// vim: et sw=4 sts=4
<?php

/*

WARNING: THIS MODULE IS EXTREMELY DANGEROUS AS IT ENABLES INLINE SCRIPTING
INSIDE HTML PURIFIER DOCUMENTS. USE ONLY WITH TRUSTED USER INPUT!!!

*/

/**
 * XHTML 1.1 Scripting module, defines elements that are used to contain
 * information pertaining to executable scripts or the lack of support
 * for executable scripts.
 * @note This module does not contain inline scripting elements
 */
class HTMLPurifier_HTMLModule_Scripting extends HTMLPurifier_HTMLModule
{
    /**
     * @type string
     */
    public $name = 'Scripting';

    /**
     * @type array
     */
    public $elements = array('script', 'noscript');

    /**
     * @type array
     */
    public $content_sets = array('Block' => 'script | noscript', 'Inline' => 'script | noscript');

    /**
     * @type bool
     */
    public $safe = false;

    /**
     * @param HTMLPurifier_Config $config
     */
    public function setup($config)
    {
        // TODO: create custom child-definition for noscript that
        // auto-wraps stray #PCDATA in a similar manner to
        // blockquote's custom definition (we would use it but
        // blockquote's contents are optional while noscript's contents
        // are required)

        // TODO: convert this to new syntax, main problem is getting
        // both content sets working

        // In theory, this could be safe, but I don't see any reason to
        // allow it.
        $this->info['noscript'] = new HTMLPurifier_ElementDef();
        $this->info['noscript']->attr = array(0 => array('Common'));
        $this->info['noscript']->content_model = 'Heading | List | Block';
        $this->info['noscript']->content_model_type = 'required';

        $this->info['script'] = new HTMLPurifier_ElementDef();
        $this->info['script']->attr = array(
            'defer' => new HTMLPurifier_AttrDef_Enum(array('defer')),
            'src' => new HTMLPurifier_AttrDef_URI(true),
            'type' => new HTMLPurifier_AttrDef_Enum(array('text/javascript'))
        );
        $this->info['script']->content_model = '#PCDATA';
        $this->info['script']->content_model_type = 'optional';
        $this->info['script']->attr_transform_pre[] =
        $this->info['script']->attr_transform_post[] =
            new HTMLPurifier_AttrTransform_ScriptRequired();
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Module adds the target=blank attribute transformation to a tags.  It
 * is enabled by HTML.TargetBlank
 */
class HTMLPurifier_HTMLModule_TargetBlank extends HTMLPurifier_HTMLModule
{
    /**
     * @type string
     */
    public $name = 'TargetBlank';

    /**
     * @param HTMLPurifier_Config $config
     */
    public function setup($config)
    {
        $a = $this->addBlankElement('a');
        $a->attr_transform_post[] = new HTMLPurifier_AttrTransform_TargetBlank();
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * A "safe" embed module. See SafeObject. This is a proprietary element.
 */
class HTMLPurifier_HTMLModule_SafeEmbed extends HTMLPurifier_HTMLModule
{
    /**
     * @type string
     */
    public $name = 'SafeEmbed';

    /**
     * @param HTMLPurifier_Config $config
     */
    public function setup($config)
    {
        $max = $config->get('HTML.MaxImgLength');
        $embed = $this->addElement(
            'embed',
            'Inline',
            'Empty',
            'Common',
            array(
                'src*' => 'URI#embedded',
                'type' => 'Enum#application/x-shockwave-flash',
                'width' => 'Pixels#' . $max,
                'height' => 'Pixels#' . $max,
                'allowscriptaccess' => 'Enum#never',
                'allownetworking' => 'Enum#internal',
                'flashvars' => 'Text',
                'wmode' => 'Enum#window,transparent,opaque',
                'name' => 'ID',
            )
        );
        $embed->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeEmbed();
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * XHTML 1.1 Iframe Module provides inline frames.
 *
 * @note This module is not considered safe unless an Iframe
 * whitelisting mechanism is specified.  Currently, the only
 * such mechanism is %URL.SafeIframeRegexp
 */
class HTMLPurifier_HTMLModule_Iframe extends HTMLPurifier_HTMLModule
{

    /**
     * @type string
     */
    public $name = 'Iframe';

    /**
     * @type bool
     */
    public $safe = false;

    /**
     * @param HTMLPurifier_Config $config
     */
    public function setup($config)
    {
        if ($config->get('HTML.SafeIframe')) {
            $this->safe = true;
        }
        $this->addElement(
            'iframe',
            'Inline',
            'Flow',
            'Common',
            array(
                'src' => 'URI#embedded',
                'width' => 'Length',
                'height' => 'Length',
                'name' => 'ID',
                'scrolling' => 'Enum#yes,no,auto',
                'frameborder' => 'Enum#0,1',
                'longdesc' => 'URI',
                'marginheight' => 'Pixels',
                'marginwidth' => 'Pixels',
            )
        );
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * XHTML 1.1 Text Module, defines basic text containers. Core Module.
 * @note In the normative XML Schema specification, this module
 *       is further abstracted into the following modules:
 *          - Block Phrasal (address, blockquote, pre, h1, h2, h3, h4, h5, h6)
 *          - Block Structural (div, p)
 *          - Inline Phrasal (abbr, acronym, cite, code, dfn, em, kbd, q, samp, strong, var)
 *          - Inline Structural (br, span)
 *       This module, functionally, does not distinguish between these
 *       sub-modules, but the code is internally structured to reflect
 *       these distinctions.
 */
class HTMLPurifier_HTMLModule_Text extends HTMLPurifier_HTMLModule
{
    /**
     * @type string
     */
    public $name = 'Text';

    /**
     * @type array
     */
    public $content_sets = array(
        'Flow' => 'Heading | Block | Inline'
    );

    /**
     * @param HTMLPurifier_Config $config
     */
    public function setup($config)
    {
        // Inline Phrasal -------------------------------------------------
        $this->addElement('abbr', 'Inline', 'Inline', 'Common');
        $this->addElement('acronym', 'Inline', 'Inline', 'Common');
        $this->addElement('cite', 'Inline', 'Inline', 'Common');
        $this->addElement('dfn', 'Inline', 'Inline', 'Common');
        $this->addElement('kbd', 'Inline', 'Inline', 'Common');
        $this->addElement('q', 'Inline', 'Inline', 'Common', array('cite' => 'URI'));
        $this->addElement('samp', 'Inline', 'Inline', 'Common');
        $this->addElement('var', 'Inline', 'Inline', 'Common');

        $em = $this->addElement('em', 'Inline', 'Inline', 'Common');
        $em->formatting = true;

        $strong = $this->addElement('strong', 'Inline', 'Inline', 'Common');
        $strong->formatting = true;

        $code = $this->addElement('code', 'Inline', 'Inline', 'Common');
        $code->formatting = true;

        // Inline Structural ----------------------------------------------
        $this->addElement('span', 'Inline', 'Inline', 'Common');
        $this->addElement('br', 'Inline', 'Empty', 'Core');

        // Block Phrasal --------------------------------------------------
        $this->addElement('address', 'Block', 'Inline', 'Common');
        $this->addElement('blockquote', 'Block', 'Optional: Heading | Block | List', 'Common', array('cite' => 'URI'));
        $pre = $this->addElement('pre', 'Block', 'Inline', 'Common');
        $pre->excludes = $this->makeLookup(
            'img',
            'big',
            'small',
            'object',
            'applet',
            'font',
            'basefont'
        );
        $this->addElement('h1', 'Heading', 'Inline', 'Common');
        $this->addElement('h2', 'Heading', 'Inline', 'Common');
        $this->addElement('h3', 'Heading', 'Inline', 'Common');
        $this->addElement('h4', 'Heading', 'Inline', 'Common');
        $this->addElement('h5', 'Heading', 'Inline', 'Common');
        $this->addElement('h6', 'Heading', 'Inline', 'Common');

        // Block Structural -----------------------------------------------
        $p = $this->addElement('p', 'Block', 'Inline', 'Common');
        $p->autoclose = array_flip(
            array("address", "blockquote", "center", "dir", "div", "dl", "fieldset", "ol", "p", "ul")
        );

        $this->addElement('div', 'Block', 'Flow', 'Common');
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * XHTML 1.1 Image Module provides basic image embedding.
 * @note There is specialized code for removing empty images in
 *       HTMLPurifier_Strategy_RemoveForeignElements
 */
class HTMLPurifier_HTMLModule_Image extends HTMLPurifier_HTMLModule
{

    /**
     * @type string
     */
    public $name = 'Image';

    /**
     * @param HTMLPurifier_Config $config
     */
    public function setup($config)
    {
        $max = $config->get('HTML.MaxImgLength');
        $img = $this->addElement(
            'img',
            'Inline',
            'Empty',
            'Common',
            array(
                'alt*' => 'Text',
                // According to the spec, it's Length, but percents can
                // be abused, so we allow only Pixels.
                'height' => 'Pixels#' . $max,
                'width' => 'Pixels#' . $max,
                'longdesc' => 'URI',
                'src*' => new HTMLPurifier_AttrDef_URI(true), // embedded
            )
        );
        if ($max === null || $config->get('HTML.Trusted')) {
            $img->attr['height'] =
            $img->attr['width'] = 'Length';
        }

        // kind of strange, but splitting things up would be inefficient
        $img->attr_transform_pre[] =
        $img->attr_transform_post[] =
            new HTMLPurifier_AttrTransform_ImgRequired();
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * XHTML 1.1 Forms module, defines all form-related elements found in HTML 4.
 */
class HTMLPurifier_HTMLModule_Forms extends HTMLPurifier_HTMLModule
{
    /**
     * @type string
     */
    public $name = 'Forms';

    /**
     * @type bool
     */
    public $safe = false;

    /**
     * @type array
     */
    public $content_sets = array(
        'Block' => 'Form',
        'Inline' => 'Formctrl',
    );

    /**
     * @param HTMLPurifier_Config $config
     */
    public function setup($config)
    {
        $form = $this->addElement(
            'form',
            'Form',
            'Required: Heading | List | Block | fieldset',
            'Common',
            array(
                'accept' => 'ContentTypes',
                'accept-charset' => 'Charsets',
                'action*' => 'URI',
                'method' => 'Enum#get,post',
                // really ContentType, but these two are the only ones used today
                'enctype' => 'Enum#application/x-www-form-urlencoded,multipart/form-data',
            )
        );
        $form->excludes = array('form' => true);

        $input = $this->addElement(
            'input',
            'Formctrl',
            'Empty',
            'Common',
            array(
                'accept' => 'ContentTypes',
                'accesskey' => 'Character',
                'alt' => 'Text',
                'checked' => 'Bool#checked',
                'disabled' => 'Bool#disabled',
                'maxlength' => 'Number',
                'name' => 'CDATA',
                'readonly' => 'Bool#readonly',
                'size' => 'Number',
                'src' => 'URI#embedded',
                'tabindex' => 'Number',
                'type' => 'Enum#text,password,checkbox,button,radio,submit,reset,file,hidden,image',
                'value' => 'CDATA',
            )
        );
        $input->attr_transform_post[] = new HTMLPurifier_AttrTransform_Input();

        $this->addElement(
            'select',
            'Formctrl',
            'Required: optgroup | option',
            'Common',
            array(
                'disabled' => 'Bool#disabled',
                'multiple' => 'Bool#multiple',
                'name' => 'CDATA',
                'size' => 'Number',
                'tabindex' => 'Number',
            )
        );

        $this->addElement(
            'option',
            false,
            'Optional: #PCDATA',
            'Common',
            array(
                'disabled' => 'Bool#disabled',
                'label' => 'Text',
                'selected' => 'Bool#selected',
                'value' => 'CDATA',
            )
        );
        // It's illegal for there to be more than one selected, but not
        // be multiple. Also, no selected means undefined behavior. This might
        // be difficult to implement; perhaps an injector, or a context variable.

        $textarea = $this->addElement(
            'textarea',
            'Formctrl',
            'Optional: #PCDATA',
            'Common',
            array(
                'accesskey' => 'Character',
                'cols*' => 'Number',
                'disabled' => 'Bool#disabled',
                'name' => 'CDATA',
                'readonly' => 'Bool#readonly',
                'rows*' => 'Number',
                'tabindex' => 'Number',
            )
        );
        $textarea->attr_transform_pre[] = new HTMLPurifier_AttrTransform_Textarea();

        $button = $this->addElement(
            'button',
            'Formctrl',
            'Optional: #PCDATA | Heading | List | Block | Inline',
            'Common',
            array(
                'accesskey' => 'Character',
                'disabled' => 'Bool#disabled',
                'name' => 'CDATA',
                'tabindex' => 'Number',
                'type' => 'Enum#button,submit,reset',
                'value' => 'CDATA',
            )
        );

        // For exclusions, ideally we'd specify content sets, not literal elements
        $button->excludes = $this->makeLookup(
            'form',
            'fieldset', // Form
            'input',
            'select',
            'textarea',
            'label',
            'button', // Formctrl
            'a', // as per HTML 4.01 spec, this is omitted by modularization
            'isindex',
            'iframe' // legacy items
        );

        // Extra exclusion: img usemap="" is not permitted within this element.
        // We'll omit this for now, since we don't have any good way of
        // indicating it yet.

        // This is HIGHLY user-unfriendly; we need a custom child-def for this
        $this->addElement('fieldset', 'Form', 'Custom: (#WS?,legend,(Flow|#PCDATA)*)', 'Common');

        $label = $this->addElement(
            'label',
            'Formctrl',
            'Optional: #PCDATA | Inline',
            'Common',
            array(
                'accesskey' => 'Character',
                // 'for' => 'IDREF', // IDREF not implemented, cannot allow
            )
        );
        $label->excludes = array('label' => true);

        $this->addElement(
            'legend',
            false,
            'Optional: #PCDATA | Inline',
            'Common',
            array(
                'accesskey' => 'Character',
            )
        );

        $this->addElement(
            'optgroup',
            false,
            'Required: option',
            'Common',
            array(
                'disabled' => 'Bool#disabled',
                'label*' => 'Text',
            )
        );
        // Don't forget an injector for <isindex>. This one's a little complex
        // because it maps to multiple elements.
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * XHTML 1.1 Legacy module defines elements that were previously
 * deprecated.
 *
 * @note Not all legacy elements have been implemented yet, which
 *       is a bit of a reverse problem as compared to browsers! In
 *       addition, this legacy module may implement a bit more than
 *       mandated by XHTML 1.1.
 *
 * This module can be used in combination with TransformToStrict in order
 * to transform as many deprecated elements as possible, but retain
 * questionably deprecated elements that do not have good alternatives
 * as well as transform elements that don't have an implementation.
 * See docs/ref-strictness.txt for more details.
 */

class HTMLPurifier_HTMLModule_Legacy extends HTMLPurifier_HTMLModule
{
    /**
     * @type string
     */
    public $name = 'Legacy';

    /**
     * @param HTMLPurifier_Config $config
     */
    public function setup($config)
    {
        $this->addElement(
            'basefont',
            'Inline',
            'Empty',
            null,
            array(
                'color' => 'Color',
                'face' => 'Text', // extremely broad, we should
                'size' => 'Text', // tighten it
                'id' => 'ID'
            )
        );
        $this->addElement('center', 'Block', 'Flow', 'Common');
        $this->addElement(
            'dir',
            'Block',
            'Required: li',
            'Common',
            array(
                'compact' => 'Bool#compact'
            )
        );
        $this->addElement(
            'font',
            'Inline',
            'Inline',
            array('Core', 'I18N'),
            array(
                'color' => 'Color',
                'face' => 'Text', // extremely broad, we should
                'size' => 'Text', // tighten it
            )
        );
        $this->addElement(
            'menu',
            'Block',
            'Required: li',
            'Common',
            array(
                'compact' => 'Bool#compact'
            )
        );

        $s = $this->addElement('s', 'Inline', 'Inline', 'Common');
        $s->formatting = true;

        $strike = $this->addElement('strike', 'Inline', 'Inline', 'Common');
        $strike->formatting = true;

        $u = $this->addElement('u', 'Inline', 'Inline', 'Common');
        $u->formatting = true;

        // setup modifications to old elements

        $align = 'Enum#left,right,center,justify';

        $address = $this->addBlankElement('address');
        $address->content_model = 'Inline | #PCDATA | p';
        $address->content_model_type = 'optional';
        $address->child = false;

        $blockquote = $this->addBlankElement('blockquote');
        $blockquote->content_model = 'Flow | #PCDATA';
        $blockquote->content_model_type = 'optional';
        $blockquote->child = false;

        $br = $this->addBlankElement('br');
        $br->attr['clear'] = 'Enum#left,all,right,none';

        $caption = $this->addBlankElement('caption');
        $caption->attr['align'] = 'Enum#top,bottom,left,right';

        $div = $this->addBlankElement('div');
        $div->attr['align'] = $align;

        $dl = $this->addBlankElement('dl');
        $dl->attr['compact'] = 'Bool#compact';

        for ($i = 1; $i <= 6; $i++) {
            $h = $this->addBlankElement("h$i");
            $h->attr['align'] = $align;
        }

        $hr = $this->addBlankElement('hr');
        $hr->attr['align'] = $align;
        $hr->attr['noshade'] = 'Bool#noshade';
        $hr->attr['size'] = 'Pixels';
        $hr->attr['width'] = 'Length';

        $img = $this->addBlankElement('img');
        $img->attr['align'] = 'IAlign';
        $img->attr['border'] = 'Pixels';
        $img->attr['hspace'] = 'Pixels';
        $img->attr['vspace'] = 'Pixels';

        // figure out this integer business

        $li = $this->addBlankElement('li');
        $li->attr['value'] = new HTMLPurifier_AttrDef_Integer();
        $li->attr['type'] = 'Enum#s:1,i,I,a,A,disc,square,circle';

        $ol = $this->addBlankElement('ol');
        $ol->attr['compact'] = 'Bool#compact';
        $ol->attr['start'] = new HTMLPurifier_AttrDef_Integer();
        $ol->attr['type'] = 'Enum#s:1,i,I,a,A';

        $p = $this->addBlankElement('p');
        $p->attr['align'] = $align;

        $pre = $this->addBlankElement('pre');
        $pre->attr['width'] = 'Number';

        // script omitted

        $table = $this->addBlankElement('table');
        $table->attr['align'] = 'Enum#left,center,right';
        $table->attr['bgcolor'] = 'Color';

        $tr = $this->addBlankElement('tr');
        $tr->attr['bgcolor'] = 'Color';

        $th = $this->addBlankElement('th');
        $th->attr['bgcolor'] = 'Color';
        $th->attr['height'] = 'Length';
        $th->attr['nowrap'] = 'Bool#nowrap';
        $th->attr['width'] = 'Length';

        $td = $this->addBlankElement('td');
        $td->attr['bgcolor'] = 'Color';
        $td->attr['height'] = 'Length';
        $td->attr['nowrap'] = 'Bool#nowrap';
        $td->attr['width'] = 'Length';

        $ul = $this->addBlankElement('ul');
        $ul->attr['compact'] = 'Bool#compact';
        $ul->attr['type'] = 'Enum#square,disc,circle';

        // "safe" modifications to "unsafe" elements
        // WARNING: If you want to add support for an unsafe, legacy
        // attribute, make a new TrustedLegacy module with the trusted
        // bit set appropriately

        $form = $this->addBlankElement('form');
        $form->content_model = 'Flow | #PCDATA';
        $form->content_model_type = 'optional';
        $form->attr['target'] = 'FrameTarget';

        $input = $this->addBlankElement('input');
        $input->attr['align'] = 'IAlign';

        $legend = $this->addBlankElement('legend');
        $legend->attr['align'] = 'LAlign';
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Module defines proprietary tags and attributes in HTML.
 * @warning If this module is enabled, standards-compliance is off!
 */
class HTMLPurifier_HTMLModule_Proprietary extends HTMLPurifier_HTMLModule
{
    /**
     * @type string
     */
    public $name = 'Proprietary';

    /**
     * @param HTMLPurifier_Config $config
     */
    public function setup($config)
    {
        $this->addElement(
            'marquee',
            'Inline',
            'Flow',
            'Common',
            array(
                'direction' => 'Enum#left,right,up,down',
                'behavior' => 'Enum#alternate',
                'width' => 'Length',
                'height' => 'Length',
                'scrolldelay' => 'Number',
                'scrollamount' => 'Number',
                'loop' => 'Number',
                'bgcolor' => 'Color',
                'hspace' => 'Pixels',
                'vspace' => 'Pixels',
            )
        );
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Defines allowed CSS attributes and what their values are.
 * @see HTMLPurifier_HTMLDefinition
 */
class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
{

    public $type = 'CSS';

    /**
     * Assoc array of attribute name to definition object.
     * @type HTMLPurifier_AttrDef[]
     */
    public $info = array();

    /**
     * Constructs the info array.  The meat of this class.
     * @param HTMLPurifier_Config $config
     */
    protected function doSetup($config)
    {
        $this->info['text-align'] = new HTMLPurifier_AttrDef_Enum(
            array('left', 'right', 'center', 'justify'),
            false
        );

        $border_style =
            $this->info['border-bottom-style'] =
            $this->info['border-right-style'] =
            $this->info['border-left-style'] =
            $this->info['border-top-style'] = new HTMLPurifier_AttrDef_Enum(
                array(
                    'none',
                    'hidden',
                    'dotted',
                    'dashed',
                    'solid',
                    'double',
                    'groove',
                    'ridge',
                    'inset',
                    'outset'
                ),
                false
            );

        $this->info['border-style'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_style);

        $this->info['clear'] = new HTMLPurifier_AttrDef_Enum(
            array('none', 'left', 'right', 'both'),
            false
        );
        $this->info['float'] = new HTMLPurifier_AttrDef_Enum(
            array('none', 'left', 'right'),
            false
        );
        $this->info['font-style'] = new HTMLPurifier_AttrDef_Enum(
            array('normal', 'italic', 'oblique'),
            false
        );
        $this->info['font-variant'] = new HTMLPurifier_AttrDef_Enum(
            array('normal', 'small-caps'),
            false
        );

        $uri_or_none = new HTMLPurifier_AttrDef_CSS_Composite(
            array(
                new HTMLPurifier_AttrDef_Enum(array('none')),
                new HTMLPurifier_AttrDef_CSS_URI()
            )
        );

        $this->info['list-style-position'] = new HTMLPurifier_AttrDef_Enum(
            array('inside', 'outside'),
            false
        );
        $this->info['list-style-type'] = new HTMLPurifier_AttrDef_Enum(
            array(
                'disc',
                'circle',
                'square',
                'decimal',
                'lower-roman',
                'upper-roman',
                'lower-alpha',
                'upper-alpha',
                'none'
            ),
            false
        );
        $this->info['list-style-image'] = $uri_or_none;

        $this->info['list-style'] = new HTMLPurifier_AttrDef_CSS_ListStyle($config);

        $this->info['text-transform'] = new HTMLPurifier_AttrDef_Enum(
            array('capitalize', 'uppercase', 'lowercase', 'none'),
            false
        );
        $this->info['color'] = new HTMLPurifier_AttrDef_CSS_Color();

        $this->info['background-image'] = $uri_or_none;
        $this->info['background-repeat'] = new HTMLPurifier_AttrDef_Enum(
            array('repeat', 'repeat-x', 'repeat-y', 'no-repeat')
        );
        $this->info['background-attachment'] = new HTMLPurifier_AttrDef_Enum(
            array('scroll', 'fixed')
        );
        $this->info['background-position'] = new HTMLPurifier_AttrDef_CSS_BackgroundPosition();

        $border_color =
            $this->info['border-top-color'] =
            $this->info['border-bottom-color'] =
            $this->info['border-left-color'] =
            $this->info['border-right-color'] =
            $this->info['background-color'] = new HTMLPurifier_AttrDef_CSS_Composite(
                array(
                    new HTMLPurifier_AttrDef_Enum(array('transparent')),
                    new HTMLPurifier_AttrDef_CSS_Color()
                )
            );

        $this->info['background'] = new HTMLPurifier_AttrDef_CSS_Background($config);

        $this->info['border-color'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_color);

        $border_width =
            $this->info['border-top-width'] =
            $this->info['border-bottom-width'] =
            $this->info['border-left-width'] =
            $this->info['border-right-width'] = new HTMLPurifier_AttrDef_CSS_Composite(
                array(
                    new HTMLPurifier_AttrDef_Enum(array('thin', 'medium', 'thick')),
                    new HTMLPurifier_AttrDef_CSS_Length('0') //disallow negative
                )
            );

        $this->info['border-width'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_width);

        $this->info['letter-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(
            array(
                new HTMLPurifier_AttrDef_Enum(array('normal')),
                new HTMLPurifier_AttrDef_CSS_Length()
            )
        );

        $this->info['word-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(
            array(
                new HTMLPurifier_AttrDef_Enum(array('normal')),
                new HTMLPurifier_AttrDef_CSS_Length()
            )
        );

        $this->info['font-size'] = new HTMLPurifier_AttrDef_CSS_Composite(
            array(
                new HTMLPurifier_AttrDef_Enum(
                    array(
                        'xx-small',
                        'x-small',
                        'small',
                        'medium',
                        'large',
                        'x-large',
                        'xx-large',
                        'larger',
                        'smaller'
                    )
                ),
                new HTMLPurifier_AttrDef_CSS_Percentage(),
                new HTMLPurifier_AttrDef_CSS_Length()
            )
        );

        $this->info['line-height'] = new HTMLPurifier_AttrDef_CSS_Composite(
            array(
                new HTMLPurifier_AttrDef_Enum(array('normal')),
                new HTMLPurifier_AttrDef_CSS_Number(true), // no negatives
                new HTMLPurifier_AttrDef_CSS_Length('0'),
                new HTMLPurifier_AttrDef_CSS_Percentage(true)
            )
        );

        $margin =
            $this->info['margin-top'] =
            $this->info['margin-bottom'] =
            $this->info['margin-left'] =
            $this->info['margin-right'] = new HTMLPurifier_AttrDef_CSS_Composite(
                array(
                    new HTMLPurifier_AttrDef_CSS_Length(),
                    new HTMLPurifier_AttrDef_CSS_Percentage(),
                    new HTMLPurifier_AttrDef_Enum(array('auto'))
                )
            );

        $this->info['margin'] = new HTMLPurifier_AttrDef_CSS_Multiple($margin);

        // non-negative
        $padding =
            $this->info['padding-top'] =
            $this->info['padding-bottom'] =
            $this->info['padding-left'] =
            $this->info['padding-right'] = new HTMLPurifier_AttrDef_CSS_Composite(
                array(
                    new HTMLPurifier_AttrDef_CSS_Length('0'),
                    new HTMLPurifier_AttrDef_CSS_Percentage(true)
                )
            );

        $this->info['padding'] = new HTMLPurifier_AttrDef_CSS_Multiple($padding);

        $this->info['text-indent'] = new HTMLPurifier_AttrDef_CSS_Composite(
            array(
                new HTMLPurifier_AttrDef_CSS_Length(),
                new HTMLPurifier_AttrDef_CSS_Percentage()
            )
        );

        $trusted_wh = new HTMLPurifier_AttrDef_CSS_Composite(
            array(
                new HTMLPurifier_AttrDef_CSS_Length('0'),
                new HTMLPurifier_AttrDef_CSS_Percentage(true),
                new HTMLPurifier_AttrDef_Enum(array('auto'))
            )
        );
        $max = $config->get('CSS.MaxImgLength');

        $this->info['min-width'] =
        $this->info['max-width'] =
        $this->info['min-height'] =
        $this->info['max-height'] =
        $this->info['width'] =
        $this->info['height'] =
            $max === null ?
                $trusted_wh :
                new HTMLPurifier_AttrDef_Switch(
                    'img',
                    // For img tags:
                    new HTMLPurifier_AttrDef_CSS_Composite(
                        array(
                            new HTMLPurifier_AttrDef_CSS_Length('0', $max),
                            new HTMLPurifier_AttrDef_Enum(array('auto'))
                        )
                    ),
                    // For everyone else:
                    $trusted_wh
                );

        $this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration();

        $this->info['font-family'] = new HTMLPurifier_AttrDef_CSS_FontFamily();

        // this could use specialized code
        $this->info['font-weight'] = new HTMLPurifier_AttrDef_Enum(
            array(
                'normal',
                'bold',
                'bolder',
                'lighter',
                '100',
                '200',
                '300',
                '400',
                '500',
                '600',
                '700',
                '800',
                '900'
            ),
            false
        );

        // MUST be called after other font properties, as it references
        // a CSSDefinition object
        $this->info['font'] = new HTMLPurifier_AttrDef_CSS_Font($config);

        // same here
        $this->info['border'] =
        $this->info['border-bottom'] =
        $this->info['border-top'] =
        $this->info['border-left'] =
        $this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config);

        $this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum(
            array('collapse', 'separate')
        );

        $this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum(
            array('top', 'bottom')
        );

        $this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum(
            array('auto', 'fixed')
        );

        $this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite(
            array(
                new HTMLPurifier_AttrDef_Enum(
                    array(
                        'baseline',
                        'sub',
                        'super',
                        'top',
                        'text-top',
                        'middle',
                        'bottom',
                        'text-bottom'
                    )
                ),
                new HTMLPurifier_AttrDef_CSS_Length(),
                new HTMLPurifier_AttrDef_CSS_Percentage()
            )
        );

        $this->info['border-spacing'] = new HTMLPurifier_AttrDef_CSS_Multiple(new HTMLPurifier_AttrDef_CSS_Length(), 2);

        // These CSS properties don't work on many browsers, but we live
        // in THE FUTURE!
        $this->info['white-space'] = new HTMLPurifier_AttrDef_Enum(
            array('nowrap', 'normal', 'pre', 'pre-wrap', 'pre-line')
        );

        if ($config->get('CSS.Proprietary')) {
            $this->doSetupProprietary($config);
        }

        if ($config->get('CSS.AllowTricky')) {
            $this->doSetupTricky($config);
        }

        if ($config->get('CSS.Trusted')) {
            $this->doSetupTrusted($config);
        }

        $allow_important = $config->get('CSS.AllowImportant');
        // wrap all attr-defs with decorator that handles !important
        foreach ($this->info as $k => $v) {
            $this->info[$k] = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($v, $allow_important);
        }

        $this->setupConfigStuff($config);
    }

    /**
     * @param HTMLPurifier_Config $config
     */
    protected function doSetupProprietary($config)
    {
        // Internet Explorer only scrollbar colors
        $this->info['scrollbar-arrow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
        $this->info['scrollbar-base-color'] = new HTMLPurifier_AttrDef_CSS_Color();
        $this->info['scrollbar-darkshadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
        $this->info['scrollbar-face-color'] = new HTMLPurifier_AttrDef_CSS_Color();
        $this->info['scrollbar-highlight-color'] = new HTMLPurifier_AttrDef_CSS_Color();
        $this->info['scrollbar-shadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();

        // vendor specific prefixes of opacity
        $this->info['-moz-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
        $this->info['-khtml-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();

        // only opacity, for now
        $this->info['filter'] = new HTMLPurifier_AttrDef_CSS_Filter();

        // more CSS3
        $this->info['page-break-after'] =
        $this->info['page-break-before'] = new HTMLPurifier_AttrDef_Enum(
            array(
                'auto',
                'always',
                'avoid',
                'left',
                'right'
            )
        );
        $this->info['page-break-inside'] = new HTMLPurifier_AttrDef_Enum(array('auto', 'avoid'));

        $border_radius = new HTMLPurifier_AttrDef_CSS_Composite(
            array(
                new HTMLPurifier_AttrDef_CSS_Percentage(true), // disallow negative
                new HTMLPurifier_AttrDef_CSS_Length('0') // disallow negative
            ));

        $this->info['border-top-left-radius'] =
        $this->info['border-top-right-radius'] =
        $this->info['border-bottom-right-radius'] =
        $this->info['border-bottom-left-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 2);
        // TODO: support SLASH syntax
        $this->info['border-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 4);

    }

    /**
     * @param HTMLPurifier_Config $config
     */
    protected function doSetupTricky($config)
    {
        $this->info['display'] = new HTMLPurifier_AttrDef_Enum(
            array(
                'inline',
                'block',
                'list-item',
                'run-in',
                'compact',
                'marker',
                'table',
                'inline-block',
                'inline-table',
                'table-row-group',
                'table-header-group',
                'table-footer-group',
                'table-row',
                'table-column-group',
                'table-column',
                'table-cell',
                'table-caption',
                'none'
            )
        );
        $this->info['visibility'] = new HTMLPurifier_AttrDef_Enum(
            array('visible', 'hidden', 'collapse')
        );
        $this->info['overflow'] = new HTMLPurifier_AttrDef_Enum(array('visible', 'hidden', 'auto', 'scroll'));
        $this->info['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
    }

    /**
     * @param HTMLPurifier_Config $config
     */
    protected function doSetupTrusted($config)
    {
        $this->info['position'] = new HTMLPurifier_AttrDef_Enum(
            array('static', 'relative', 'absolute', 'fixed')
        );
        $this->info['top'] =
        $this->info['left'] =
        $this->info['right'] =
        $this->info['bottom'] = new HTMLPurifier_AttrDef_CSS_Composite(
            array(
                new HTMLPurifier_AttrDef_CSS_Length(),
                new HTMLPurifier_AttrDef_CSS_Percentage(),
                new HTMLPurifier_AttrDef_Enum(array('auto')),
            )
        );
        $this->info['z-index'] = new HTMLPurifier_AttrDef_CSS_Composite(
            array(
                new HTMLPurifier_AttrDef_Integer(),
                new HTMLPurifier_AttrDef_Enum(array('auto')),
            )
        );
    }

    /**
     * Performs extra config-based processing. Based off of
     * HTMLPurifier_HTMLDefinition.
     * @param HTMLPurifier_Config $config
     * @todo Refactor duplicate elements into common class (probably using
     *       composition, not inheritance).
     */
    protected function setupConfigStuff($config)
    {
        // setup allowed elements
        $support = "(for information on implementing this, see the " .
            "support forums) ";
        $allowed_properties = $config->get('CSS.AllowedProperties');
        if ($allowed_properties !== null) {
            foreach ($this->info as $name => $d) {
                if (!isset($allowed_properties[$name])) {
                    unset($this->info[$name]);
                }
                unset($allowed_properties[$name]);
            }
            // emit errors
            foreach ($allowed_properties as $name => $d) {
                // :TODO: Is this htmlspecialchars() call really necessary?
                $name = htmlspecialchars($name);
                trigger_error("Style attribute '$name' is not supported $support", E_USER_WARNING);
            }
        }

        $forbidden_properties = $config->get('CSS.ForbiddenProperties');
        if ($forbidden_properties !== null) {
            foreach ($this->info as $name => $d) {
                if (isset($forbidden_properties[$name])) {
                    unset($this->info[$name]);
                }
            }
        }
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Our in-house implementation of a parser.
 *
 * A pure PHP parser, DirectLex has absolutely no dependencies, making
 * it a reasonably good default for PHP4.  Written with efficiency in mind,
 * it can be four times faster than HTMLPurifier_Lexer_PEARSax3, although it
 * pales in comparison to HTMLPurifier_Lexer_DOMLex.
 *
 * @todo Reread XML spec and document differences.
 */
class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
{
    /**
     * @type bool
     */
    public $tracksLineNumbers = true;

    /**
     * Whitespace characters for str(c)spn.
     * @type string
     */
    protected $_whitespace = "\x20\x09\x0D\x0A";

    /**
     * Callback function for script CDATA fudge
     * @param array $matches, in form of array(opening tag, contents, closing tag)
     * @return string
     */
    protected function scriptCallback($matches)
    {
        return $matches[1] . htmlspecialchars($matches[2], ENT_COMPAT, 'UTF-8') . $matches[3];
    }

    /**
     * @param String $html
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array|HTMLPurifier_Token[]
     */
    public function tokenizeHTML($html, $config, $context)
    {
        // special normalization for script tags without any armor
        // our "armor" heurstic is a < sign any number of whitespaces after
        // the first script tag
        if ($config->get('HTML.Trusted')) {
            $html = preg_replace_callback(
                '#(<script[^>]*>)(\s*[^<].+?)(</script>)#si',
                array($this, 'scriptCallback'),
                $html
            );
        }

        $html = $this->normalize($html, $config, $context);

        $cursor = 0; // our location in the text
        $inside_tag = false; // whether or not we're parsing the inside of a tag
        $array = array(); // result array

        // This is also treated to mean maintain *column* numbers too
        $maintain_line_numbers = $config->get('Core.MaintainLineNumbers');

        if ($maintain_line_numbers === null) {
            // automatically determine line numbering by checking
            // if error collection is on
            $maintain_line_numbers = $config->get('Core.CollectErrors');
        }

        if ($maintain_line_numbers) {
            $current_line = 1;
            $current_col = 0;
            $length = strlen($html);
        } else {
            $current_line = false;
            $current_col = false;
            $length = false;
        }
        $context->register('CurrentLine', $current_line);
        $context->register('CurrentCol', $current_col);
        $nl = "\n";
        // how often to manually recalculate. This will ALWAYS be right,
        // but it's pretty wasteful. Set to 0 to turn off
        $synchronize_interval = $config->get('Core.DirectLexLineNumberSyncInterval');

        $e = false;
        if ($config->get('Core.CollectErrors')) {
            $e =& $context->get('ErrorCollector');
        }

        // for testing synchronization
        $loops = 0;

        while (++$loops) {
            // $cursor is either at the start of a token, or inside of
            // a tag (i.e. there was a < immediately before it), as indicated
            // by $inside_tag

            if ($maintain_line_numbers) {
                // $rcursor, however, is always at the start of a token.
                $rcursor = $cursor - (int)$inside_tag;

                // Column number is cheap, so we calculate it every round.
                // We're interested at the *end* of the newline string, so
                // we need to add strlen($nl) == 1 to $nl_pos before subtracting it
                // from our "rcursor" position.
                $nl_pos = strrpos($html, $nl, $rcursor - $length);
                $current_col = $rcursor - (is_bool($nl_pos) ? 0 : $nl_pos + 1);

                // recalculate lines
                if ($synchronize_interval && // synchronization is on
                    $cursor > 0 && // cursor is further than zero
                    $loops % $synchronize_interval === 0) { // time to synchronize!
                    $current_line = 1 + $this->substrCount($html, $nl, 0, $cursor);
                }
            }

            $position_next_lt = strpos($html, '<', $cursor);
            $position_next_gt = strpos($html, '>', $cursor);

            // triggers on "<b>asdf</b>" but not "asdf <b></b>"
            // special case to set up context
            if ($position_next_lt === $cursor) {
                $inside_tag = true;
                $cursor++;
            }

            if (!$inside_tag && $position_next_lt !== false) {
                // We are not inside tag and there still is another tag to parse
                $token = new
                HTMLPurifier_Token_Text(
                    $this->parseText(
                        substr(
                            $html,
                            $cursor,
                            $position_next_lt - $cursor
                        ), $config
                    )
                );
                if ($maintain_line_numbers) {
                    $token->rawPosition($current_line, $current_col);
                    $current_line += $this->substrCount($html, $nl, $cursor, $position_next_lt - $cursor);
                }
                $array[] = $token;
                $cursor = $position_next_lt + 1;
                $inside_tag = true;
                continue;
            } elseif (!$inside_tag) {
                // We are not inside tag but there are no more tags
                // If we're already at the end, break
                if ($cursor === strlen($html)) {
                    break;
                }
                // Create Text of rest of string
                $token = new
                HTMLPurifier_Token_Text(
                    $this->parseText(
                        substr(
                            $html,
                            $cursor
                        ), $config
                    )
                );
                if ($maintain_line_numbers) {
                    $token->rawPosition($current_line, $current_col);
                }
                $array[] = $token;
                break;
            } elseif ($inside_tag && $position_next_gt !== false) {
                // We are in tag and it is well formed
                // Grab the internals of the tag
                $strlen_segment = $position_next_gt - $cursor;

                if ($strlen_segment < 1) {
                    // there's nothing to process!
                    $token = new HTMLPurifier_Token_Text('<');
                    $cursor++;
                    continue;
                }

                $segment = substr($html, $cursor, $strlen_segment);

                if ($segment === false) {
                    // somehow, we attempted to access beyond the end of
                    // the string, defense-in-depth, reported by Nate Abele
                    break;
                }

                // Check if it's a comment
                if (substr($segment, 0, 3) === '!--') {
                    // re-determine segment length, looking for -->
                    $position_comment_end = strpos($html, '-->', $cursor);
                    if ($position_comment_end === false) {
                        // uh oh, we have a comment that extends to
                        // infinity. Can't be helped: set comment
                        // end position to end of string
                        if ($e) {
                            $e->send(E_WARNING, 'Lexer: Unclosed comment');
                        }
                        $position_comment_end = strlen($html);
                        $end = true;
                    } else {
                        $end = false;
                    }
                    $strlen_segment = $position_comment_end - $cursor;
                    $segment = substr($html, $cursor, $strlen_segment);
                    $token = new
                    HTMLPurifier_Token_Comment(
                        substr(
                            $segment,
                            3,
                            $strlen_segment - 3
                        )
                    );
                    if ($maintain_line_numbers) {
                        $token->rawPosition($current_line, $current_col);
                        $current_line += $this->substrCount($html, $nl, $cursor, $strlen_segment);
                    }
                    $array[] = $token;
                    $cursor = $end ? $position_comment_end : $position_comment_end + 3;
                    $inside_tag = false;
                    continue;
                }

                // Check if it's an end tag
                $is_end_tag = (strpos($segment, '/') === 0);
                if ($is_end_tag) {
                    $type = substr($segment, 1);
                    $token = new HTMLPurifier_Token_End($type);
                    if ($maintain_line_numbers) {
                        $token->rawPosition($current_line, $current_col);
                        $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
                    }
                    $array[] = $token;
                    $inside_tag = false;
                    $cursor = $position_next_gt + 1;
                    continue;
                }

                // Check leading character is alnum, if not, we may
                // have accidently grabbed an emoticon. Translate into
                // text and go our merry way
                if (!ctype_alpha($segment[0])) {
                    // XML:  $segment[0] !== '_' && $segment[0] !== ':'
                    if ($e) {
                        $e->send(E_NOTICE, 'Lexer: Unescaped lt');
                    }
                    $token = new HTMLPurifier_Token_Text('<');
                    if ($maintain_line_numbers) {
                        $token->rawPosition($current_line, $current_col);
                        $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
                    }
                    $array[] = $token;
                    $inside_tag = false;
                    continue;
                }

                // Check if it is explicitly self closing, if so, remove
                // trailing slash. Remember, we could have a tag like <br>, so
                // any later token processing scripts must convert improperly
                // classified EmptyTags from StartTags.
                $is_self_closing = (strrpos($segment, '/') === $strlen_segment - 1);
                if ($is_self_closing) {
                    $strlen_segment--;
                    $segment = substr($segment, 0, $strlen_segment);
                }

                // Check if there are any attributes
                $position_first_space = strcspn($segment, $this->_whitespace);

                if ($position_first_space >= $strlen_segment) {
                    if ($is_self_closing) {
                        $token = new HTMLPurifier_Token_Empty($segment);
                    } else {
                        $token = new HTMLPurifier_Token_Start($segment);
                    }
                    if ($maintain_line_numbers) {
                        $token->rawPosition($current_line, $current_col);
                        $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
                    }
                    $array[] = $token;
                    $inside_tag = false;
                    $cursor = $position_next_gt + 1;
                    continue;
                }

                // Grab out all the data
                $type = substr($segment, 0, $position_first_space);
                $attribute_string =
                    trim(
                        substr(
                            $segment,
                            $position_first_space
                        )
                    );
                if ($attribute_string) {
                    $attr = $this->parseAttributeString(
                        $attribute_string,
                        $config,
                        $context
                    );
                } else {
                    $attr = array();
                }

                if ($is_self_closing) {
                    $token = new HTMLPurifier_Token_Empty($type, $attr);
                } else {
                    $token = new HTMLPurifier_Token_Start($type, $attr);
                }
                if ($maintain_line_numbers) {
                    $token->rawPosition($current_line, $current_col);
                    $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
                }
                $array[] = $token;
                $cursor = $position_next_gt + 1;
                $inside_tag = false;
                continue;
            } else {
                // inside tag, but there's no ending > sign
                if ($e) {
                    $e->send(E_WARNING, 'Lexer: Missing gt');
                }
                $token = new
                HTMLPurifier_Token_Text(
                    '<' .
                    $this->parseText(
                        substr($html, $cursor), $config
                    )
                );
                if ($maintain_line_numbers) {
                    $token->rawPosition($current_line, $current_col);
                }
                // no cursor scroll? Hmm...
                $array[] = $token;
                break;
            }
            break;
        }

        $context->destroy('CurrentLine');
        $context->destroy('CurrentCol');
        return $array;
    }

    /**
     * PHP 5.0.x compatible substr_count that implements offset and length
     * @param string $haystack
     * @param string $needle
     * @param int $offset
     * @param int $length
     * @return int
     */
    protected function substrCount($haystack, $needle, $offset, $length)
    {
        static $oldVersion;
        if ($oldVersion === null) {
            $oldVersion = version_compare(PHP_VERSION, '5.1', '<');
        }
        if ($oldVersion) {
            $haystack = substr($haystack, $offset, $length);
            return substr_count($haystack, $needle);
        } else {
            return substr_count($haystack, $needle, $offset, $length);
        }
    }

    /**
     * Takes the inside of an HTML tag and makes an assoc array of attributes.
     *
     * @param string $string Inside of tag excluding name.
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array Assoc array of attributes.
     */
    public function parseAttributeString($string, $config, $context)
    {
        $string = (string)$string; // quick typecast

        if ($string == '') {
            return array();
        } // no attributes

        $e = false;
        if ($config->get('Core.CollectErrors')) {
            $e =& $context->get('ErrorCollector');
        }

        // let's see if we can abort as quickly as possible
        // one equal sign, no spaces => one attribute
        $num_equal = substr_count($string, '=');
        $has_space = strpos($string, ' ');
        if ($num_equal === 0 && !$has_space) {
            // bool attribute
            return array($string => $string);
        } elseif ($num_equal === 1 && !$has_space) {
            // only one attribute
            list($key, $quoted_value) = explode('=', $string);
            $quoted_value = trim($quoted_value);
            if (!$key) {
                if ($e) {
                    $e->send(E_ERROR, 'Lexer: Missing attribute key');
                }
                return array();
            }
            if (!$quoted_value) {
                return array($key => '');
            }
            $first_char = @$quoted_value[0];
            $last_char = @$quoted_value[strlen($quoted_value) - 1];

            $same_quote = ($first_char == $last_char);
            $open_quote = ($first_char == '"' || $first_char == "'");

            if ($same_quote && $open_quote) {
                // well behaved
                $value = substr($quoted_value, 1, strlen($quoted_value) - 2);
            } else {
                // not well behaved
                if ($open_quote) {
                    if ($e) {
                        $e->send(E_ERROR, 'Lexer: Missing end quote');
                    }
                    $value = substr($quoted_value, 1);
                } else {
                    $value = $quoted_value;
                }
            }
            if ($value === false) {
                $value = '';
            }
            return array($key => $this->parseAttr($value, $config));
        }

        // setup loop environment
        $array = array(); // return assoc array of attributes
        $cursor = 0; // current position in string (moves forward)
        $size = strlen($string); // size of the string (stays the same)

        // if we have unquoted attributes, the parser expects a terminating
        // space, so let's guarantee that there's always a terminating space.
        $string .= ' ';

        $old_cursor = -1;
        while ($cursor < $size) {
            if ($old_cursor >= $cursor) {
                throw new Exception("Infinite loop detected");
            }
            $old_cursor = $cursor;

            $cursor += ($value = strspn($string, $this->_whitespace, $cursor));
            // grab the key

            $key_begin = $cursor; //we're currently at the start of the key

            // scroll past all characters that are the key (not whitespace or =)
            $cursor += strcspn($string, $this->_whitespace . '=', $cursor);

            $key_end = $cursor; // now at the end of the key

            $key = substr($string, $key_begin, $key_end - $key_begin);

            if (!$key) {
                if ($e) {
                    $e->send(E_ERROR, 'Lexer: Missing attribute key');
                }
                $cursor += 1 + strcspn($string, $this->_whitespace, $cursor + 1); // prevent infinite loop
                continue; // empty key
            }

            // scroll past all whitespace
            $cursor += strspn($string, $this->_whitespace, $cursor);

            if ($cursor >= $size) {
                $array[$key] = $key;
                break;
            }

            // if the next character is an equal sign, we've got a regular
            // pair, otherwise, it's a bool attribute
            $first_char = @$string[$cursor];

            if ($first_char == '=') {
                // key="value"

                $cursor++;
                $cursor += strspn($string, $this->_whitespace, $cursor);

                if ($cursor === false) {
                    $array[$key] = '';
                    break;
                }

                // we might be in front of a quote right now

                $char = @$string[$cursor];

                if ($char == '"' || $char == "'") {
                    // it's quoted, end bound is $char
                    $cursor++;
                    $value_begin = $cursor;
                    $cursor = strpos($string, $char, $cursor);
                    $value_end = $cursor;
                } else {
                    // it's not quoted, end bound is whitespace
                    $value_begin = $cursor;
                    $cursor += strcspn($string, $this->_whitespace, $cursor);
                    $value_end = $cursor;
                }

                // we reached a premature end
                if ($cursor === false) {
                    $cursor = $size;
                    $value_end = $cursor;
                }

                $value = substr($string, $value_begin, $value_end - $value_begin);
                if ($value === false) {
                    $value = '';
                }
                $array[$key] = $this->parseAttr($value, $config);
                $cursor++;
            } else {
                // boolattr
                if ($key !== '') {
                    $array[$key] = $key;
                } else {
                    // purely theoretical
                    if ($e) {
                        $e->send(E_ERROR, 'Lexer: Missing attribute key');
                    }
                }
            }
        }
        return $array;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Parser that uses PHP 5's DOM extension (part of the core).
 *
 * In PHP 5, the DOM XML extension was revamped into DOM and added to the core.
 * It gives us a forgiving HTML parser, which we use to transform the HTML
 * into a DOM, and then into the tokens.  It is blazingly fast (for large
 * documents, it performs twenty times faster than
 * HTMLPurifier_Lexer_DirectLex,and is the default choice for PHP 5.
 *
 * @note Any empty elements will have empty tokens associated with them, even if
 * this is prohibited by the spec. This is cannot be fixed until the spec
 * comes into play.
 *
 * @note PHP's DOM extension does not actually parse any entities, we use
 *       our own function to do that.
 *
 * @warning DOM tends to drop whitespace, which may wreak havoc on indenting.
 *          If this is a huge problem, due to the fact that HTML is hand
 *          edited and you are unable to get a parser cache that caches the
 *          the output of HTML Purifier while keeping the original HTML lying
 *          around, you may want to run Tidy on the resulting output or use
 *          HTMLPurifier_DirectLex
 */

class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
{

    /**
     * @type HTMLPurifier_TokenFactory
     */
    private $factory;

    public function __construct()
    {
        // setup the factory
        parent::__construct();
        $this->factory = new HTMLPurifier_TokenFactory();
    }

    /**
     * @param string $html
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return HTMLPurifier_Token[]
     */
    public function tokenizeHTML($html, $config, $context)
    {
        $html = $this->normalize($html, $config, $context);

        // attempt to armor stray angled brackets that cannot possibly
        // form tags and thus are probably being used as emoticons
        if ($config->get('Core.AggressivelyFixLt')) {
            $char = '[^a-z!\/]';
            $comment = "/<!--(.*?)(-->|\z)/is";
            $html = preg_replace_callback($comment, array($this, 'callbackArmorCommentEntities'), $html);
            do {
                $old = $html;
                $html = preg_replace("/<($char)/i", '&lt;\\1', $html);
            } while ($html !== $old);
            $html = preg_replace_callback($comment, array($this, 'callbackUndoCommentSubst'), $html); // fix comments
        }

        // preprocess html, essential for UTF-8
        $html = $this->wrapHTML($html, $config, $context);

        $doc = new DOMDocument();
        $doc->encoding = 'UTF-8'; // theoretically, the above has this covered

        set_error_handler(array($this, 'muteErrorHandler'));
        $doc->loadHTML($html);
        restore_error_handler();

        $body = $doc->getElementsByTagName('html')->item(0)-> // <html>
                      getElementsByTagName('body')->item(0);  // <body>

        $div = $body->getElementsByTagName('div')->item(0); // <div>
        $tokens = array();
        $this->tokenizeDOM($div, $tokens, $config);
        // If the div has a sibling, that means we tripped across
        // a premature </div> tag.  So remove the div we parsed,
        // and then tokenize the rest of body.  We can't tokenize
        // the sibling directly as we'll lose the tags in that case.
        if ($div->nextSibling) {
            $body->removeChild($div);
            $this->tokenizeDOM($body, $tokens, $config);
        }
        return $tokens;
    }

    /**
     * Iterative function that tokenizes a node, putting it into an accumulator.
     * To iterate is human, to recurse divine - L. Peter Deutsch
     * @param DOMNode $node DOMNode to be tokenized.
     * @param HTMLPurifier_Token[] $tokens   Array-list of already tokenized tokens.
     * @return HTMLPurifier_Token of node appended to previously passed tokens.
     */
    protected function tokenizeDOM($node, &$tokens, $config)
    {
        $level = 0;
        $nodes = array($level => new HTMLPurifier_Queue(array($node)));
        $closingNodes = array();
        do {
            while (!$nodes[$level]->isEmpty()) {
                $node = $nodes[$level]->shift(); // FIFO
                $collect = $level > 0 ? true : false;
                $needEndingTag = $this->createStartNode($node, $tokens, $collect, $config);
                if ($needEndingTag) {
                    $closingNodes[$level][] = $node;
                }
                if ($node->childNodes && $node->childNodes->length) {
                    $level++;
                    $nodes[$level] = new HTMLPurifier_Queue();
                    foreach ($node->childNodes as $childNode) {
                        $nodes[$level]->push($childNode);
                    }
                }
            }
            $level--;
            if ($level && isset($closingNodes[$level])) {
                while ($node = array_pop($closingNodes[$level])) {
                    $this->createEndNode($node, $tokens);
                }
            }
        } while ($level > 0);
    }

    /**
     * Portably retrieve the tag name of a node; deals with older versions
     * of libxml like 2.7.6
     * @param DOMNode $node
     */
    protected function getTagName($node)
    {
        if (property_exists($node, 'tagName')) {
            return $node->tagName;
        } else if (property_exists($node, 'nodeName')) {
            return $node->nodeName;
        } else if (property_exists($node, 'localName')) {
            return $node->localName;
        }
        return null;
    }

    /**
     * Portably retrieve the data of a node; deals with older versions
     * of libxml like 2.7.6
     * @param DOMNode $node
     */
    protected function getData($node)
    {
        if (property_exists($node, 'data')) {
            return $node->data;
        } else if (property_exists($node, 'nodeValue')) {
            return $node->nodeValue;
        } else if (property_exists($node, 'textContent')) {
            return $node->textContent;
        }
        return null;
    }


    /**
     * @param DOMNode $node DOMNode to be tokenized.
     * @param HTMLPurifier_Token[] $tokens   Array-list of already tokenized tokens.
     * @param bool $collect  Says whether or start and close are collected, set to
     *                    false at first recursion because it's the implicit DIV
     *                    tag you're dealing with.
     * @return bool if the token needs an endtoken
     * @todo data and tagName properties don't seem to exist in DOMNode?
     */
    protected function createStartNode($node, &$tokens, $collect, $config)
    {
        // intercept non element nodes. WE MUST catch all of them,
        // but we're not getting the character reference nodes because
        // those should have been preprocessed
        if ($node->nodeType === XML_TEXT_NODE) {
            $data = $this->getData($node); // Handle variable data property
            if ($data !== null) {
              $tokens[] = $this->factory->createText($data);
            }
            return false;
        } elseif ($node->nodeType === XML_CDATA_SECTION_NODE) {
            // undo libxml's special treatment of <script> and <style> tags
            $last = end($tokens);
            $data = $node->data;
            // (note $node->tagname is already normalized)
            if ($last instanceof HTMLPurifier_Token_Start && ($last->name == 'script' || $last->name == 'style')) {
                $new_data = trim($data);
                if (substr($new_data, 0, 4) === '<!--') {
                    $data = substr($new_data, 4);
                    if (substr($data, -3) === '-->') {
                        $data = substr($data, 0, -3);
                    } else {
                        // Highly suspicious! Not sure what to do...
                    }
                }
            }
            $tokens[] = $this->factory->createText($this->parseText($data, $config));
            return false;
        } elseif ($node->nodeType === XML_COMMENT_NODE) {
            // this is code is only invoked for comments in script/style in versions
            // of libxml pre-2.6.28 (regular comments, of course, are still
            // handled regularly)
            $tokens[] = $this->factory->createComment($node->data);
            return false;
        } elseif ($node->nodeType !== XML_ELEMENT_NODE) {
            // not-well tested: there may be other nodes we have to grab
            return false;
        }
        $attr = $node->hasAttributes() ? $this->transformAttrToAssoc($node->attributes) : array();
        $tag_name = $this->getTagName($node); // Handle variable tagName property
        if (empty($tag_name)) {
            return (bool) $node->childNodes->length;
        }
        // We still have to make sure that the element actually IS empty
        if (!$node->childNodes->length) {
            if ($collect) {
                $tokens[] = $this->factory->createEmpty($tag_name, $attr);
            }
            return false;
        } else {
            if ($collect) {
                $tokens[] = $this->factory->createStart($tag_name, $attr);
            }
            return true;
        }
    }

    /**
     * @param DOMNode $node
     * @param HTMLPurifier_Token[] $tokens
     */
    protected function createEndNode($node, &$tokens)
    {
        $tag_name = $this->getTagName($node); // Handle variable tagName property
        $tokens[] = $this->factory->createEnd($tag_name);
    }

    /**
     * Converts a DOMNamedNodeMap of DOMAttr objects into an assoc array.
     *
     * @param DOMNamedNodeMap $node_map DOMNamedNodeMap of DOMAttr objects.
     * @return array Associative array of attributes.
     */
    protected function transformAttrToAssoc($node_map)
    {
        // NamedNodeMap is documented very well, so we're using undocumented
        // features, namely, the fact that it implements Iterator and
        // has a ->length attribute
        if ($node_map->length === 0) {
            return array();
        }
        $array = array();
        foreach ($node_map as $attr) {
            $array[$attr->name] = $attr->value;
        }
        return $array;
    }

    /**
     * An error handler that mutes all errors
     * @param int $errno
     * @param string $errstr
     */
    public function muteErrorHandler($errno, $errstr)
    {
    }

    /**
     * Callback function for undoing escaping of stray angled brackets
     * in comments
     * @param array $matches
     * @return string
     */
    public function callbackUndoCommentSubst($matches)
    {
        return '<!--' . strtr($matches[1], array('&amp;' => '&', '&lt;' => '<')) . $matches[2];
    }

    /**
     * Callback function that entity-izes ampersands in comments so that
     * callbackUndoCommentSubst doesn't clobber them
     * @param array $matches
     * @return string
     */
    public function callbackArmorCommentEntities($matches)
    {
        return '<!--' . str_replace('&', '&amp;', $matches[1]) . $matches[2];
    }

    /**
     * Wraps an HTML fragment in the necessary HTML
     * @param string $html
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return string
     */
    protected function wrapHTML($html, $config, $context, $use_div = true)
    {
        $def = $config->getDefinition('HTML');
        $ret = '';

        if (!empty($def->doctype->dtdPublic) || !empty($def->doctype->dtdSystem)) {
            $ret .= '<!DOCTYPE html ';
            if (!empty($def->doctype->dtdPublic)) {
                $ret .= 'PUBLIC "' . $def->doctype->dtdPublic . '" ';
            }
            if (!empty($def->doctype->dtdSystem)) {
                $ret .= '"' . $def->doctype->dtdSystem . '" ';
            }
            $ret .= '>';
        }

        $ret .= '<html><head>';
        $ret .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
        // No protection if $html contains a stray </div>!
        $ret .= '</head><body>';
        if ($use_div) $ret .= '<div>';
        $ret .= $html;
        if ($use_div) $ret .= '</div>';
        $ret .= '</body></html>';
        return $ret;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Experimental HTML5-based parser using Jeroen van der Meer's PH5P library.
 * Occupies space in the HTML5 pseudo-namespace, which may cause conflicts.
 *
 * @note
 *    Recent changes to PHP's DOM extension have resulted in some fatal
 *    error conditions with the original version of PH5P. Pending changes,
 *    this lexer will punt to DirectLex if DOM throws an exception.
 */

class HTMLPurifier_Lexer_PH5P extends HTMLPurifier_Lexer_DOMLex
{
    /**
     * @param string $html
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return HTMLPurifier_Token[]
     */
    public function tokenizeHTML($html, $config, $context)
    {
        $new_html = $this->normalize($html, $config, $context);
        $new_html = $this->wrapHTML($new_html, $config, $context, false /* no div */);
        try {
            $parser = new HTML5($new_html);
            $doc = $parser->save();
        } catch (DOMException $e) {
            // Uh oh, it failed. Punt to DirectLex.
            $lexer = new HTMLPurifier_Lexer_DirectLex();
            $context->register('PH5PError', $e); // save the error, so we can detect it
            return $lexer->tokenizeHTML($html, $config, $context); // use original HTML
        }
        $tokens = array();
        $this->tokenizeDOM(
            $doc->getElementsByTagName('html')->item(0)-> // <html>
                  getElementsByTagName('body')->item(0) //   <body>
            ,
            $tokens, $config
        );
        return $tokens;
    }
}

/*

Copyright 2007 Jeroen van der Meer <http://jero.net/>

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/

class HTML5
{
    private $data;
    private $char;
    private $EOF;
    private $state;
    private $tree;
    private $token;
    private $content_model;
    private $escape = false;
    private $entities = array(
        'AElig;',
        'AElig',
        'AMP;',
        'AMP',
        'Aacute;',
        'Aacute',
        'Acirc;',
        'Acirc',
        'Agrave;',
        'Agrave',
        'Alpha;',
        'Aring;',
        'Aring',
        'Atilde;',
        'Atilde',
        'Auml;',
        'Auml',
        'Beta;',
        'COPY;',
        'COPY',
        'Ccedil;',
        'Ccedil',
        'Chi;',
        'Dagger;',
        'Delta;',
        'ETH;',
        'ETH',
        'Eacute;',
        'Eacute',
        'Ecirc;',
        'Ecirc',
        'Egrave;',
        'Egrave',
        'Epsilon;',
        'Eta;',
        'Euml;',
        'Euml',
        'GT;',
        'GT',
        'Gamma;',
        'Iacute;',
        'Iacute',
        'Icirc;',
        'Icirc',
        'Igrave;',
        'Igrave',
        'Iota;',
        'Iuml;',
        'Iuml',
        'Kappa;',
        'LT;',
        'LT',
        'Lambda;',
        'Mu;',
        'Ntilde;',
        'Ntilde',
        'Nu;',
        'OElig;',
        'Oacute;',
        'Oacute',
        'Ocirc;',
        'Ocirc',
        'Ograve;',
        'Ograve',
        'Omega;',
        'Omicron;',
        'Oslash;',
        'Oslash',
        'Otilde;',
        'Otilde',
        'Ouml;',
        'Ouml',
        'Phi;',
        'Pi;',
        'Prime;',
        'Psi;',
        'QUOT;',
        'QUOT',
        'REG;',
        'REG',
        'Rho;',
        'Scaron;',
        'Sigma;',
        'THORN;',
        'THORN',
        'TRADE;',
        'Tau;',
        'Theta;',
        'Uacute;',
        'Uacute',
        'Ucirc;',
        'Ucirc',
        'Ugrave;',
        'Ugrave',
        'Upsilon;',
        'Uuml;',
        'Uuml',
        'Xi;',
        'Yacute;',
        'Yacute',
        'Yuml;',
        'Zeta;',
        'aacute;',
        'aacute',
        'acirc;',
        'acirc',
        'acute;',
        'acute',
        'aelig;',
        'aelig',
        'agrave;',
        'agrave',
        'alefsym;',
        'alpha;',
        'amp;',
        'amp',
        'and;',
        'ang;',
        'apos;',
        'aring;',
        'aring',
        'asymp;',
        'atilde;',
        'atilde',
        'auml;',
        'auml',
        'bdquo;',
        'beta;',
        'brvbar;',
        'brvbar',
        'bull;',
        'cap;',
        'ccedil;',
        'ccedil',
        'cedil;',
        'cedil',
        'cent;',
        'cent',
        'chi;',
        'circ;',
        'clubs;',
        'cong;',
        'copy;',
        'copy',
        'crarr;',
        'cup;',
        'curren;',
        'curren',
        'dArr;',
        'dagger;',
        'darr;',
        'deg;',
        'deg',
        'delta;',
        'diams;',
        'divide;',
        'divide',
        'eacute;',
        'eacute',
        'ecirc;',
        'ecirc',
        'egrave;',
        'egrave',
        'empty;',
        'emsp;',
        'ensp;',
        'epsilon;',
        'equiv;',
        'eta;',
        'eth;',
        'eth',
        'euml;',
        'euml',
        'euro;',
        'exist;',
        'fnof;',
        'forall;',
        'frac12;',
        'frac12',
        'frac14;',
        'frac14',
        'frac34;',
        'frac34',
        'frasl;',
        'gamma;',
        'ge;',
        'gt;',
        'gt',
        'hArr;',
        'harr;',
        'hearts;',
        'hellip;',
        'iacute;',
        'iacute',
        'icirc;',
        'icirc',
        'iexcl;',
        'iexcl',
        'igrave;',
        'igrave',
        'image;',
        'infin;',
        'int;',
        'iota;',
        'iquest;',
        'iquest',
        'isin;',
        'iuml;',
        'iuml',
        'kappa;',
        'lArr;',
        'lambda;',
        'lang;',
        'laquo;',
        'laquo',
        'larr;',
        'lceil;',
        'ldquo;',
        'le;',
        'lfloor;',
        'lowast;',
        'loz;',
        'lrm;',
        'lsaquo;',
        'lsquo;',
        'lt;',
        'lt',
        'macr;',
        'macr',
        'mdash;',
        'micro;',
        'micro',
        'middot;',
        'middot',
        'minus;',
        'mu;',
        'nabla;',
        'nbsp;',
        'nbsp',
        'ndash;',
        'ne;',
        'ni;',
        'not;',
        'not',
        'notin;',
        'nsub;',
        'ntilde;',
        'ntilde',
        'nu;',
        'oacute;',
        'oacute',
        'ocirc;',
        'ocirc',
        'oelig;',
        'ograve;',
        'ograve',
        'oline;',
        'omega;',
        'omicron;',
        'oplus;',
        'or;',
        'ordf;',
        'ordf',
        'ordm;',
        'ordm',
        'oslash;',
        'oslash',
        'otilde;',
        'otilde',
        'otimes;',
        'ouml;',
        'ouml',
        'para;',
        'para',
        'part;',
        'permil;',
        'perp;',
        'phi;',
        'pi;',
        'piv;',
        'plusmn;',
        'plusmn',
        'pound;',
        'pound',
        'prime;',
        'prod;',
        'prop;',
        'psi;',
        'quot;',
        'quot',
        'rArr;',
        'radic;',
        'rang;',
        'raquo;',
        'raquo',
        'rarr;',
        'rceil;',
        'rdquo;',
        'real;',
        'reg;',
        'reg',
        'rfloor;',
        'rho;',
        'rlm;',
        'rsaquo;',
        'rsquo;',
        'sbquo;',
        'scaron;',
        'sdot;',
        'sect;',
        'sect',
        'shy;',
        'shy',
        'sigma;',
        'sigmaf;',
        'sim;',
        'spades;',
        'sub;',
        'sube;',
        'sum;',
        'sup1;',
        'sup1',
        'sup2;',
        'sup2',
        'sup3;',
        'sup3',
        'sup;',
        'supe;',
        'szlig;',
        'szlig',
        'tau;',
        'there4;',
        'theta;',
        'thetasym;',
        'thinsp;',
        'thorn;',
        'thorn',
        'tilde;',
        'times;',
        'times',
        'trade;',
        'uArr;',
        'uacute;',
        'uacute',
        'uarr;',
        'ucirc;',
        'ucirc',
        'ugrave;',
        'ugrave',
        'uml;',
        'uml',
        'upsih;',
        'upsilon;',
        'uuml;',
        'uuml',
        'weierp;',
        'xi;',
        'yacute;',
        'yacute',
        'yen;',
        'yen',
        'yuml;',
        'yuml',
        'zeta;',
        'zwj;',
        'zwnj;'
    );

    const PCDATA = 0;
    const RCDATA = 1;
    const CDATA = 2;
    const PLAINTEXT = 3;

    const DOCTYPE = 0;
    const STARTTAG = 1;
    const ENDTAG = 2;
    const COMMENT = 3;
    const CHARACTR = 4;
    const EOF = 5;

    public function __construct($data)
    {
        $this->data = $data;
        $this->char = -1;
        $this->EOF = strlen($data);
        $this->tree = new HTML5TreeConstructer;
        $this->content_model = self::PCDATA;

        $this->state = 'data';

        while ($this->state !== null) {
            $this->{$this->state . 'State'}();
        }
    }

    public function save()
    {
        return $this->tree->save();
    }

    private function char()
    {
        return ($this->char < $this->EOF)
            ? $this->data[$this->char]
            : false;
    }

    private function character($s, $l = 0)
    {
        if ($s + $l < $this->EOF) {
            if ($l === 0) {
                return $this->data[$s];
            } else {
                return substr($this->data, $s, $l);
            }
        }
    }

    private function characters($char_class, $start)
    {
        return preg_replace('#^([' . $char_class . ']+).*#s', '\\1', substr($this->data, $start));
    }

    private function dataState()
    {
        // Consume the next input character
        $this->char++;
        $char = $this->char();

        if ($char === '&' && ($this->content_model === self::PCDATA || $this->content_model === self::RCDATA)) {
            /* U+0026 AMPERSAND (&)
            When the content model flag is set to one of the PCDATA or RCDATA
            states: switch to the entity data state. Otherwise: treat it as per
            the "anything else"    entry below. */
            $this->state = 'entityData';

        } elseif ($char === '-') {
            /* If the content model flag is set to either the RCDATA state or
            the CDATA state, and the escape flag is false, and there are at
            least three characters before this one in the input stream, and the
            last four characters in the input stream, including this one, are
            U+003C LESS-THAN SIGN, U+0021 EXCLAMATION MARK, U+002D HYPHEN-MINUS,
            and U+002D HYPHEN-MINUS ("<!--"), then set the escape flag to true. */
            if (($this->content_model === self::RCDATA || $this->content_model ===
                    self::CDATA) && $this->escape === false &&
                $this->char >= 3 && $this->character($this->char - 4, 4) === '<!--'
            ) {
                $this->escape = true;
            }

            /* In any case, emit the input character as a character token. Stay
            in the data state. */
            $this->emitToken(
                array(
                    'type' => self::CHARACTR,
                    'data' => $char
                )
            );

            /* U+003C LESS-THAN SIGN (<) */
        } elseif ($char === '<' && ($this->content_model === self::PCDATA ||
                (($this->content_model === self::RCDATA ||
                        $this->content_model === self::CDATA) && $this->escape === false))
        ) {
            /* When the content model flag is set to the PCDATA state: switch
            to the tag open state.

            When the content model flag is set to either the RCDATA state or
            the CDATA state and the escape flag is false: switch to the tag
            open state.

            Otherwise: treat it as per the "anything else" entry below. */
            $this->state = 'tagOpen';

            /* U+003E GREATER-THAN SIGN (>) */
        } elseif ($char === '>') {
            /* If the content model flag is set to either the RCDATA state or
            the CDATA state, and the escape flag is true, and the last three
            characters in the input stream including this one are U+002D
            HYPHEN-MINUS, U+002D HYPHEN-MINUS, U+003E GREATER-THAN SIGN ("-->"),
            set the escape flag to false. */
            if (($this->content_model === self::RCDATA ||
                    $this->content_model === self::CDATA) && $this->escape === true &&
                $this->character($this->char, 3) === '-->'
            ) {
                $this->escape = false;
            }

            /* In any case, emit the input character as a character token.
            Stay in the data state. */
            $this->emitToken(
                array(
                    'type' => self::CHARACTR,
                    'data' => $char
                )
            );

        } elseif ($this->char === $this->EOF) {
            /* EOF
            Emit an end-of-file token. */
            $this->EOF();

        } elseif ($this->content_model === self::PLAINTEXT) {
            /* When the content model flag is set to the PLAINTEXT state
            THIS DIFFERS GREATLY FROM THE SPEC: Get the remaining characters of
            the text and emit it as a character token. */
            $this->emitToken(
                array(
                    'type' => self::CHARACTR,
                    'data' => substr($this->data, $this->char)
                )
            );

            $this->EOF();

        } else {
            /* Anything else
            THIS DIFFERS GREATLY FROM THE SPEC: Get as many character that
            otherwise would also be treated as a character token and emit it
            as a single character token. Stay in the data state. */
            $len = strcspn($this->data, '<&', $this->char);
            $char = substr($this->data, $this->char, $len);
            $this->char += $len - 1;

            $this->emitToken(
                array(
                    'type' => self::CHARACTR,
                    'data' => $char
                )
            );

            $this->state = 'data';
        }
    }

    private function entityDataState()
    {
        // Attempt to consume an entity.
        $entity = $this->entity();

        // If nothing is returned, emit a U+0026 AMPERSAND character token.
        // Otherwise, emit the character token that was returned.
        $char = (!$entity) ? '&' : $entity;
        $this->emitToken(
            array(
                'type' => self::CHARACTR,
                'data' => $char
            )
        );

        // Finally, switch to the data state.
        $this->state = 'data';
    }

    private function tagOpenState()
    {
        switch ($this->content_model) {
            case self::RCDATA:
            case self::CDATA:
                /* If the next input character is a U+002F SOLIDUS (/) character,
                consume it and switch to the close tag open state. If the next
                input character is not a U+002F SOLIDUS (/) character, emit a
                U+003C LESS-THAN SIGN character token and switch to the data
                state to process the next input character. */
                if ($this->character($this->char + 1) === '/') {
                    $this->char++;
                    $this->state = 'closeTagOpen';

                } else {
                    $this->emitToken(
                        array(
                            'type' => self::CHARACTR,
                            'data' => '<'
                        )
                    );

                    $this->state = 'data';
                }
                break;

            case self::PCDATA:
                // If the content model flag is set to the PCDATA state
                // Consume the next input character:
                $this->char++;
                $char = $this->char();

                if ($char === '!') {
                    /* U+0021 EXCLAMATION MARK (!)
                    Switch to the markup declaration open state. */
                    $this->state = 'markupDeclarationOpen';

                } elseif ($char === '/') {
                    /* U+002F SOLIDUS (/)
                    Switch to the close tag open state. */
                    $this->state = 'closeTagOpen';

                } elseif (preg_match('/^[A-Za-z]$/', $char)) {
                    /* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z
                    Create a new start tag token, set its tag name to the lowercase
                    version of the input character (add 0x0020 to the character's code
                    point), then switch to the tag name state. (Don't emit the token
                    yet; further details will be filled in before it is emitted.) */
                    $this->token = array(
                        'name' => strtolower($char),
                        'type' => self::STARTTAG,
                        'attr' => array()
                    );

                    $this->state = 'tagName';

                } elseif ($char === '>') {
                    /* U+003E GREATER-THAN SIGN (>)
                    Parse error. Emit a U+003C LESS-THAN SIGN character token and a
                    U+003E GREATER-THAN SIGN character token. Switch to the data state. */
                    $this->emitToken(
                        array(
                            'type' => self::CHARACTR,
                            'data' => '<>'
                        )
                    );

                    $this->state = 'data';

                } elseif ($char === '?') {
                    /* U+003F QUESTION MARK (?)
                    Parse error. Switch to the bogus comment state. */
                    $this->state = 'bogusComment';

                } else {
                    /* Anything else
                    Parse error. Emit a U+003C LESS-THAN SIGN character token and
                    reconsume the current input character in the data state. */
                    $this->emitToken(
                        array(
                            'type' => self::CHARACTR,
                            'data' => '<'
                        )
                    );

                    $this->char--;
                    $this->state = 'data';
                }
                break;
        }
    }

    private function closeTagOpenState()
    {
        $next_node = strtolower($this->characters('A-Za-z', $this->char + 1));
        $the_same = count($this->tree->stack) > 0 && $next_node === end($this->tree->stack)->nodeName;

        if (($this->content_model === self::RCDATA || $this->content_model === self::CDATA) &&
            (!$the_same || ($the_same && (!preg_match(
                            '/[\t\n\x0b\x0c >\/]/',
                            $this->character($this->char + 1 + strlen($next_node))
                        ) || $this->EOF === $this->char)))
        ) {
            /* If the content model flag is set to the RCDATA or CDATA states then
            examine the next few characters. If they do not match the tag name of
            the last start tag token emitted (case insensitively), or if they do but
            they are not immediately followed by one of the following characters:
                * U+0009 CHARACTER TABULATION
                * U+000A LINE FEED (LF)
                * U+000B LINE TABULATION
                * U+000C FORM FEED (FF)
                * U+0020 SPACE
                * U+003E GREATER-THAN SIGN (>)
                * U+002F SOLIDUS (/)
                * EOF
            ...then there is a parse error. Emit a U+003C LESS-THAN SIGN character
            token, a U+002F SOLIDUS character token, and switch to the data state
            to process the next input character. */
            $this->emitToken(
                array(
                    'type' => self::CHARACTR,
                    'data' => '</'
                )
            );

            $this->state = 'data';

        } else {
            /* Otherwise, if the content model flag is set to the PCDATA state,
            or if the next few characters do match that tag name, consume the
            next input character: */
            $this->char++;
            $char = $this->char();

            if (preg_match('/^[A-Za-z]$/', $char)) {
                /* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z
                Create a new end tag token, set its tag name to the lowercase version
                of the input character (add 0x0020 to the character's code point), then
                switch to the tag name state. (Don't emit the token yet; further details
                will be filled in before it is emitted.) */
                $this->token = array(
                    'name' => strtolower($char),
                    'type' => self::ENDTAG
                );

                $this->state = 'tagName';

            } elseif ($char === '>') {
                /* U+003E GREATER-THAN SIGN (>)
                Parse error. Switch to the data state. */
                $this->state = 'data';

            } elseif ($this->char === $this->EOF) {
                /* EOF
                Parse error. Emit a U+003C LESS-THAN SIGN character token and a U+002F
                SOLIDUS character token. Reconsume the EOF character in the data state. */
                $this->emitToken(
                    array(
                        'type' => self::CHARACTR,
                        'data' => '</'
                    )
                );

                $this->char--;
                $this->state = 'data';

            } else {
                /* Parse error. Switch to the bogus comment state. */
                $this->state = 'bogusComment';
            }
        }
    }

    private function tagNameState()
    {
        // Consume the next input character:
        $this->char++;
        $char = $this->character($this->char);

        if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
            /* U+0009 CHARACTER TABULATION
            U+000A LINE FEED (LF)
            U+000B LINE TABULATION
            U+000C FORM FEED (FF)
            U+0020 SPACE
            Switch to the before attribute name state. */
            $this->state = 'beforeAttributeName';

        } elseif ($char === '>') {
            /* U+003E GREATER-THAN SIGN (>)
            Emit the current tag token. Switch to the data state. */
            $this->emitToken($this->token);
            $this->state = 'data';

        } elseif ($this->char === $this->EOF) {
            /* EOF
            Parse error. Emit the current tag token. Reconsume the EOF
            character in the data state. */
            $this->emitToken($this->token);

            $this->char--;
            $this->state = 'data';

        } elseif ($char === '/') {
            /* U+002F SOLIDUS (/)
            Parse error unless this is a permitted slash. Switch to the before
            attribute name state. */
            $this->state = 'beforeAttributeName';

        } else {
            /* Anything else
            Append the current input character to the current tag token's tag name.
            Stay in the tag name state. */
            $this->token['name'] .= strtolower($char);
            $this->state = 'tagName';
        }
    }

    private function beforeAttributeNameState()
    {
        // Consume the next input character:
        $this->char++;
        $char = $this->character($this->char);

        if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
            /* U+0009 CHARACTER TABULATION
            U+000A LINE FEED (LF)
            U+000B LINE TABULATION
            U+000C FORM FEED (FF)
            U+0020 SPACE
            Stay in the before attribute name state. */
            $this->state = 'beforeAttributeName';

        } elseif ($char === '>') {
            /* U+003E GREATER-THAN SIGN (>)
            Emit the current tag token. Switch to the data state. */
            $this->emitToken($this->token);
            $this->state = 'data';

        } elseif ($char === '/') {
            /* U+002F SOLIDUS (/)
            Parse error unless this is a permitted slash. Stay in the before
            attribute name state. */
            $this->state = 'beforeAttributeName';

        } elseif ($this->char === $this->EOF) {
            /* EOF
            Parse error. Emit the current tag token. Reconsume the EOF
            character in the data state. */
            $this->emitToken($this->token);

            $this->char--;
            $this->state = 'data';

        } else {
            /* Anything else
            Start a new attribute in the current tag token. Set that attribute's
            name to the current input character, and its value to the empty string.
            Switch to the attribute name state. */
            $this->token['attr'][] = array(
                'name' => strtolower($char),
                'value' => null
            );

            $this->state = 'attributeName';
        }
    }

    private function attributeNameState()
    {
        // Consume the next input character:
        $this->char++;
        $char = $this->character($this->char);

        if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
            /* U+0009 CHARACTER TABULATION
            U+000A LINE FEED (LF)
            U+000B LINE TABULATION
            U+000C FORM FEED (FF)
            U+0020 SPACE
            Stay in the before attribute name state. */
            $this->state = 'afterAttributeName';

        } elseif ($char === '=') {
            /* U+003D EQUALS SIGN (=)
            Switch to the before attribute value state. */
            $this->state = 'beforeAttributeValue';

        } elseif ($char === '>') {
            /* U+003E GREATER-THAN SIGN (>)
            Emit the current tag token. Switch to the data state. */
            $this->emitToken($this->token);
            $this->state = 'data';

        } elseif ($char === '/' && $this->character($this->char + 1) !== '>') {
            /* U+002F SOLIDUS (/)
            Parse error unless this is a permitted slash. Switch to the before
            attribute name state. */
            $this->state = 'beforeAttributeName';

        } elseif ($this->char === $this->EOF) {
            /* EOF
            Parse error. Emit the current tag token. Reconsume the EOF
            character in the data state. */
            $this->emitToken($this->token);

            $this->char--;
            $this->state = 'data';

        } else {
            /* Anything else
            Append the current input character to the current attribute's name.
            Stay in the attribute name state. */
            $last = count($this->token['attr']) - 1;
            $this->token['attr'][$last]['name'] .= strtolower($char);

            $this->state = 'attributeName';
        }
    }

    private function afterAttributeNameState()
    {
        // Consume the next input character:
        $this->char++;
        $char = $this->character($this->char);

        if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
            /* U+0009 CHARACTER TABULATION
            U+000A LINE FEED (LF)
            U+000B LINE TABULATION
            U+000C FORM FEED (FF)
            U+0020 SPACE
            Stay in the after attribute name state. */
            $this->state = 'afterAttributeName';

        } elseif ($char === '=') {
            /* U+003D EQUALS SIGN (=)
            Switch to the before attribute value state. */
            $this->state = 'beforeAttributeValue';

        } elseif ($char === '>') {
            /* U+003E GREATER-THAN SIGN (>)
            Emit the current tag token. Switch to the data state. */
            $this->emitToken($this->token);
            $this->state = 'data';

        } elseif ($char === '/' && $this->character($this->char + 1) !== '>') {
            /* U+002F SOLIDUS (/)
            Parse error unless this is a permitted slash. Switch to the
            before attribute name state. */
            $this->state = 'beforeAttributeName';

        } elseif ($this->char === $this->EOF) {
            /* EOF
            Parse error. Emit the current tag token. Reconsume the EOF
            character in the data state. */
            $this->emitToken($this->token);

            $this->char--;
            $this->state = 'data';

        } else {
            /* Anything else
            Start a new attribute in the current tag token. Set that attribute's
            name to the current input character, and its value to the empty string.
            Switch to the attribute name state. */
            $this->token['attr'][] = array(
                'name' => strtolower($char),
                'value' => null
            );

            $this->state = 'attributeName';
        }
    }

    private function beforeAttributeValueState()
    {
        // Consume the next input character:
        $this->char++;
        $char = $this->character($this->char);

        if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
            /* U+0009 CHARACTER TABULATION
            U+000A LINE FEED (LF)
            U+000B LINE TABULATION
            U+000C FORM FEED (FF)
            U+0020 SPACE
            Stay in the before attribute value state. */
            $this->state = 'beforeAttributeValue';

        } elseif ($char === '"') {
            /* U+0022 QUOTATION MARK (")
            Switch to the attribute value (double-quoted) state. */
            $this->state = 'attributeValueDoubleQuoted';

        } elseif ($char === '&') {
            /* U+0026 AMPERSAND (&)
            Switch to the attribute value (unquoted) state and reconsume
            this input character. */
            $this->char--;
            $this->state = 'attributeValueUnquoted';

        } elseif ($char === '\'') {
            /* U+0027 APOSTROPHE (')
            Switch to the attribute value (single-quoted) state. */
            $this->state = 'attributeValueSingleQuoted';

        } elseif ($char === '>') {
            /* U+003E GREATER-THAN SIGN (>)
            Emit the current tag token. Switch to the data state. */
            $this->emitToken($this->token);
            $this->state = 'data';

        } else {
            /* Anything else
            Append the current input character to the current attribute's value.
            Switch to the attribute value (unquoted) state. */
            $last = count($this->token['attr']) - 1;
            $this->token['attr'][$last]['value'] .= $char;

            $this->state = 'attributeValueUnquoted';
        }
    }

    private function attributeValueDoubleQuotedState()
    {
        // Consume the next input character:
        $this->char++;
        $char = $this->character($this->char);

        if ($char === '"') {
            /* U+0022 QUOTATION MARK (")
            Switch to the before attribute name state. */
            $this->state = 'beforeAttributeName';

        } elseif ($char === '&') {
            /* U+0026 AMPERSAND (&)
            Switch to the entity in attribute value state. */
            $this->entityInAttributeValueState('double');

        } elseif ($this->char === $this->EOF) {
            /* EOF
            Parse error. Emit the current tag token. Reconsume the character
            in the data state. */
            $this->emitToken($this->token);

            $this->char--;
            $this->state = 'data';

        } else {
            /* Anything else
            Append the current input character to the current attribute's value.
            Stay in the attribute value (double-quoted) state. */
            $last = count($this->token['attr']) - 1;
            $this->token['attr'][$last]['value'] .= $char;

            $this->state = 'attributeValueDoubleQuoted';
        }
    }

    private function attributeValueSingleQuotedState()
    {
        // Consume the next input character:
        $this->char++;
        $char = $this->character($this->char);

        if ($char === '\'') {
            /* U+0022 QUOTATION MARK (')
            Switch to the before attribute name state. */
            $this->state = 'beforeAttributeName';

        } elseif ($char === '&') {
            /* U+0026 AMPERSAND (&)
            Switch to the entity in attribute value state. */
            $this->entityInAttributeValueState('single');

        } elseif ($this->char === $this->EOF) {
            /* EOF
            Parse error. Emit the current tag token. Reconsume the character
            in the data state. */
            $this->emitToken($this->token);

            $this->char--;
            $this->state = 'data';

        } else {
            /* Anything else
            Append the current input character to the current attribute's value.
            Stay in the attribute value (single-quoted) state. */
            $last = count($this->token['attr']) - 1;
            $this->token['attr'][$last]['value'] .= $char;

            $this->state = 'attributeValueSingleQuoted';
        }
    }

    private function attributeValueUnquotedState()
    {
        // Consume the next input character:
        $this->char++;
        $char = $this->character($this->char);

        if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
            /* U+0009 CHARACTER TABULATION
            U+000A LINE FEED (LF)
            U+000B LINE TABULATION
            U+000C FORM FEED (FF)
            U+0020 SPACE
            Switch to the before attribute name state. */
            $this->state = 'beforeAttributeName';

        } elseif ($char === '&') {
            /* U+0026 AMPERSAND (&)
            Switch to the entity in attribute value state. */
            $this->entityInAttributeValueState();

        } elseif ($char === '>') {
            /* U+003E GREATER-THAN SIGN (>)
            Emit the current tag token. Switch to the data state. */
            $this->emitToken($this->token);
            $this->state = 'data';

        } else {
            /* Anything else
            Append the current input character to the current attribute's value.
            Stay in the attribute value (unquoted) state. */
            $last = count($this->token['attr']) - 1;
            $this->token['attr'][$last]['value'] .= $char;

            $this->state = 'attributeValueUnquoted';
        }
    }

    private function entityInAttributeValueState()
    {
        // Attempt to consume an entity.
        $entity = $this->entity();

        // If nothing is returned, append a U+0026 AMPERSAND character to the
        // current attribute's value. Otherwise, emit the character token that
        // was returned.
        $char = (!$entity)
            ? '&'
            : $entity;

        $last = count($this->token['attr']) - 1;
        $this->token['attr'][$last]['value'] .= $char;
    }

    private function bogusCommentState()
    {
        /* Consume every character up to the first U+003E GREATER-THAN SIGN
        character (>) or the end of the file (EOF), whichever comes first. Emit
        a comment token whose data is the concatenation of all the characters
        starting from and including the character that caused the state machine
        to switch into the bogus comment state, up to and including the last
        consumed character before the U+003E character, if any, or up to the
        end of the file otherwise. (If the comment was started by the end of
        the file (EOF), the token is empty.) */
        $data = $this->characters('^>', $this->char);
        $this->emitToken(
            array(
                'data' => $data,
                'type' => self::COMMENT
            )
        );

        $this->char += strlen($data);

        /* Switch to the data state. */
        $this->state = 'data';

        /* If the end of the file was reached, reconsume the EOF character. */
        if ($this->char === $this->EOF) {
            $this->char = $this->EOF - 1;
        }
    }

    private function markupDeclarationOpenState()
    {
        /* If the next two characters are both U+002D HYPHEN-MINUS (-)
        characters, consume those two characters, create a comment token whose
        data is the empty string, and switch to the comment state. */
        if ($this->character($this->char + 1, 2) === '--') {
            $this->char += 2;
            $this->state = 'comment';
            $this->token = array(
                'data' => null,
                'type' => self::COMMENT
            );

            /* Otherwise if the next seven chacacters are a case-insensitive match
            for the word "DOCTYPE", then consume those characters and switch to the
            DOCTYPE state. */
        } elseif (strtolower($this->character($this->char + 1, 7)) === 'doctype') {
            $this->char += 7;
            $this->state = 'doctype';

            /* Otherwise, is is a parse error. Switch to the bogus comment state.
            The next character that is consumed, if any, is the first character
            that will be in the comment. */
        } else {
            $this->char++;
            $this->state = 'bogusComment';
        }
    }

    private function commentState()
    {
        /* Consume the next input character: */
        $this->char++;
        $char = $this->char();

        /* U+002D HYPHEN-MINUS (-) */
        if ($char === '-') {
            /* Switch to the comment dash state  */
            $this->state = 'commentDash';

            /* EOF */
        } elseif ($this->char === $this->EOF) {
            /* Parse error. Emit the comment token. Reconsume the EOF character
            in the data state. */
            $this->emitToken($this->token);
            $this->char--;
            $this->state = 'data';

            /* Anything else */
        } else {
            /* Append the input character to the comment token's data. Stay in
            the comment state. */
            $this->token['data'] .= $char;
        }
    }

    private function commentDashState()
    {
        /* Consume the next input character: */
        $this->char++;
        $char = $this->char();

        /* U+002D HYPHEN-MINUS (-) */
        if ($char === '-') {
            /* Switch to the comment end state  */
            $this->state = 'commentEnd';

            /* EOF */
        } elseif ($this->char === $this->EOF) {
            /* Parse error. Emit the comment token. Reconsume the EOF character
            in the data state. */
            $this->emitToken($this->token);
            $this->char--;
            $this->state = 'data';

            /* Anything else */
        } else {
            /* Append a U+002D HYPHEN-MINUS (-) character and the input
            character to the comment token's data. Switch to the comment state. */
            $this->token['data'] .= '-' . $char;
            $this->state = 'comment';
        }
    }

    private function commentEndState()
    {
        /* Consume the next input character: */
        $this->char++;
        $char = $this->char();

        if ($char === '>') {
            $this->emitToken($this->token);
            $this->state = 'data';

        } elseif ($char === '-') {
            $this->token['data'] .= '-';

        } elseif ($this->char === $this->EOF) {
            $this->emitToken($this->token);
            $this->char--;
            $this->state = 'data';

        } else {
            $this->token['data'] .= '--' . $char;
            $this->state = 'comment';
        }
    }

    private function doctypeState()
    {
        /* Consume the next input character: */
        $this->char++;
        $char = $this->char();

        if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
            $this->state = 'beforeDoctypeName';

        } else {
            $this->char--;
            $this->state = 'beforeDoctypeName';
        }
    }

    private function beforeDoctypeNameState()
    {
        /* Consume the next input character: */
        $this->char++;
        $char = $this->char();

        if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
            // Stay in the before DOCTYPE name state.

        } elseif (preg_match('/^[a-z]$/', $char)) {
            $this->token = array(
                'name' => strtoupper($char),
                'type' => self::DOCTYPE,
                'error' => true
            );

            $this->state = 'doctypeName';

        } elseif ($char === '>') {
            $this->emitToken(
                array(
                    'name' => null,
                    'type' => self::DOCTYPE,
                    'error' => true
                )
            );

            $this->state = 'data';

        } elseif ($this->char === $this->EOF) {
            $this->emitToken(
                array(
                    'name' => null,
                    'type' => self::DOCTYPE,
                    'error' => true
                )
            );

            $this->char--;
            $this->state = 'data';

        } else {
            $this->token = array(
                'name' => $char,
                'type' => self::DOCTYPE,
                'error' => true
            );

            $this->state = 'doctypeName';
        }
    }

    private function doctypeNameState()
    {
        /* Consume the next input character: */
        $this->char++;
        $char = $this->char();

        if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
            $this->state = 'AfterDoctypeName';

        } elseif ($char === '>') {
            $this->emitToken($this->token);
            $this->state = 'data';

        } elseif (preg_match('/^[a-z]$/', $char)) {
            $this->token['name'] .= strtoupper($char);

        } elseif ($this->char === $this->EOF) {
            $this->emitToken($this->token);
            $this->char--;
            $this->state = 'data';

        } else {
            $this->token['name'] .= $char;
        }

        $this->token['error'] = ($this->token['name'] === 'HTML')
            ? false
            : true;
    }

    private function afterDoctypeNameState()
    {
        /* Consume the next input character: */
        $this->char++;
        $char = $this->char();

        if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
            // Stay in the DOCTYPE name state.

        } elseif ($char === '>') {
            $this->emitToken($this->token);
            $this->state = 'data';

        } elseif ($this->char === $this->EOF) {
            $this->emitToken($this->token);
            $this->char--;
            $this->state = 'data';

        } else {
            $this->token['error'] = true;
            $this->state = 'bogusDoctype';
        }
    }

    private function bogusDoctypeState()
    {
        /* Consume the next input character: */
        $this->char++;
        $char = $this->char();

        if ($char === '>') {
            $this->emitToken($this->token);
            $this->state = 'data';

        } elseif ($this->char === $this->EOF) {
            $this->emitToken($this->token);
            $this->char--;
            $this->state = 'data';

        } else {
            // Stay in the bogus DOCTYPE state.
        }
    }

    private function entity()
    {
        $start = $this->char;

        // This section defines how to consume an entity. This definition is
        // used when parsing entities in text and in attributes.

        // The behaviour depends on the identity of the next character (the
        // one immediately after the U+0026 AMPERSAND character):

        switch ($this->character($this->char + 1)) {
            // U+0023 NUMBER SIGN (#)
            case '#':

                // The behaviour further depends on the character after the
                // U+0023 NUMBER SIGN:
                switch ($this->character($this->char + 1)) {
                    // U+0078 LATIN SMALL LETTER X
                    // U+0058 LATIN CAPITAL LETTER X
                    case 'x':
                    case 'X':
                        // Follow the steps below, but using the range of
                        // characters U+0030 DIGIT ZERO through to U+0039 DIGIT
                        // NINE, U+0061 LATIN SMALL LETTER A through to U+0066
                        // LATIN SMALL LETTER F, and U+0041 LATIN CAPITAL LETTER
                        // A, through to U+0046 LATIN CAPITAL LETTER F (in other
                        // words, 0-9, A-F, a-f).
                        $char = 1;
                        $char_class = '0-9A-Fa-f';
                        break;

                    // Anything else
                    default:
                        // Follow the steps below, but using the range of
                        // characters U+0030 DIGIT ZERO through to U+0039 DIGIT
                        // NINE (i.e. just 0-9).
                        $char = 0;
                        $char_class = '0-9';
                        break;
                }

                // Consume as many characters as match the range of characters
                // given above.
                $this->char++;
                $e_name = $this->characters($char_class, $this->char + $char + 1);
                $entity = $this->character($start, $this->char);
                $cond = strlen($e_name) > 0;

                // The rest of the parsing happens below.
                break;

            // Anything else
            default:
                // Consume the maximum number of characters possible, with the
                // consumed characters case-sensitively matching one of the
                // identifiers in the first column of the entities table.

                $e_name = $this->characters('0-9A-Za-z;', $this->char + 1);
                $len = strlen($e_name);

                for ($c = 1; $c <= $len; $c++) {
                    $id = substr($e_name, 0, $c);
                    $this->char++;

                    if (in_array($id, $this->entities)) {
                        if ($e_name[$c - 1] !== ';') {
                            if ($c < $len && $e_name[$c] == ';') {
                                $this->char++; // consume extra semicolon
                            }
                        }
                        $entity = $id;
                        break;
                    }
                }

                $cond = isset($entity);
                // The rest of the parsing happens below.
                break;
        }

        if (!$cond) {
            // If no match can be made, then this is a parse error. No
            // characters are consumed, and nothing is returned.
            $this->char = $start;
            return false;
        }

        // Return a character token for the character corresponding to the
        // entity name (as given by the second column of the entities table).
        return html_entity_decode('&' . rtrim($entity, ';') . ';', ENT_QUOTES, 'UTF-8');
    }

    private function emitToken($token)
    {
        $emit = $this->tree->emitToken($token);

        if (is_int($emit)) {
            $this->content_model = $emit;

        } elseif ($token['type'] === self::ENDTAG) {
            $this->content_model = self::PCDATA;
        }
    }

    private function EOF()
    {
        $this->state = null;
        $this->tree->emitToken(
            array(
                'type' => self::EOF
            )
        );
    }
}

class HTML5TreeConstructer
{
    public $stack = array();

    private $phase;
    private $mode;
    private $dom;
    private $foster_parent = null;
    private $a_formatting = array();

    private $head_pointer = null;
    private $form_pointer = null;

    private $scoping = array('button', 'caption', 'html', 'marquee', 'object', 'table', 'td', 'th');
    private $formatting = array(
        'a',
        'b',
        'big',
        'em',
        'font',
        'i',
        'nobr',
        's',
        'small',
        'strike',
        'strong',
        'tt',
        'u'
    );
    private $special = array(
        'address',
        'area',
        'base',
        'basefont',
        'bgsound',
        'blockquote',
        'body',
        'br',
        'center',
        'col',
        'colgroup',
        'dd',
        'dir',
        'div',
        'dl',
        'dt',
        'embed',
        'fieldset',
        'form',
        'frame',
        'frameset',
        'h1',
        'h2',
        'h3',
        'h4',
        'h5',
        'h6',
        'head',
        'hr',
        'iframe',
        'image',
        'img',
        'input',
        'isindex',
        'li',
        'link',
        'listing',
        'menu',
        'meta',
        'noembed',
        'noframes',
        'noscript',
        'ol',
        'optgroup',
        'option',
        'p',
        'param',
        'plaintext',
        'pre',
        'script',
        'select',
        'spacer',
        'style',
        'tbody',
        'textarea',
        'tfoot',
        'thead',
        'title',
        'tr',
        'ul',
        'wbr'
    );

    // The different phases.
    const INIT_PHASE = 0;
    const ROOT_PHASE = 1;
    const MAIN_PHASE = 2;
    const END_PHASE = 3;

    // The different insertion modes for the main phase.
    const BEFOR_HEAD = 0;
    const IN_HEAD = 1;
    const AFTER_HEAD = 2;
    const IN_BODY = 3;
    const IN_TABLE = 4;
    const IN_CAPTION = 5;
    const IN_CGROUP = 6;
    const IN_TBODY = 7;
    const IN_ROW = 8;
    const IN_CELL = 9;
    const IN_SELECT = 10;
    const AFTER_BODY = 11;
    const IN_FRAME = 12;
    const AFTR_FRAME = 13;

    // The different types of elements.
    const SPECIAL = 0;
    const SCOPING = 1;
    const FORMATTING = 2;
    const PHRASING = 3;

    const MARKER = 0;

    public function __construct()
    {
        $this->phase = self::INIT_PHASE;
        $this->mode = self::BEFOR_HEAD;
        $this->dom = new DOMDocument;

        $this->dom->encoding = 'UTF-8';
        $this->dom->preserveWhiteSpace = true;
        $this->dom->substituteEntities = true;
        $this->dom->strictErrorChecking = false;
    }

    // Process tag tokens
    public function emitToken($token)
    {
        switch ($this->phase) {
            case self::INIT_PHASE:
                return $this->initPhase($token);
                break;
            case self::ROOT_PHASE:
                return $this->rootElementPhase($token);
                break;
            case self::MAIN_PHASE:
                return $this->mainPhase($token);
                break;
            case self::END_PHASE :
                return $this->trailingEndPhase($token);
                break;
        }
    }

    private function initPhase($token)
    {
        /* Initially, the tree construction stage must handle each token
        emitted from the tokenisation stage as follows: */

        /* A DOCTYPE token that is marked as being in error
        A comment token
        A start tag token
        An end tag token
        A character token that is not one of one of U+0009 CHARACTER TABULATION,
            U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
            or U+0020 SPACE
        An end-of-file token */
        if ((isset($token['error']) && $token['error']) ||
            $token['type'] === HTML5::COMMENT ||
            $token['type'] === HTML5::STARTTAG ||
            $token['type'] === HTML5::ENDTAG ||
            $token['type'] === HTML5::EOF ||
            ($token['type'] === HTML5::CHARACTR && isset($token['data']) &&
                !preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']))
        ) {
            /* This specification does not define how to handle this case. In
            particular, user agents may ignore the entirety of this specification
            altogether for such documents, and instead invoke special parse modes
            with a greater emphasis on backwards compatibility. */

            $this->phase = self::ROOT_PHASE;
            return $this->rootElementPhase($token);

            /* A DOCTYPE token marked as being correct */
        } elseif (isset($token['error']) && !$token['error']) {
            /* Append a DocumentType node to the Document  node, with the name
            attribute set to the name given in the DOCTYPE token (which will be
            "HTML"), and the other attributes specific to DocumentType objects
            set to null, empty lists, or the empty string as appropriate. */
            $doctype = new DOMDocumentType(null, null, 'HTML');

            /* Then, switch to the root element phase of the tree construction
            stage. */
            $this->phase = self::ROOT_PHASE;

            /* A character token that is one of one of U+0009 CHARACTER TABULATION,
            U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
            or U+0020 SPACE */
        } elseif (isset($token['data']) && preg_match(
                '/^[\t\n\x0b\x0c ]+$/',
                $token['data']
            )
        ) {
            /* Append that character  to the Document node. */
            $text = $this->dom->createTextNode($token['data']);
            $this->dom->appendChild($text);
        }
    }

    private function rootElementPhase($token)
    {
        /* After the initial phase, as each token is emitted from the tokenisation
        stage, it must be processed as described in this section. */

        /* A DOCTYPE token */
        if ($token['type'] === HTML5::DOCTYPE) {
            // Parse error. Ignore the token.

            /* A comment token */
        } elseif ($token['type'] === HTML5::COMMENT) {
            /* Append a Comment node to the Document object with the data
            attribute set to the data given in the comment token. */
            $comment = $this->dom->createComment($token['data']);
            $this->dom->appendChild($comment);

            /* A character token that is one of one of U+0009 CHARACTER TABULATION,
            U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
            or U+0020 SPACE */
        } elseif ($token['type'] === HTML5::CHARACTR &&
            preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])
        ) {
            /* Append that character  to the Document node. */
            $text = $this->dom->createTextNode($token['data']);
            $this->dom->appendChild($text);

            /* A character token that is not one of U+0009 CHARACTER TABULATION,
                U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED
                (FF), or U+0020 SPACE
            A start tag token
            An end tag token
            An end-of-file token */
        } elseif (($token['type'] === HTML5::CHARACTR &&
                !preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) ||
            $token['type'] === HTML5::STARTTAG ||
            $token['type'] === HTML5::ENDTAG ||
            $token['type'] === HTML5::EOF
        ) {
            /* Create an HTMLElement node with the tag name html, in the HTML
            namespace. Append it to the Document object. Switch to the main
            phase and reprocess the current token. */
            $html = $this->dom->createElement('html');
            $this->dom->appendChild($html);
            $this->stack[] = $html;

            $this->phase = self::MAIN_PHASE;
            return $this->mainPhase($token);
        }
    }

    private function mainPhase($token)
    {
        /* Tokens in the main phase must be handled as follows: */

        /* A DOCTYPE token */
        if ($token['type'] === HTML5::DOCTYPE) {
            // Parse error. Ignore the token.

            /* A start tag token with the tag name "html" */
        } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'html') {
            /* If this start tag token was not the first start tag token, then
            it is a parse error. */

            /* For each attribute on the token, check to see if the attribute
            is already present on the top element of the stack of open elements.
            If it is not, add the attribute and its corresponding value to that
            element. */
            foreach ($token['attr'] as $attr) {
                if (!$this->stack[0]->hasAttribute($attr['name'])) {
                    $this->stack[0]->setAttribute($attr['name'], $attr['value']);
                }
            }

            /* An end-of-file token */
        } elseif ($token['type'] === HTML5::EOF) {
            /* Generate implied end tags. */
            $this->generateImpliedEndTags();

            /* Anything else. */
        } else {
            /* Depends on the insertion mode: */
            switch ($this->mode) {
                case self::BEFOR_HEAD:
                    return $this->beforeHead($token);
                    break;
                case self::IN_HEAD:
                    return $this->inHead($token);
                    break;
                case self::AFTER_HEAD:
                    return $this->afterHead($token);
                    break;
                case self::IN_BODY:
                    return $this->inBody($token);
                    break;
                case self::IN_TABLE:
                    return $this->inTable($token);
                    break;
                case self::IN_CAPTION:
                    return $this->inCaption($token);
                    break;
                case self::IN_CGROUP:
                    return $this->inColumnGroup($token);
                    break;
                case self::IN_TBODY:
                    return $this->inTableBody($token);
                    break;
                case self::IN_ROW:
                    return $this->inRow($token);
                    break;
                case self::IN_CELL:
                    return $this->inCell($token);
                    break;
                case self::IN_SELECT:
                    return $this->inSelect($token);
                    break;
                case self::AFTER_BODY:
                    return $this->afterBody($token);
                    break;
                case self::IN_FRAME:
                    return $this->inFrameset($token);
                    break;
                case self::AFTR_FRAME:
                    return $this->afterFrameset($token);
                    break;
                case self::END_PHASE:
                    return $this->trailingEndPhase($token);
                    break;
            }
        }
    }

    private function beforeHead($token)
    {
        /* Handle the token as follows: */

        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
        or U+0020 SPACE */
        if ($token['type'] === HTML5::CHARACTR &&
            preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])
        ) {
            /* Append the character to the current node. */
            $this->insertText($token['data']);

            /* A comment token */
        } elseif ($token['type'] === HTML5::COMMENT) {
            /* Append a Comment node to the current node with the data attribute
            set to the data given in the comment token. */
            $this->insertComment($token['data']);

            /* A start tag token with the tag name "head" */
        } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'head') {
            /* Create an element for the token, append the new element to the
            current node and push it onto the stack of open elements. */
            $element = $this->insertElement($token);

            /* Set the head element pointer to this new element node. */
            $this->head_pointer = $element;

            /* Change the insertion mode to "in head". */
            $this->mode = self::IN_HEAD;

            /* A start tag token whose tag name is one of: "base", "link", "meta",
            "script", "style", "title". Or an end tag with the tag name "html".
            Or a character token that is not one of U+0009 CHARACTER TABULATION,
            U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
            or U+0020 SPACE. Or any other start tag token */
        } elseif ($token['type'] === HTML5::STARTTAG ||
            ($token['type'] === HTML5::ENDTAG && $token['name'] === 'html') ||
            ($token['type'] === HTML5::CHARACTR && !preg_match(
                    '/^[\t\n\x0b\x0c ]$/',
                    $token['data']
                ))
        ) {
            /* Act as if a start tag token with the tag name "head" and no
            attributes had been seen, then reprocess the current token. */
            $this->beforeHead(
                array(
                    'name' => 'head',
                    'type' => HTML5::STARTTAG,
                    'attr' => array()
                )
            );

            return $this->inHead($token);

            /* Any other end tag */
        } elseif ($token['type'] === HTML5::ENDTAG) {
            /* Parse error. Ignore the token. */
        }
    }

    private function inHead($token)
    {
        /* Handle the token as follows: */

        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
        or U+0020 SPACE.

        THIS DIFFERS FROM THE SPEC: If the current node is either a title, style
        or script element, append the character to the current node regardless
        of its content. */
        if (($token['type'] === HTML5::CHARACTR &&
                preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || (
                $token['type'] === HTML5::CHARACTR && in_array(
                    end($this->stack)->nodeName,
                    array('title', 'style', 'script')
                ))
        ) {
            /* Append the character to the current node. */
            $this->insertText($token['data']);

            /* A comment token */
        } elseif ($token['type'] === HTML5::COMMENT) {
            /* Append a Comment node to the current node with the data attribute
            set to the data given in the comment token. */
            $this->insertComment($token['data']);

        } elseif ($token['type'] === HTML5::ENDTAG &&
            in_array($token['name'], array('title', 'style', 'script'))
        ) {
            array_pop($this->stack);
            return HTML5::PCDATA;

            /* A start tag with the tag name "title" */
        } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'title') {
            /* Create an element for the token and append the new element to the
            node pointed to by the head element pointer, or, if that is null
            (innerHTML case), to the current node. */
            if ($this->head_pointer !== null) {
                $element = $this->insertElement($token, false);
                $this->head_pointer->appendChild($element);

            } else {
                $element = $this->insertElement($token);
            }

            /* Switch the tokeniser's content model flag  to the RCDATA state. */
            return HTML5::RCDATA;

            /* A start tag with the tag name "style" */
        } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'style') {
            /* Create an element for the token and append the new element to the
            node pointed to by the head element pointer, or, if that is null
            (innerHTML case), to the current node. */
            if ($this->head_pointer !== null) {
                $element = $this->insertElement($token, false);
                $this->head_pointer->appendChild($element);

            } else {
                $this->insertElement($token);
            }

            /* Switch the tokeniser's content model flag  to the CDATA state. */
            return HTML5::CDATA;

            /* A start tag with the tag name "script" */
        } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'script') {
            /* Create an element for the token. */
            $element = $this->insertElement($token, false);
            $this->head_pointer->appendChild($element);

            /* Switch the tokeniser's content model flag  to the CDATA state. */
            return HTML5::CDATA;

            /* A start tag with the tag name "base", "link", or "meta" */
        } elseif ($token['type'] === HTML5::STARTTAG && in_array(
                $token['name'],
                array('base', 'link', 'meta')
            )
        ) {
            /* Create an element for the token and append the new element to the
            node pointed to by the head element pointer, or, if that is null
            (innerHTML case), to the current node. */
            if ($this->head_pointer !== null) {
                $element = $this->insertElement($token, false);
                $this->head_pointer->appendChild($element);
                array_pop($this->stack);

            } else {
                $this->insertElement($token);
            }

            /* An end tag with the tag name "head" */
        } elseif ($token['type'] === HTML5::ENDTAG && $token['name'] === 'head') {
            /* If the current node is a head element, pop the current node off
            the stack of open elements. */
            if ($this->head_pointer->isSameNode(end($this->stack))) {
                array_pop($this->stack);

                /* Otherwise, this is a parse error. */
            } else {
                // k
            }

            /* Change the insertion mode to "after head". */
            $this->mode = self::AFTER_HEAD;

            /* A start tag with the tag name "head" or an end tag except "html". */
        } elseif (($token['type'] === HTML5::STARTTAG && $token['name'] === 'head') ||
            ($token['type'] === HTML5::ENDTAG && $token['name'] !== 'html')
        ) {
            // Parse error. Ignore the token.

            /* Anything else */
        } else {
            /* If the current node is a head element, act as if an end tag
            token with the tag name "head" had been seen. */
            if ($this->head_pointer->isSameNode(end($this->stack))) {
                $this->inHead(
                    array(
                        'name' => 'head',
                        'type' => HTML5::ENDTAG
                    )
                );

                /* Otherwise, change the insertion mode to "after head". */
            } else {
                $this->mode = self::AFTER_HEAD;
            }

            /* Then, reprocess the current token. */
            return $this->afterHead($token);
        }
    }

    private function afterHead($token)
    {
        /* Handle the token as follows: */

        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
        or U+0020 SPACE */
        if ($token['type'] === HTML5::CHARACTR &&
            preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])
        ) {
            /* Append the character to the current node. */
            $this->insertText($token['data']);

            /* A comment token */
        } elseif ($token['type'] === HTML5::COMMENT) {
            /* Append a Comment node to the current node with the data attribute
            set to the data given in the comment token. */
            $this->insertComment($token['data']);

            /* A start tag token with the tag name "body" */
        } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'body') {
            /* Insert a body element for the token. */
            $this->insertElement($token);

            /* Change the insertion mode to "in body". */
            $this->mode = self::IN_BODY;

            /* A start tag token with the tag name "frameset" */
        } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'frameset') {
            /* Insert a frameset element for the token. */
            $this->insertElement($token);

            /* Change the insertion mode to "in frameset". */
            $this->mode = self::IN_FRAME;

            /* A start tag token whose tag name is one of: "base", "link", "meta",
            "script", "style", "title" */
        } elseif ($token['type'] === HTML5::STARTTAG && in_array(
                $token['name'],
                array('base', 'link', 'meta', 'script', 'style', 'title')
            )
        ) {
            /* Parse error. Switch the insertion mode back to "in head" and
            reprocess the token. */
            $this->mode = self::IN_HEAD;
            return $this->inHead($token);

            /* Anything else */
        } else {
            /* Act as if a start tag token with the tag name "body" and no
            attributes had been seen, and then reprocess the current token. */
            $this->afterHead(
                array(
                    'name' => 'body',
                    'type' => HTML5::STARTTAG,
                    'attr' => array()
                )
            );

            return $this->inBody($token);
        }
    }

    private function inBody($token)
    {
        /* Handle the token as follows: */

        switch ($token['type']) {
            /* A character token */
            case HTML5::CHARACTR:
                /* Reconstruct the active formatting elements, if any. */
                $this->reconstructActiveFormattingElements();

                /* Append the token's character to the current node. */
                $this->insertText($token['data']);
                break;

            /* A comment token */
            case HTML5::COMMENT:
                /* Append a Comment node to the current node with the data
                attribute set to the data given in the comment token. */
                $this->insertComment($token['data']);
                break;

            case HTML5::STARTTAG:
                switch ($token['name']) {
                    /* A start tag token whose tag name is one of: "script",
                    "style" */
                    case 'script':
                    case 'style':
                        /* Process the token as if the insertion mode had been "in
                        head". */
                        return $this->inHead($token);
                        break;

                    /* A start tag token whose tag name is one of: "base", "link",
                    "meta", "title" */
                    case 'base':
                    case 'link':
                    case 'meta':
                    case 'title':
                        /* Parse error. Process the token as if the insertion mode
                        had    been "in head". */
                        return $this->inHead($token);
                        break;

                    /* A start tag token with the tag name "body" */
                    case 'body':
                        /* Parse error. If the second element on the stack of open
                        elements is not a body element, or, if the stack of open
                        elements has only one node on it, then ignore the token.
                        (innerHTML case) */
                        if (count($this->stack) === 1 || $this->stack[1]->nodeName !== 'body') {
                            // Ignore

                            /* Otherwise, for each attribute on the token, check to see
                            if the attribute is already present on the body element (the
                            second element)    on the stack of open elements. If it is not,
                            add the attribute and its corresponding value to that
                            element. */
                        } else {
                            foreach ($token['attr'] as $attr) {
                                if (!$this->stack[1]->hasAttribute($attr['name'])) {
                                    $this->stack[1]->setAttribute($attr['name'], $attr['value']);
                                }
                            }
                        }
                        break;

                    /* A start tag whose tag name is one of: "address",
                    "blockquote", "center", "dir", "div", "dl", "fieldset",
                    "listing", "menu", "ol", "p", "ul" */
                    case 'address':
                    case 'blockquote':
                    case 'center':
                    case 'dir':
                    case 'div':
                    case 'dl':
                    case 'fieldset':
                    case 'listing':
                    case 'menu':
                    case 'ol':
                    case 'p':
                    case 'ul':
                        /* If the stack of open elements has a p element in scope,
                        then act as if an end tag with the tag name p had been
                        seen. */
                        if ($this->elementInScope('p')) {
                            $this->emitToken(
                                array(
                                    'name' => 'p',
                                    'type' => HTML5::ENDTAG
                                )
                            );
                        }

                        /* Insert an HTML element for the token. */
                        $this->insertElement($token);
                        break;

                    /* A start tag whose tag name is "form" */
                    case 'form':
                        /* If the form element pointer is not null, ignore the
                        token with a parse error. */
                        if ($this->form_pointer !== null) {
                            // Ignore.

                            /* Otherwise: */
                        } else {
                            /* If the stack of open elements has a p element in
                            scope, then act as if an end tag with the tag name p
                            had been seen. */
                            if ($this->elementInScope('p')) {
                                $this->emitToken(
                                    array(
                                        'name' => 'p',
                                        'type' => HTML5::ENDTAG
                                    )
                                );
                            }

                            /* Insert an HTML element for the token, and set the
                            form element pointer to point to the element created. */
                            $element = $this->insertElement($token);
                            $this->form_pointer = $element;
                        }
                        break;

                    /* A start tag whose tag name is "li", "dd" or "dt" */
                    case 'li':
                    case 'dd':
                    case 'dt':
                        /* If the stack of open elements has a p  element in scope,
                        then act as if an end tag with the tag name p had been
                        seen. */
                        if ($this->elementInScope('p')) {
                            $this->emitToken(
                                array(
                                    'name' => 'p',
                                    'type' => HTML5::ENDTAG
                                )
                            );
                        }

                        $stack_length = count($this->stack) - 1;

                        for ($n = $stack_length; 0 <= $n; $n--) {
                            /* 1. Initialise node to be the current node (the
                            bottommost node of the stack). */
                            $stop = false;
                            $node = $this->stack[$n];
                            $cat = $this->getElementCategory($node->tagName);

                            /* 2. If node is an li, dd or dt element, then pop all
                            the    nodes from the current node up to node, including
                            node, then stop this algorithm. */
                            if ($token['name'] === $node->tagName || ($token['name'] !== 'li'
                                    && ($node->tagName === 'dd' || $node->tagName === 'dt'))
                            ) {
                                for ($x = $stack_length; $x >= $n; $x--) {
                                    array_pop($this->stack);
                                }

                                break;
                            }

                            /* 3. If node is not in the formatting category, and is
                            not    in the phrasing category, and is not an address or
                            div element, then stop this algorithm. */
                            if ($cat !== self::FORMATTING && $cat !== self::PHRASING &&
                                $node->tagName !== 'address' && $node->tagName !== 'div'
                            ) {
                                break;
                            }
                        }

                        /* Finally, insert an HTML element with the same tag
                        name as the    token's. */
                        $this->insertElement($token);
                        break;

                    /* A start tag token whose tag name is "plaintext" */
                    case 'plaintext':
                        /* If the stack of open elements has a p  element in scope,
                        then act as if an end tag with the tag name p had been
                        seen. */
                        if ($this->elementInScope('p')) {
                            $this->emitToken(
                                array(
                                    'name' => 'p',
                                    'type' => HTML5::ENDTAG
                                )
                            );
                        }

                        /* Insert an HTML element for the token. */
                        $this->insertElement($token);

                        return HTML5::PLAINTEXT;
                        break;

                    /* A start tag whose tag name is one of: "h1", "h2", "h3", "h4",
                    "h5", "h6" */
                    case 'h1':
                    case 'h2':
                    case 'h3':
                    case 'h4':
                    case 'h5':
                    case 'h6':
                        /* If the stack of open elements has a p  element in scope,
                        then act as if an end tag with the tag name p had been seen. */
                        if ($this->elementInScope('p')) {
                            $this->emitToken(
                                array(
                                    'name' => 'p',
                                    'type' => HTML5::ENDTAG
                                )
                            );
                        }

                        /* If the stack of open elements has in scope an element whose
                        tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then
                        this is a parse error; pop elements from the stack until an
                        element with one of those tag names has been popped from the
                        stack. */
                        while ($this->elementInScope(array('h1', 'h2', 'h3', 'h4', 'h5', 'h6'))) {
                            array_pop($this->stack);
                        }

                        /* Insert an HTML element for the token. */
                        $this->insertElement($token);
                        break;

                    /* A start tag whose tag name is "a" */
                    case 'a':
                        /* If the list of active formatting elements contains
                        an element whose tag name is "a" between the end of the
                        list and the last marker on the list (or the start of
                        the list if there is no marker on the list), then this
                        is a parse error; act as if an end tag with the tag name
                        "a" had been seen, then remove that element from the list
                        of active formatting elements and the stack of open
                        elements if the end tag didn't already remove it (it
                        might not have if the element is not in table scope). */
                        $leng = count($this->a_formatting);

                        for ($n = $leng - 1; $n >= 0; $n--) {
                            if ($this->a_formatting[$n] === self::MARKER) {
                                break;

                            } elseif ($this->a_formatting[$n]->nodeName === 'a') {
                                $this->emitToken(
                                    array(
                                        'name' => 'a',
                                        'type' => HTML5::ENDTAG
                                    )
                                );
                                break;
                            }
                        }

                        /* Reconstruct the active formatting elements, if any. */
                        $this->reconstructActiveFormattingElements();

                        /* Insert an HTML element for the token. */
                        $el = $this->insertElement($token);

                        /* Add that element to the list of active formatting
                        elements. */
                        $this->a_formatting[] = $el;
                        break;

                    /* A start tag whose tag name is one of: "b", "big", "em", "font",
                    "i", "nobr", "s", "small", "strike", "strong", "tt", "u" */
                    case 'b':
                    case 'big':
                    case 'em':
                    case 'font':
                    case 'i':
                    case 'nobr':
                    case 's':
                    case 'small':
                    case 'strike':
                    case 'strong':
                    case 'tt':
                    case 'u':
                        /* Reconstruct the active formatting elements, if any. */
                        $this->reconstructActiveFormattingElements();

                        /* Insert an HTML element for the token. */
                        $el = $this->insertElement($token);

                        /* Add that element to the list of active formatting
                        elements. */
                        $this->a_formatting[] = $el;
                        break;

                    /* A start tag token whose tag name is "button" */
                    case 'button':
                        /* If the stack of open elements has a button element in scope,
                        then this is a parse error; act as if an end tag with the tag
                        name "button" had been seen, then reprocess the token. (We don't
                        do that. Unnecessary.) */
                        if ($this->elementInScope('button')) {
                            $this->inBody(
                                array(
                                    'name' => 'button',
                                    'type' => HTML5::ENDTAG
                                )
                            );
                        }

                        /* Reconstruct the active formatting elements, if any. */
                        $this->reconstructActiveFormattingElements();

                        /* Insert an HTML element for the token. */
                        $this->insertElement($token);

                        /* Insert a marker at the end of the list of active
                        formatting elements. */
                        $this->a_formatting[] = self::MARKER;
                        break;

                    /* A start tag token whose tag name is one of: "marquee", "object" */
                    case 'marquee':
                    case 'object':
                        /* Reconstruct the active formatting elements, if any. */
                        $this->reconstructActiveFormattingElements();

                        /* Insert an HTML element for the token. */
                        $this->insertElement($token);

                        /* Insert a marker at the end of the list of active
                        formatting elements. */
                        $this->a_formatting[] = self::MARKER;
                        break;

                    /* A start tag token whose tag name is "xmp" */
                    case 'xmp':
                        /* Reconstruct the active formatting elements, if any. */
                        $this->reconstructActiveFormattingElements();

                        /* Insert an HTML element for the token. */
                        $this->insertElement($token);

                        /* Switch the content model flag to the CDATA state. */
                        return HTML5::CDATA;
                        break;

                    /* A start tag whose tag name is "table" */
                    case 'table':
                        /* If the stack of open elements has a p element in scope,
                        then act as if an end tag with the tag name p had been seen. */
                        if ($this->elementInScope('p')) {
                            $this->emitToken(
                                array(
                                    'name' => 'p',
                                    'type' => HTML5::ENDTAG
                                )
                            );
                        }

                        /* Insert an HTML element for the token. */
                        $this->insertElement($token);

                        /* Change the insertion mode to "in table". */
                        $this->mode = self::IN_TABLE;
                        break;

                    /* A start tag whose tag name is one of: "area", "basefont",
                    "bgsound", "br", "embed", "img", "param", "spacer", "wbr" */
                    case 'area':
                    case 'basefont':
                    case 'bgsound':
                    case 'br':
                    case 'embed':
                    case 'img':
                    case 'param':
                    case 'spacer':
                    case 'wbr':
                        /* Reconstruct the active formatting elements, if any. */
                        $this->reconstructActiveFormattingElements();

                        /* Insert an HTML element for the token. */
                        $this->insertElement($token);

                        /* Immediately pop the current node off the stack of open elements. */
                        array_pop($this->stack);
                        break;

                    /* A start tag whose tag name is "hr" */
                    case 'hr':
                        /* If the stack of open elements has a p element in scope,
                        then act as if an end tag with the tag name p had been seen. */
                        if ($this->elementInScope('p')) {
                            $this->emitToken(
                                array(
                                    'name' => 'p',
                                    'type' => HTML5::ENDTAG
                                )
                            );
                        }

                        /* Insert an HTML element for the token. */
                        $this->insertElement($token);

                        /* Immediately pop the current node off the stack of open elements. */
                        array_pop($this->stack);
                        break;

                    /* A start tag whose tag name is "image" */
                    case 'image':
                        /* Parse error. Change the token's tag name to "img" and
                        reprocess it. (Don't ask.) */
                        $token['name'] = 'img';
                        return $this->inBody($token);
                        break;

                    /* A start tag whose tag name is "input" */
                    case 'input':
                        /* Reconstruct the active formatting elements, if any. */
                        $this->reconstructActiveFormattingElements();

                        /* Insert an input element for the token. */
                        $element = $this->insertElement($token, false);

                        /* If the form element pointer is not null, then associate the
                        input element with the form element pointed to by the form
                        element pointer. */
                        $this->form_pointer !== null
                            ? $this->form_pointer->appendChild($element)
                            : end($this->stack)->appendChild($element);

                        /* Pop that input element off the stack of open elements. */
                        array_pop($this->stack);
                        break;

                    /* A start tag whose tag name is "isindex" */
                    case 'isindex':
                        /* Parse error. */
                        // w/e

                        /* If the form element pointer is not null,
                        then ignore the token. */
                        if ($this->form_pointer === null) {
                            /* Act as if a start tag token with the tag name "form" had
                            been seen. */
                            $this->inBody(
                                array(
                                    'name' => 'body',
                                    'type' => HTML5::STARTTAG,
                                    'attr' => array()
                                )
                            );

                            /* Act as if a start tag token with the tag name "hr" had
                            been seen. */
                            $this->inBody(
                                array(
                                    'name' => 'hr',
                                    'type' => HTML5::STARTTAG,
                                    'attr' => array()
                                )
                            );

                            /* Act as if a start tag token with the tag name "p" had
                            been seen. */
                            $this->inBody(
                                array(
                                    'name' => 'p',
                                    'type' => HTML5::STARTTAG,
                                    'attr' => array()
                                )
                            );

                            /* Act as if a start tag token with the tag name "label"
                            had been seen. */
                            $this->inBody(
                                array(
                                    'name' => 'label',
                                    'type' => HTML5::STARTTAG,
                                    'attr' => array()
                                )
                            );

                            /* Act as if a stream of character tokens had been seen. */
                            $this->insertText(
                                'This is a searchable index. ' .
                                'Insert your search keywords here: '
                            );

                            /* Act as if a start tag token with the tag name "input"
                            had been seen, with all the attributes from the "isindex"
                            token, except with the "name" attribute set to the value
                            "isindex" (ignoring any explicit "name" attribute). */
                            $attr = $token['attr'];
                            $attr[] = array('name' => 'name', 'value' => 'isindex');

                            $this->inBody(
                                array(
                                    'name' => 'input',
                                    'type' => HTML5::STARTTAG,
                                    'attr' => $attr
                                )
                            );

                            /* Act as if a stream of character tokens had been seen
                            (see below for what they should say). */
                            $this->insertText(
                                'This is a searchable index. ' .
                                'Insert your search keywords here: '
                            );

                            /* Act as if an end tag token with the tag name "label"
                            had been seen. */
                            $this->inBody(
                                array(
                                    'name' => 'label',
                                    'type' => HTML5::ENDTAG
                                )
                            );

                            /* Act as if an end tag token with the tag name "p" had
                            been seen. */
                            $this->inBody(
                                array(
                                    'name' => 'p',
                                    'type' => HTML5::ENDTAG
                                )
                            );

                            /* Act as if a start tag token with the tag name "hr" had
                            been seen. */
                            $this->inBody(
                                array(
                                    'name' => 'hr',
                                    'type' => HTML5::ENDTAG
                                )
                            );

                            /* Act as if an end tag token with the tag name "form" had
                            been seen. */
                            $this->inBody(
                                array(
                                    'name' => 'form',
                                    'type' => HTML5::ENDTAG
                                )
                            );
                        }
                        break;

                    /* A start tag whose tag name is "textarea" */
                    case 'textarea':
                        $this->insertElement($token);

                        /* Switch the tokeniser's content model flag to the
                        RCDATA state. */
                        return HTML5::RCDATA;
                        break;

                    /* A start tag whose tag name is one of: "iframe", "noembed",
                    "noframes" */
                    case 'iframe':
                    case 'noembed':
                    case 'noframes':
                        $this->insertElement($token);

                        /* Switch the tokeniser's content model flag to the CDATA state. */
                        return HTML5::CDATA;
                        break;

                    /* A start tag whose tag name is "select" */
                    case 'select':
                        /* Reconstruct the active formatting elements, if any. */
                        $this->reconstructActiveFormattingElements();

                        /* Insert an HTML element for the token. */
                        $this->insertElement($token);

                        /* Change the insertion mode to "in select". */
                        $this->mode = self::IN_SELECT;
                        break;

                    /* A start or end tag whose tag name is one of: "caption", "col",
                    "colgroup", "frame", "frameset", "head", "option", "optgroup",
                    "tbody", "td", "tfoot", "th", "thead", "tr". */
                    case 'caption':
                    case 'col':
                    case 'colgroup':
                    case 'frame':
                    case 'frameset':
                    case 'head':
                    case 'option':
                    case 'optgroup':
                    case 'tbody':
                    case 'td':
                    case 'tfoot':
                    case 'th':
                    case 'thead':
                    case 'tr':
                        // Parse error. Ignore the token.
                        break;

                    /* A start or end tag whose tag name is one of: "event-source",
                    "section", "nav", "article", "aside", "header", "footer",
                    "datagrid", "command" */
                    case 'event-source':
                    case 'section':
                    case 'nav':
                    case 'article':
                    case 'aside':
                    case 'header':
                    case 'footer':
                    case 'datagrid':
                    case 'command':
                        // Work in progress!
                        break;

                    /* A start tag token not covered by the previous entries */
                    default:
                        /* Reconstruct the active formatting elements, if any. */
                        $this->reconstructActiveFormattingElements();

                        $this->insertElement($token, true, true);
                        break;
                }
                break;

            case HTML5::ENDTAG:
                switch ($token['name']) {
                    /* An end tag with the tag name "body" */
                    case 'body':
                        /* If the second element in the stack of open elements is
                        not a body element, this is a parse error. Ignore the token.
                        (innerHTML case) */
                        if (count($this->stack) < 2 || $this->stack[1]->nodeName !== 'body') {
                            // Ignore.

                            /* If the current node is not the body element, then this
                            is a parse error. */
                        } elseif (end($this->stack)->nodeName !== 'body') {
                            // Parse error.
                        }

                        /* Change the insertion mode to "after body". */
                        $this->mode = self::AFTER_BODY;
                        break;

                    /* An end tag with the tag name "html" */
                    case 'html':
                        /* Act as if an end tag with tag name "body" had been seen,
                        then, if that token wasn't ignored, reprocess the current
                        token. */
                        $this->inBody(
                            array(
                                'name' => 'body',
                                'type' => HTML5::ENDTAG
                            )
                        );

                        return $this->afterBody($token);
                        break;

                    /* An end tag whose tag name is one of: "address", "blockquote",
                    "center", "dir", "div", "dl", "fieldset", "listing", "menu",
                    "ol", "pre", "ul" */
                    case 'address':
                    case 'blockquote':
                    case 'center':
                    case 'dir':
                    case 'div':
                    case 'dl':
                    case 'fieldset':
                    case 'listing':
                    case 'menu':
                    case 'ol':
                    case 'pre':
                    case 'ul':
                        /* If the stack of open elements has an element in scope
                        with the same tag name as that of the token, then generate
                        implied end tags. */
                        if ($this->elementInScope($token['name'])) {
                            $this->generateImpliedEndTags();

                            /* Now, if the current node is not an element with
                            the same tag name as that of the token, then this
                            is a parse error. */
                            // w/e

                            /* If the stack of open elements has an element in
                            scope with the same tag name as that of the token,
                            then pop elements from this stack until an element
                            with that tag name has been popped from the stack. */
                            for ($n = count($this->stack) - 1; $n >= 0; $n--) {
                                if ($this->stack[$n]->nodeName === $token['name']) {
                                    $n = -1;
                                }

                                array_pop($this->stack);
                            }
                        }
                        break;

                    /* An end tag whose tag name is "form" */
                    case 'form':
                        /* If the stack of open elements has an element in scope
                        with the same tag name as that of the token, then generate
                        implied    end tags. */
                        if ($this->elementInScope($token['name'])) {
                            $this->generateImpliedEndTags();

                        }

                        if (end($this->stack)->nodeName !== $token['name']) {
                            /* Now, if the current node is not an element with the
                            same tag name as that of the token, then this is a parse
                            error. */
                            // w/e

                        } else {
                            /* Otherwise, if the current node is an element with
                            the same tag name as that of the token pop that element
                            from the stack. */
                            array_pop($this->stack);
                        }

                        /* In any case, set the form element pointer to null. */
                        $this->form_pointer = null;
                        break;

                    /* An end tag whose tag name is "p" */
                    case 'p':
                        /* If the stack of open elements has a p element in scope,
                        then generate implied end tags, except for p elements. */
                        if ($this->elementInScope('p')) {
                            $this->generateImpliedEndTags(array('p'));

                            /* If the current node is not a p element, then this is
                            a parse error. */
                            // k

                            /* If the stack of open elements has a p element in
                            scope, then pop elements from this stack until the stack
                            no longer has a p element in scope. */
                            for ($n = count($this->stack) - 1; $n >= 0; $n--) {
                                if ($this->elementInScope('p')) {
                                    array_pop($this->stack);

                                } else {
                                    break;
                                }
                            }
                        }
                        break;

                    /* An end tag whose tag name is "dd", "dt", or "li" */
                    case 'dd':
                    case 'dt':
                    case 'li':
                        /* If the stack of open elements has an element in scope
                        whose tag name matches the tag name of the token, then
                        generate implied end tags, except for elements with the
                        same tag name as the token. */
                        if ($this->elementInScope($token['name'])) {
                            $this->generateImpliedEndTags(array($token['name']));

                            /* If the current node is not an element with the same
                            tag name as the token, then this is a parse error. */
                            // w/e

                            /* If the stack of open elements has an element in scope
                            whose tag name matches the tag name of the token, then
                            pop elements from this stack until an element with that
                            tag name has been popped from the stack. */
                            for ($n = count($this->stack) - 1; $n >= 0; $n--) {
                                if ($this->stack[$n]->nodeName === $token['name']) {
                                    $n = -1;
                                }

                                array_pop($this->stack);
                            }
                        }
                        break;

                    /* An end tag whose tag name is one of: "h1", "h2", "h3", "h4",
                    "h5", "h6" */
                    case 'h1':
                    case 'h2':
                    case 'h3':
                    case 'h4':
                    case 'h5':
                    case 'h6':
                        $elements = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6');

                        /* If the stack of open elements has in scope an element whose
                        tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then
                        generate implied end tags. */
                        if ($this->elementInScope($elements)) {
                            $this->generateImpliedEndTags();

                            /* Now, if the current node is not an element with the same
                            tag name as that of the token, then this is a parse error. */
                            // w/e

                            /* If the stack of open elements has in scope an element
                            whose tag name is one of "h1", "h2", "h3", "h4", "h5", or
                            "h6", then pop elements from the stack until an element
                            with one of those tag names has been popped from the stack. */
                            while ($this->elementInScope($elements)) {
                                array_pop($this->stack);
                            }
                        }
                        break;

                    /* An end tag whose tag name is one of: "a", "b", "big", "em",
                    "font", "i", "nobr", "s", "small", "strike", "strong", "tt", "u" */
                    case 'a':
                    case 'b':
                    case 'big':
                    case 'em':
                    case 'font':
                    case 'i':
                    case 'nobr':
                    case 's':
                    case 'small':
                    case 'strike':
                    case 'strong':
                    case 'tt':
                    case 'u':
                        /* 1. Let the formatting element be the last element in
                        the list of active formatting elements that:
                            * is between the end of the list and the last scope
                            marker in the list, if any, or the start of the list
                            otherwise, and
                            * has the same tag name as the token.
                        */
                        while (true) {
                            for ($a = count($this->a_formatting) - 1; $a >= 0; $a--) {
                                if ($this->a_formatting[$a] === self::MARKER) {
                                    break;

                                } elseif ($this->a_formatting[$a]->tagName === $token['name']) {
                                    $formatting_element = $this->a_formatting[$a];
                                    $in_stack = in_array($formatting_element, $this->stack, true);
                                    $fe_af_pos = $a;
                                    break;
                                }
                            }

                            /* If there is no such node, or, if that node is
                            also in the stack of open elements but the element
                            is not in scope, then this is a parse error. Abort
                            these steps. The token is ignored. */
                            if (!isset($formatting_element) || ($in_stack &&
                                    !$this->elementInScope($token['name']))
                            ) {
                                break;

                                /* Otherwise, if there is such a node, but that node
                                is not in the stack of open elements, then this is a
                                parse error; remove the element from the list, and
                                abort these steps. */
                            } elseif (isset($formatting_element) && !$in_stack) {
                                unset($this->a_formatting[$fe_af_pos]);
                                $this->a_formatting = array_merge($this->a_formatting);
                                break;
                            }

                            /* 2. Let the furthest block be the topmost node in the
                            stack of open elements that is lower in the stack
                            than the formatting element, and is not an element in
                            the phrasing or formatting categories. There might
                            not be one. */
                            $fe_s_pos = array_search($formatting_element, $this->stack, true);
                            $length = count($this->stack);

                            for ($s = $fe_s_pos + 1; $s < $length; $s++) {
                                $category = $this->getElementCategory($this->stack[$s]->nodeName);

                                if ($category !== self::PHRASING && $category !== self::FORMATTING) {
                                    $furthest_block = $this->stack[$s];
                                }
                            }

                            /* 3. If there is no furthest block, then the UA must
                            skip the subsequent steps and instead just pop all
                            the nodes from the bottom of the stack of open
                            elements, from the current node up to the formatting
                            element, and remove the formatting element from the
                            list of active formatting elements. */
                            if (!isset($furthest_block)) {
                                for ($n = $length - 1; $n >= $fe_s_pos; $n--) {
                                    array_pop($this->stack);
                                }

                                unset($this->a_formatting[$fe_af_pos]);
                                $this->a_formatting = array_merge($this->a_formatting);
                                break;
                            }

                            /* 4. Let the common ancestor be the element
                            immediately above the formatting element in the stack
                            of open elements. */
                            $common_ancestor = $this->stack[$fe_s_pos - 1];

                            /* 5. If the furthest block has a parent node, then
                            remove the furthest block from its parent node. */
                            if ($furthest_block->parentNode !== null) {
                                $furthest_block->parentNode->removeChild($furthest_block);
                            }

                            /* 6. Let a bookmark note the position of the
                            formatting element in the list of active formatting
                            elements relative to the elements on either side
                            of it in the list. */
                            $bookmark = $fe_af_pos;

                            /* 7. Let node and last node  be the furthest block.
                            Follow these steps: */
                            $node = $furthest_block;
                            $last_node = $furthest_block;

                            while (true) {
                                for ($n = array_search($node, $this->stack, true) - 1; $n >= 0; $n--) {
                                    /* 7.1 Let node be the element immediately
                                    prior to node in the stack of open elements. */
                                    $node = $this->stack[$n];

                                    /* 7.2 If node is not in the list of active
                                    formatting elements, then remove node from
                                    the stack of open elements and then go back
                                    to step 1. */
                                    if (!in_array($node, $this->a_formatting, true)) {
                                        unset($this->stack[$n]);
                                        $this->stack = array_merge($this->stack);

                                    } else {
                                        break;
                                    }
                                }

                                /* 7.3 Otherwise, if node is the formatting
                                element, then go to the next step in the overall
                                algorithm. */
                                if ($node === $formatting_element) {
                                    break;

                                    /* 7.4 Otherwise, if last node is the furthest
                                    block, then move the aforementioned bookmark to
                                    be immediately after the node in the list of
                                    active formatting elements. */
                                } elseif ($last_node === $furthest_block) {
                                    $bookmark = array_search($node, $this->a_formatting, true) + 1;
                                }

                                /* 7.5 If node has any children, perform a
                                shallow clone of node, replace the entry for
                                node in the list of active formatting elements
                                with an entry for the clone, replace the entry
                                for node in the stack of open elements with an
                                entry for the clone, and let node be the clone. */
                                if ($node->hasChildNodes()) {
                                    $clone = $node->cloneNode();
                                    $s_pos = array_search($node, $this->stack, true);
                                    $a_pos = array_search($node, $this->a_formatting, true);

                                    $this->stack[$s_pos] = $clone;
                                    $this->a_formatting[$a_pos] = $clone;
                                    $node = $clone;
                                }

                                /* 7.6 Insert last node into node, first removing
                                it from its previous parent node if any. */
                                if ($last_node->parentNode !== null) {
                                    $last_node->parentNode->removeChild($last_node);
                                }

                                $node->appendChild($last_node);

                                /* 7.7 Let last node be node. */
                                $last_node = $node;
                            }

                            /* 8. Insert whatever last node ended up being in
                            the previous step into the common ancestor node,
                            first removing it from its previous parent node if
                            any. */
                            if ($last_node->parentNode !== null) {
                                $last_node->parentNode->removeChild($last_node);
                            }

                            $common_ancestor->appendChild($last_node);

                            /* 9. Perform a shallow clone of the formatting
                            element. */
                            $clone = $formatting_element->cloneNode();

                            /* 10. Take all of the child nodes of the furthest
                            block and append them to the clone created in the
                            last step. */
                            while ($furthest_block->hasChildNodes()) {
                                $child = $furthest_block->firstChild;
                                $furthest_block->removeChild($child);
                                $clone->appendChild($child);
                            }

                            /* 11. Append that clone to the furthest block. */
                            $furthest_block->appendChild($clone);

                            /* 12. Remove the formatting element from the list
                            of active formatting elements, and insert the clone
                            into the list of active formatting elements at the
                            position of the aforementioned bookmark. */
                            $fe_af_pos = array_search($formatting_element, $this->a_formatting, true);
                            unset($this->a_formatting[$fe_af_pos]);
                            $this->a_formatting = array_merge($this->a_formatting);

                            $af_part1 = array_slice($this->a_formatting, 0, $bookmark - 1);
                            $af_part2 = array_slice($this->a_formatting, $bookmark, count($this->a_formatting));
                            $this->a_formatting = array_merge($af_part1, array($clone), $af_part2);

                            /* 13. Remove the formatting element from the stack
                            of open elements, and insert the clone into the stack
                            of open elements immediately after (i.e. in a more
                            deeply nested position than) the position of the
                            furthest block in that stack. */
                            $fe_s_pos = array_search($formatting_element, $this->stack, true);
                            $fb_s_pos = array_search($furthest_block, $this->stack, true);
                            unset($this->stack[$fe_s_pos]);

                            $s_part1 = array_slice($this->stack, 0, $fb_s_pos);
                            $s_part2 = array_slice($this->stack, $fb_s_pos + 1, count($this->stack));
                            $this->stack = array_merge($s_part1, array($clone), $s_part2);

                            /* 14. Jump back to step 1 in this series of steps. */
                            unset($formatting_element, $fe_af_pos, $fe_s_pos, $furthest_block);
                        }
                        break;

                    /* An end tag token whose tag name is one of: "button",
                    "marquee", "object" */
                    case 'button':
                    case 'marquee':
                    case 'object':
                        /* If the stack of open elements has an element in scope whose
                        tag name matches the tag name of the token, then generate implied
                        tags. */
                        if ($this->elementInScope($token['name'])) {
                            $this->generateImpliedEndTags();

                            /* Now, if the current node is not an element with the same
                            tag name as the token, then this is a parse error. */
                            // k

                            /* Now, if the stack of open elements has an element in scope
                            whose tag name matches the tag name of the token, then pop
                            elements from the stack until that element has been popped from
                            the stack, and clear the list of active formatting elements up
                            to the last marker. */
                            for ($n = count($this->stack) - 1; $n >= 0; $n--) {
                                if ($this->stack[$n]->nodeName === $token['name']) {
                                    $n = -1;
                                }

                                array_pop($this->stack);
                            }

                            $marker = end(array_keys($this->a_formatting, self::MARKER, true));

                            for ($n = count($this->a_formatting) - 1; $n > $marker; $n--) {
                                array_pop($this->a_formatting);
                            }
                        }
                        break;

                    /* Or an end tag whose tag name is one of: "area", "basefont",
                    "bgsound", "br", "embed", "hr", "iframe", "image", "img",
                    "input", "isindex", "noembed", "noframes", "param", "select",
                    "spacer", "table", "textarea", "wbr" */
                    case 'area':
                    case 'basefont':
                    case 'bgsound':
                    case 'br':
                    case 'embed':
                    case 'hr':
                    case 'iframe':
                    case 'image':
                    case 'img':
                    case 'input':
                    case 'isindex':
                    case 'noembed':
                    case 'noframes':
                    case 'param':
                    case 'select':
                    case 'spacer':
                    case 'table':
                    case 'textarea':
                    case 'wbr':
                        // Parse error. Ignore the token.
                        break;

                    /* An end tag token not covered by the previous entries */
                    default:
                        for ($n = count($this->stack) - 1; $n >= 0; $n--) {
                            /* Initialise node to be the current node (the bottommost
                            node of the stack). */
                            $node = end($this->stack);

                            /* If node has the same tag name as the end tag token,
                            then: */
                            if ($token['name'] === $node->nodeName) {
                                /* Generate implied end tags. */
                                $this->generateImpliedEndTags();

                                /* If the tag name of the end tag token does not
                                match the tag name of the current node, this is a
                                parse error. */
                                // k

                                /* Pop all the nodes from the current node up to
                                node, including node, then stop this algorithm. */
                                for ($x = count($this->stack) - $n; $x >= $n; $x--) {
                                    array_pop($this->stack);
                                }

                            } else {
                                $category = $this->getElementCategory($node);

                                if ($category !== self::SPECIAL && $category !== self::SCOPING) {
                                    /* Otherwise, if node is in neither the formatting
                                    category nor the phrasing category, then this is a
                                    parse error. Stop this algorithm. The end tag token
                                    is ignored. */
                                    return false;
                                }
                            }
                        }
                        break;
                }
                break;
        }
    }

    private function inTable($token)
    {
        $clear = array('html', 'table');

        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
        or U+0020 SPACE */
        if ($token['type'] === HTML5::CHARACTR &&
            preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])
        ) {
            /* Append the character to the current node. */
            $text = $this->dom->createTextNode($token['data']);
            end($this->stack)->appendChild($text);

            /* A comment token */
        } elseif ($token['type'] === HTML5::COMMENT) {
            /* Append a Comment node to the current node with the data
            attribute set to the data given in the comment token. */
            $comment = $this->dom->createComment($token['data']);
            end($this->stack)->appendChild($comment);

            /* A start tag whose tag name is "caption" */
        } elseif ($token['type'] === HTML5::STARTTAG &&
            $token['name'] === 'caption'
        ) {
            /* Clear the stack back to a table context. */
            $this->clearStackToTableContext($clear);

            /* Insert a marker at the end of the list of active
            formatting elements. */
            $this->a_formatting[] = self::MARKER;

            /* Insert an HTML element for the token, then switch the
            insertion mode to "in caption". */
            $this->insertElement($token);
            $this->mode = self::IN_CAPTION;

            /* A start tag whose tag name is "colgroup" */
        } elseif ($token['type'] === HTML5::STARTTAG &&
            $token['name'] === 'colgroup'
        ) {
            /* Clear the stack back to a table context. */
            $this->clearStackToTableContext($clear);

            /* Insert an HTML element for the token, then switch the
            insertion mode to "in column group". */
            $this->insertElement($token);
            $this->mode = self::IN_CGROUP;

            /* A start tag whose tag name is "col" */
        } elseif ($token['type'] === HTML5::STARTTAG &&
            $token['name'] === 'col'
        ) {
            $this->inTable(
                array(
                    'name' => 'colgroup',
                    'type' => HTML5::STARTTAG,
                    'attr' => array()
                )
            );

            $this->inColumnGroup($token);

            /* A start tag whose tag name is one of: "tbody", "tfoot", "thead" */
        } elseif ($token['type'] === HTML5::STARTTAG && in_array(
                $token['name'],
                array('tbody', 'tfoot', 'thead')
            )
        ) {
            /* Clear the stack back to a table context. */
            $this->clearStackToTableContext($clear);

            /* Insert an HTML element for the token, then switch the insertion
            mode to "in table body". */
            $this->insertElement($token);
            $this->mode = self::IN_TBODY;

            /* A start tag whose tag name is one of: "td", "th", "tr" */
        } elseif ($token['type'] === HTML5::STARTTAG &&
            in_array($token['name'], array('td', 'th', 'tr'))
        ) {
            /* Act as if a start tag token with the tag name "tbody" had been
            seen, then reprocess the current token. */
            $this->inTable(
                array(
                    'name' => 'tbody',
                    'type' => HTML5::STARTTAG,
                    'attr' => array()
                )
            );

            return $this->inTableBody($token);

            /* A start tag whose tag name is "table" */
        } elseif ($token['type'] === HTML5::STARTTAG &&
            $token['name'] === 'table'
        ) {
            /* Parse error. Act as if an end tag token with the tag name "table"
            had been seen, then, if that token wasn't ignored, reprocess the
            current token. */
            $this->inTable(
                array(
                    'name' => 'table',
                    'type' => HTML5::ENDTAG
                )
            );

            return $this->mainPhase($token);

            /* An end tag whose tag name is "table" */
        } elseif ($token['type'] === HTML5::ENDTAG &&
            $token['name'] === 'table'
        ) {
            /* If the stack of open elements does not have an element in table
            scope with the same tag name as the token, this is a parse error.
            Ignore the token. (innerHTML case) */
            if (!$this->elementInScope($token['name'], true)) {
                return false;

                /* Otherwise: */
            } else {
                /* Generate implied end tags. */
                $this->generateImpliedEndTags();

                /* Now, if the current node is not a table element, then this
                is a parse error. */
                // w/e

                /* Pop elements from this stack until a table element has been
                popped from the stack. */
                while (true) {
                    $current = end($this->stack)->nodeName;
                    array_pop($this->stack);

                    if ($current === 'table') {
                        break;
                    }
                }

                /* Reset the insertion mode appropriately. */
                $this->resetInsertionMode();
            }

            /* An end tag whose tag name is one of: "body", "caption", "col",
            "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr" */
        } elseif ($token['type'] === HTML5::ENDTAG && in_array(
                $token['name'],
                array(
                    'body',
                    'caption',
                    'col',
                    'colgroup',
                    'html',
                    'tbody',
                    'td',
                    'tfoot',
                    'th',
                    'thead',
                    'tr'
                )
            )
        ) {
            // Parse error. Ignore the token.

            /* Anything else */
        } else {
            /* Parse error. Process the token as if the insertion mode was "in
            body", with the following exception: */

            /* If the current node is a table, tbody, tfoot, thead, or tr
            element, then, whenever a node would be inserted into the current
            node, it must instead be inserted into the foster parent element. */
            if (in_array(
                end($this->stack)->nodeName,
                array('table', 'tbody', 'tfoot', 'thead', 'tr')
            )
            ) {
                /* The foster parent element is the parent element of the last
                table element in the stack of open elements, if there is a
                table element and it has such a parent element. If there is no
                table element in the stack of open elements (innerHTML case),
                then the foster parent element is the first element in the
                stack of open elements (the html  element). Otherwise, if there
                is a table element in the stack of open elements, but the last
                table element in the stack of open elements has no parent, or
                its parent node is not an element, then the foster parent
                element is the element before the last table element in the
                stack of open elements. */
                for ($n = count($this->stack) - 1; $n >= 0; $n--) {
                    if ($this->stack[$n]->nodeName === 'table') {
                        $table = $this->stack[$n];
                        break;
                    }
                }

                if (isset($table) && $table->parentNode !== null) {
                    $this->foster_parent = $table->parentNode;

                } elseif (!isset($table)) {
                    $this->foster_parent = $this->stack[0];

                } elseif (isset($table) && ($table->parentNode === null ||
                        $table->parentNode->nodeType !== XML_ELEMENT_NODE)
                ) {
                    $this->foster_parent = $this->stack[$n - 1];
                }
            }

            $this->inBody($token);
        }
    }

    private function inCaption($token)
    {
        /* An end tag whose tag name is "caption" */
        if ($token['type'] === HTML5::ENDTAG && $token['name'] === 'caption') {
            /* If the stack of open elements does not have an element in table
            scope with the same tag name as the token, this is a parse error.
            Ignore the token. (innerHTML case) */
            if (!$this->elementInScope($token['name'], true)) {
                // Ignore

                /* Otherwise: */
            } else {
                /* Generate implied end tags. */
                $this->generateImpliedEndTags();

                /* Now, if the current node is not a caption element, then this
                is a parse error. */
                // w/e

                /* Pop elements from this stack until a caption element has
                been popped from the stack. */
                while (true) {
                    $node = end($this->stack)->nodeName;
                    array_pop($this->stack);

                    if ($node === 'caption') {
                        break;
                    }
                }

                /* Clear the list of active formatting elements up to the last
                marker. */
                $this->clearTheActiveFormattingElementsUpToTheLastMarker();

                /* Switch the insertion mode to "in table". */
                $this->mode = self::IN_TABLE;
            }

            /* A start tag whose tag name is one of: "caption", "col", "colgroup",
            "tbody", "td", "tfoot", "th", "thead", "tr", or an end tag whose tag
            name is "table" */
        } elseif (($token['type'] === HTML5::STARTTAG && in_array(
                    $token['name'],
                    array(
                        'caption',
                        'col',
                        'colgroup',
                        'tbody',
                        'td',
                        'tfoot',
                        'th',
                        'thead',
                        'tr'
                    )
                )) || ($token['type'] === HTML5::ENDTAG &&
                $token['name'] === 'table')
        ) {
            /* Parse error. Act as if an end tag with the tag name "caption"
            had been seen, then, if that token wasn't ignored, reprocess the
            current token. */
            $this->inCaption(
                array(
                    'name' => 'caption',
                    'type' => HTML5::ENDTAG
                )
            );

            return $this->inTable($token);

            /* An end tag whose tag name is one of: "body", "col", "colgroup",
            "html", "tbody", "td", "tfoot", "th", "thead", "tr" */
        } elseif ($token['type'] === HTML5::ENDTAG && in_array(
                $token['name'],
                array(
                    'body',
                    'col',
                    'colgroup',
                    'html',
                    'tbody',
                    'tfoot',
                    'th',
                    'thead',
                    'tr'
                )
            )
        ) {
            // Parse error. Ignore the token.

            /* Anything else */
        } else {
            /* Process the token as if the insertion mode was "in body". */
            $this->inBody($token);
        }
    }

    private function inColumnGroup($token)
    {
        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
        or U+0020 SPACE */
        if ($token['type'] === HTML5::CHARACTR &&
            preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])
        ) {
            /* Append the character to the current node. */
            $text = $this->dom->createTextNode($token['data']);
            end($this->stack)->appendChild($text);

            /* A comment token */
        } elseif ($token['type'] === HTML5::COMMENT) {
            /* Append a Comment node to the current node with the data
            attribute set to the data given in the comment token. */
            $comment = $this->dom->createComment($token['data']);
            end($this->stack)->appendChild($comment);

            /* A start tag whose tag name is "col" */
        } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'col') {
            /* Insert a col element for the token. Immediately pop the current
            node off the stack of open elements. */
            $this->insertElement($token);
            array_pop($this->stack);

            /* An end tag whose tag name is "colgroup" */
        } elseif ($token['type'] === HTML5::ENDTAG &&
            $token['name'] === 'colgroup'
        ) {
            /* If the current node is the root html element, then this is a
            parse error, ignore the token. (innerHTML case) */
            if (end($this->stack)->nodeName === 'html') {
                // Ignore

                /* Otherwise, pop the current node (which will be a colgroup
                element) from the stack of open elements. Switch the insertion
                mode to "in table". */
            } else {
                array_pop($this->stack);
                $this->mode = self::IN_TABLE;
            }

            /* An end tag whose tag name is "col" */
        } elseif ($token['type'] === HTML5::ENDTAG && $token['name'] === 'col') {
            /* Parse error. Ignore the token. */

            /* Anything else */
        } else {
            /* Act as if an end tag with the tag name "colgroup" had been seen,
            and then, if that token wasn't ignored, reprocess the current token. */
            $this->inColumnGroup(
                array(
                    'name' => 'colgroup',
                    'type' => HTML5::ENDTAG
                )
            );

            return $this->inTable($token);
        }
    }

    private function inTableBody($token)
    {
        $clear = array('tbody', 'tfoot', 'thead', 'html');

        /* A start tag whose tag name is "tr" */
        if ($token['type'] === HTML5::STARTTAG && $token['name'] === 'tr') {
            /* Clear the stack back to a table body context. */
            $this->clearStackToTableContext($clear);

            /* Insert a tr element for the token, then switch the insertion
            mode to "in row". */
            $this->insertElement($token);
            $this->mode = self::IN_ROW;

            /* A start tag whose tag name is one of: "th", "td" */
        } elseif ($token['type'] === HTML5::STARTTAG &&
            ($token['name'] === 'th' || $token['name'] === 'td')
        ) {
            /* Parse error. Act as if a start tag with the tag name "tr" had
            been seen, then reprocess the current token. */
            $this->inTableBody(
                array(
                    'name' => 'tr',
                    'type' => HTML5::STARTTAG,
                    'attr' => array()
                )
            );

            return $this->inRow($token);

            /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */
        } elseif ($token['type'] === HTML5::ENDTAG &&
            in_array($token['name'], array('tbody', 'tfoot', 'thead'))
        ) {
            /* If the stack of open elements does not have an element in table
            scope with the same tag name as the token, this is a parse error.
            Ignore the token. */
            if (!$this->elementInScope($token['name'], true)) {
                // Ignore

                /* Otherwise: */
            } else {
                /* Clear the stack back to a table body context. */
                $this->clearStackToTableContext($clear);

                /* Pop the current node from the stack of open elements. Switch
                the insertion mode to "in table". */
                array_pop($this->stack);
                $this->mode = self::IN_TABLE;
            }

            /* A start tag whose tag name is one of: "caption", "col", "colgroup",
            "tbody", "tfoot", "thead", or an end tag whose tag name is "table" */
        } elseif (($token['type'] === HTML5::STARTTAG && in_array(
                    $token['name'],
                    array('caption', 'col', 'colgroup', 'tbody', 'tfoor', 'thead')
                )) ||
            ($token['type'] === HTML5::STARTTAG && $token['name'] === 'table')
        ) {
            /* If the stack of open elements does not have a tbody, thead, or
            tfoot element in table scope, this is a parse error. Ignore the
            token. (innerHTML case) */
            if (!$this->elementInScope(array('tbody', 'thead', 'tfoot'), true)) {
                // Ignore.

                /* Otherwise: */
            } else {
                /* Clear the stack back to a table body context. */
                $this->clearStackToTableContext($clear);

                /* Act as if an end tag with the same tag name as the current
                node ("tbody", "tfoot", or "thead") had been seen, then
                reprocess the current token. */
                $this->inTableBody(
                    array(
                        'name' => end($this->stack)->nodeName,
                        'type' => HTML5::ENDTAG
                    )
                );

                return $this->mainPhase($token);
            }

            /* An end tag whose tag name is one of: "body", "caption", "col",
            "colgroup", "html", "td", "th", "tr" */
        } elseif ($token['type'] === HTML5::ENDTAG && in_array(
                $token['name'],
                array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th', 'tr')
            )
        ) {
            /* Parse error. Ignore the token. */

            /* Anything else */
        } else {
            /* Process the token as if the insertion mode was "in table". */
            $this->inTable($token);
        }
    }

    private function inRow($token)
    {
        $clear = array('tr', 'html');

        /* A start tag whose tag name is one of: "th", "td" */
        if ($token['type'] === HTML5::STARTTAG &&
            ($token['name'] === 'th' || $token['name'] === 'td')
        ) {
            /* Clear the stack back to a table row context. */
            $this->clearStackToTableContext($clear);

            /* Insert an HTML element for the token, then switch the insertion
            mode to "in cell". */
            $this->insertElement($token);
            $this->mode = self::IN_CELL;

            /* Insert a marker at the end of the list of active formatting
            elements. */
            $this->a_formatting[] = self::MARKER;

            /* An end tag whose tag name is "tr" */
        } elseif ($token['type'] === HTML5::ENDTAG && $token['name'] === 'tr') {
            /* If the stack of open elements does not have an element in table
            scope with the same tag name as the token, this is a parse error.
            Ignore the token. (innerHTML case) */
            if (!$this->elementInScope($token['name'], true)) {
                // Ignore.

                /* Otherwise: */
            } else {
                /* Clear the stack back to a table row context. */
                $this->clearStackToTableContext($clear);

                /* Pop the current node (which will be a tr element) from the
                stack of open elements. Switch the insertion mode to "in table
                body". */
                array_pop($this->stack);
                $this->mode = self::IN_TBODY;
            }

            /* A start tag whose tag name is one of: "caption", "col", "colgroup",
            "tbody", "tfoot", "thead", "tr" or an end tag whose tag name is "table" */
        } elseif ($token['type'] === HTML5::STARTTAG && in_array(
                $token['name'],
                array('caption', 'col', 'colgroup', 'tbody', 'tfoot', 'thead', 'tr')
            )
        ) {
            /* Act as if an end tag with the tag name "tr" had been seen, then,
            if that token wasn't ignored, reprocess the current token. */
            $this->inRow(
                array(
                    'name' => 'tr',
                    'type' => HTML5::ENDTAG
                )
            );

            return $this->inCell($token);

            /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */
        } elseif ($token['type'] === HTML5::ENDTAG &&
            in_array($token['name'], array('tbody', 'tfoot', 'thead'))
        ) {
            /* If the stack of open elements does not have an element in table
            scope with the same tag name as the token, this is a parse error.
            Ignore the token. */
            if (!$this->elementInScope($token['name'], true)) {
                // Ignore.

                /* Otherwise: */
            } else {
                /* Otherwise, act as if an end tag with the tag name "tr" had
                been seen, then reprocess the current token. */
                $this->inRow(
                    array(
                        'name' => 'tr',
                        'type' => HTML5::ENDTAG
                    )
                );

                return $this->inCell($token);
            }

            /* An end tag whose tag name is one of: "body", "caption", "col",
            "colgroup", "html", "td", "th" */
        } elseif ($token['type'] === HTML5::ENDTAG && in_array(
                $token['name'],
                array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th', 'tr')
            )
        ) {
            /* Parse error. Ignore the token. */

            /* Anything else */
        } else {
            /* Process the token as if the insertion mode was "in table". */
            $this->inTable($token);
        }
    }

    private function inCell($token)
    {
        /* An end tag whose tag name is one of: "td", "th" */
        if ($token['type'] === HTML5::ENDTAG &&
            ($token['name'] === 'td' || $token['name'] === 'th')
        ) {
            /* If the stack of open elements does not have an element in table
            scope with the same tag name as that of the token, then this is a
            parse error and the token must be ignored. */
            if (!$this->elementInScope($token['name'], true)) {
                // Ignore.

                /* Otherwise: */
            } else {
                /* Generate implied end tags, except for elements with the same
                tag name as the token. */
                $this->generateImpliedEndTags(array($token['name']));

                /* Now, if the current node is not an element with the same tag
                name as the token, then this is a parse error. */
                // k

                /* Pop elements from this stack until an element with the same
                tag name as the token has been popped from the stack. */
                while (true) {
                    $node = end($this->stack)->nodeName;
                    array_pop($this->stack);

                    if ($node === $token['name']) {
                        break;
                    }
                }

                /* Clear the list of active formatting elements up to the last
                marker. */
                $this->clearTheActiveFormattingElementsUpToTheLastMarker();

                /* Switch the insertion mode to "in row". (The current node
                will be a tr element at this point.) */
                $this->mode = self::IN_ROW;
            }

            /* A start tag whose tag name is one of: "caption", "col", "colgroup",
            "tbody", "td", "tfoot", "th", "thead", "tr" */
        } elseif ($token['type'] === HTML5::STARTTAG && in_array(
                $token['name'],
                array(
                    'caption',
                    'col',
                    'colgroup',
                    'tbody',
                    'td',
                    'tfoot',
                    'th',
                    'thead',
                    'tr'
                )
            )
        ) {
            /* If the stack of open elements does not have a td or th element
            in table scope, then this is a parse error; ignore the token.
            (innerHTML case) */
            if (!$this->elementInScope(array('td', 'th'), true)) {
                // Ignore.

                /* Otherwise, close the cell (see below) and reprocess the current
                token. */
            } else {
                $this->closeCell();
                return $this->inRow($token);
            }

            /* A start tag whose tag name is one of: "caption", "col", "colgroup",
            "tbody", "td", "tfoot", "th", "thead", "tr" */
        } elseif ($token['type'] === HTML5::STARTTAG && in_array(
                $token['name'],
                array(
                    'caption',
                    'col',
                    'colgroup',
                    'tbody',
                    'td',
                    'tfoot',
                    'th',
                    'thead',
                    'tr'
                )
            )
        ) {
            /* If the stack of open elements does not have a td or th element
            in table scope, then this is a parse error; ignore the token.
            (innerHTML case) */
            if (!$this->elementInScope(array('td', 'th'), true)) {
                // Ignore.

                /* Otherwise, close the cell (see below) and reprocess the current
                token. */
            } else {
                $this->closeCell();
                return $this->inRow($token);
            }

            /* An end tag whose tag name is one of: "body", "caption", "col",
            "colgroup", "html" */
        } elseif ($token['type'] === HTML5::ENDTAG && in_array(
                $token['name'],
                array('body', 'caption', 'col', 'colgroup', 'html')
            )
        ) {
            /* Parse error. Ignore the token. */

            /* An end tag whose tag name is one of: "table", "tbody", "tfoot",
            "thead", "tr" */
        } elseif ($token['type'] === HTML5::ENDTAG && in_array(
                $token['name'],
                array('table', 'tbody', 'tfoot', 'thead', 'tr')
            )
        ) {
            /* If the stack of open elements does not have an element in table
            scope with the same tag name as that of the token (which can only
            happen for "tbody", "tfoot" and "thead", or, in the innerHTML case),
            then this is a parse error and the token must be ignored. */
            if (!$this->elementInScope($token['name'], true)) {
                // Ignore.

                /* Otherwise, close the cell (see below) and reprocess the current
                token. */
            } else {
                $this->closeCell();
                return $this->inRow($token);
            }

            /* Anything else */
        } else {
            /* Process the token as if the insertion mode was "in body". */
            $this->inBody($token);
        }
    }

    private function inSelect($token)
    {
        /* Handle the token as follows: */

        /* A character token */
        if ($token['type'] === HTML5::CHARACTR) {
            /* Append the token's character to the current node. */
            $this->insertText($token['data']);

            /* A comment token */
        } elseif ($token['type'] === HTML5::COMMENT) {
            /* Append a Comment node to the current node with the data
            attribute set to the data given in the comment token. */
            $this->insertComment($token['data']);

            /* A start tag token whose tag name is "option" */
        } elseif ($token['type'] === HTML5::STARTTAG &&
            $token['name'] === 'option'
        ) {
            /* If the current node is an option element, act as if an end tag
            with the tag name "option" had been seen. */
            if (end($this->stack)->nodeName === 'option') {
                $this->inSelect(
                    array(
                        'name' => 'option',
                        'type' => HTML5::ENDTAG
                    )
                );
            }

            /* Insert an HTML element for the token. */
            $this->insertElement($token);

            /* A start tag token whose tag name is "optgroup" */
        } elseif ($token['type'] === HTML5::STARTTAG &&
            $token['name'] === 'optgroup'
        ) {
            /* If the current node is an option element, act as if an end tag
            with the tag name "option" had been seen. */
            if (end($this->stack)->nodeName === 'option') {
                $this->inSelect(
                    array(
                        'name' => 'option',
                        'type' => HTML5::ENDTAG
                    )
                );
            }

            /* If the current node is an optgroup element, act as if an end tag
            with the tag name "optgroup" had been seen. */
            if (end($this->stack)->nodeName === 'optgroup') {
                $this->inSelect(
                    array(
                        'name' => 'optgroup',
                        'type' => HTML5::ENDTAG
                    )
                );
            }

            /* Insert an HTML element for the token. */
            $this->insertElement($token);

            /* An end tag token whose tag name is "optgroup" */
        } elseif ($token['type'] === HTML5::ENDTAG &&
            $token['name'] === 'optgroup'
        ) {
            /* First, if the current node is an option element, and the node
            immediately before it in the stack of open elements is an optgroup
            element, then act as if an end tag with the tag name "option" had
            been seen. */
            $elements_in_stack = count($this->stack);

            if ($this->stack[$elements_in_stack - 1]->nodeName === 'option' &&
                $this->stack[$elements_in_stack - 2]->nodeName === 'optgroup'
            ) {
                $this->inSelect(
                    array(
                        'name' => 'option',
                        'type' => HTML5::ENDTAG
                    )
                );
            }

            /* If the current node is an optgroup element, then pop that node
            from the stack of open elements. Otherwise, this is a parse error,
            ignore the token. */
            if ($this->stack[$elements_in_stack - 1] === 'optgroup') {
                array_pop($this->stack);
            }

            /* An end tag token whose tag name is "option" */
        } elseif ($token['type'] === HTML5::ENDTAG &&
            $token['name'] === 'option'
        ) {
            /* If the current node is an option element, then pop that node
            from the stack of open elements. Otherwise, this is a parse error,
            ignore the token. */
            if (end($this->stack)->nodeName === 'option') {
                array_pop($this->stack);
            }

            /* An end tag whose tag name is "select" */
        } elseif ($token['type'] === HTML5::ENDTAG &&
            $token['name'] === 'select'
        ) {
            /* If the stack of open elements does not have an element in table
            scope with the same tag name as the token, this is a parse error.
            Ignore the token. (innerHTML case) */
            if (!$this->elementInScope($token['name'], true)) {
                // w/e

                /* Otherwise: */
            } else {
                /* Pop elements from the stack of open elements until a select
                element has been popped from the stack. */
                while (true) {
                    $current = end($this->stack)->nodeName;
                    array_pop($this->stack);

                    if ($current === 'select') {
                        break;
                    }
                }

                /* Reset the insertion mode appropriately. */
                $this->resetInsertionMode();
            }

            /* A start tag whose tag name is "select" */
        } elseif ($token['name'] === 'select' &&
            $token['type'] === HTML5::STARTTAG
        ) {
            /* Parse error. Act as if the token had been an end tag with the
            tag name "select" instead. */
            $this->inSelect(
                array(
                    'name' => 'select',
                    'type' => HTML5::ENDTAG
                )
            );

            /* An end tag whose tag name is one of: "caption", "table", "tbody",
            "tfoot", "thead", "tr", "td", "th" */
        } elseif (in_array(
                $token['name'],
                array(
                    'caption',
                    'table',
                    'tbody',
                    'tfoot',
                    'thead',
                    'tr',
                    'td',
                    'th'
                )
            ) && $token['type'] === HTML5::ENDTAG
        ) {
            /* Parse error. */
            // w/e

            /* If the stack of open elements has an element in table scope with
            the same tag name as that of the token, then act as if an end tag
            with the tag name "select" had been seen, and reprocess the token.
            Otherwise, ignore the token. */
            if ($this->elementInScope($token['name'], true)) {
                $this->inSelect(
                    array(
                        'name' => 'select',
                        'type' => HTML5::ENDTAG
                    )
                );

                $this->mainPhase($token);
            }

            /* Anything else */
        } else {
            /* Parse error. Ignore the token. */
        }
    }

    private function afterBody($token)
    {
        /* Handle the token as follows: */

        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
        or U+0020 SPACE */
        if ($token['type'] === HTML5::CHARACTR &&
            preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])
        ) {
            /* Process the token as it would be processed if the insertion mode
            was "in body". */
            $this->inBody($token);

            /* A comment token */
        } elseif ($token['type'] === HTML5::COMMENT) {
            /* Append a Comment node to the first element in the stack of open
            elements (the html element), with the data attribute set to the
            data given in the comment token. */
            $comment = $this->dom->createComment($token['data']);
            $this->stack[0]->appendChild($comment);

            /* An end tag with the tag name "html" */
        } elseif ($token['type'] === HTML5::ENDTAG && $token['name'] === 'html') {
            /* If the parser was originally created in order to handle the
            setting of an element's innerHTML attribute, this is a parse error;
            ignore the token. (The element will be an html element in this
            case.) (innerHTML case) */

            /* Otherwise, switch to the trailing end phase. */
            $this->phase = self::END_PHASE;

            /* Anything else */
        } else {
            /* Parse error. Set the insertion mode to "in body" and reprocess
            the token. */
            $this->mode = self::IN_BODY;
            return $this->inBody($token);
        }
    }

    private function inFrameset($token)
    {
        /* Handle the token as follows: */

        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
        U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */
        if ($token['type'] === HTML5::CHARACTR &&
            preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])
        ) {
            /* Append the character to the current node. */
            $this->insertText($token['data']);

            /* A comment token */
        } elseif ($token['type'] === HTML5::COMMENT) {
            /* Append a Comment node to the current node with the data
            attribute set to the data given in the comment token. */
            $this->insertComment($token['data']);

            /* A start tag with the tag name "frameset" */
        } elseif ($token['name'] === 'frameset' &&
            $token['type'] === HTML5::STARTTAG
        ) {
            $this->insertElement($token);

            /* An end tag with the tag name "frameset" */
        } elseif ($token['name'] === 'frameset' &&
            $token['type'] === HTML5::ENDTAG
        ) {
            /* If the current node is the root html element, then this is a
            parse error; ignore the token. (innerHTML case) */
            if (end($this->stack)->nodeName === 'html') {
                // Ignore

            } else {
                /* Otherwise, pop the current node from the stack of open
                elements. */
                array_pop($this->stack);

                /* If the parser was not originally created in order to handle
                the setting of an element's innerHTML attribute (innerHTML case),
                and the current node is no longer a frameset element, then change
                the insertion mode to "after frameset". */
                $this->mode = self::AFTR_FRAME;
            }

            /* A start tag with the tag name "frame" */
        } elseif ($token['name'] === 'frame' &&
            $token['type'] === HTML5::STARTTAG
        ) {
            /* Insert an HTML element for the token. */
            $this->insertElement($token);

            /* Immediately pop the current node off the stack of open elements. */
            array_pop($this->stack);

            /* A start tag with the tag name "noframes" */
        } elseif ($token['name'] === 'noframes' &&
            $token['type'] === HTML5::STARTTAG
        ) {
            /* Process the token as if the insertion mode had been "in body". */
            $this->inBody($token);

            /* Anything else */
        } else {
            /* Parse error. Ignore the token. */
        }
    }

    private function afterFrameset($token)
    {
        /* Handle the token as follows: */

        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
        U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */
        if ($token['type'] === HTML5::CHARACTR &&
            preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])
        ) {
            /* Append the character to the current node. */
            $this->insertText($token['data']);

            /* A comment token */
        } elseif ($token['type'] === HTML5::COMMENT) {
            /* Append a Comment node to the current node with the data
            attribute set to the data given in the comment token. */
            $this->insertComment($token['data']);

            /* An end tag with the tag name "html" */
        } elseif ($token['name'] === 'html' &&
            $token['type'] === HTML5::ENDTAG
        ) {
            /* Switch to the trailing end phase. */
            $this->phase = self::END_PHASE;

            /* A start tag with the tag name "noframes" */
        } elseif ($token['name'] === 'noframes' &&
            $token['type'] === HTML5::STARTTAG
        ) {
            /* Process the token as if the insertion mode had been "in body". */
            $this->inBody($token);

            /* Anything else */
        } else {
            /* Parse error. Ignore the token. */
        }
    }

    private function trailingEndPhase($token)
    {
        /* After the main phase, as each token is emitted from the tokenisation
        stage, it must be processed as described in this section. */

        /* A DOCTYPE token */
        if ($token['type'] === HTML5::DOCTYPE) {
            // Parse error. Ignore the token.

            /* A comment token */
        } elseif ($token['type'] === HTML5::COMMENT) {
            /* Append a Comment node to the Document object with the data
            attribute set to the data given in the comment token. */
            $comment = $this->dom->createComment($token['data']);
            $this->dom->appendChild($comment);

            /* A character token that is one of one of U+0009 CHARACTER TABULATION,
            U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
            or U+0020 SPACE */
        } elseif ($token['type'] === HTML5::CHARACTR &&
            preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])
        ) {
            /* Process the token as it would be processed in the main phase. */
            $this->mainPhase($token);

            /* A character token that is not one of U+0009 CHARACTER TABULATION,
            U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
            or U+0020 SPACE. Or a start tag token. Or an end tag token. */
        } elseif (($token['type'] === HTML5::CHARACTR &&
                preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) ||
            $token['type'] === HTML5::STARTTAG || $token['type'] === HTML5::ENDTAG
        ) {
            /* Parse error. Switch back to the main phase and reprocess the
            token. */
            $this->phase = self::MAIN_PHASE;
            return $this->mainPhase($token);

            /* An end-of-file token */
        } elseif ($token['type'] === HTML5::EOF) {
            /* OMG DONE!! */
        }
    }

    private function insertElement($token, $append = true, $check = false)
    {
        // Proprietary workaround for libxml2's limitations with tag names
        if ($check) {
            // Slightly modified HTML5 tag-name modification,
            // removing anything that's not an ASCII letter, digit, or hyphen
            $token['name'] = preg_replace('/[^a-z0-9-]/i', '', $token['name']);
            // Remove leading hyphens and numbers
            $token['name'] = ltrim($token['name'], '-0..9');
            // In theory, this should ever be needed, but just in case
            if ($token['name'] === '') {
                $token['name'] = 'span';
            } // arbitrary generic choice
        }

        $el = $this->dom->createElement($token['name']);

        foreach ($token['attr'] as $attr) {
            if (!$el->hasAttribute($attr['name'])) {
                $el->setAttribute($attr['name'], $attr['value']);
            }
        }

        $this->appendToRealParent($el);
        $this->stack[] = $el;

        return $el;
    }

    private function insertText($data)
    {
        $text = $this->dom->createTextNode($data);
        $this->appendToRealParent($text);
    }

    private function insertComment($data)
    {
        $comment = $this->dom->createComment($data);
        $this->appendToRealParent($comment);
    }

    private function appendToRealParent($node)
    {
        if ($this->foster_parent === null) {
            end($this->stack)->appendChild($node);

        } elseif ($this->foster_parent !== null) {
            /* If the foster parent element is the parent element of the
            last table element in the stack of open elements, then the new
            node must be inserted immediately before the last table element
            in the stack of open elements in the foster parent element;
            otherwise, the new node must be appended to the foster parent
            element. */
            for ($n = count($this->stack) - 1; $n >= 0; $n--) {
                if ($this->stack[$n]->nodeName === 'table' &&
                    $this->stack[$n]->parentNode !== null
                ) {
                    $table = $this->stack[$n];
                    break;
                }
            }

            if (isset($table) && $this->foster_parent->isSameNode($table->parentNode)) {
                $this->foster_parent->insertBefore($node, $table);
            } else {
                $this->foster_parent->appendChild($node);
            }

            $this->foster_parent = null;
        }
    }

    private function elementInScope($el, $table = false)
    {
        if (is_array($el)) {
            foreach ($el as $element) {
                if ($this->elementInScope($element, $table)) {
                    return true;
                }
            }

            return false;
        }

        $leng = count($this->stack);

        for ($n = 0; $n < $leng; $n++) {
            /* 1. Initialise node to be the current node (the bottommost node of
            the stack). */
            $node = $this->stack[$leng - 1 - $n];

            if ($node->tagName === $el) {
                /* 2. If node is the target node, terminate in a match state. */
                return true;

            } elseif ($node->tagName === 'table') {
                /* 3. Otherwise, if node is a table element, terminate in a failure
                state. */
                return false;

            } elseif ($table === true && in_array(
                    $node->tagName,
                    array(
                        'caption',
                        'td',
                        'th',
                        'button',
                        'marquee',
                        'object'
                    )
                )
            ) {
                /* 4. Otherwise, if the algorithm is the "has an element in scope"
                variant (rather than the "has an element in table scope" variant),
                and node is one of the following, terminate in a failure state. */
                return false;

            } elseif ($node === $node->ownerDocument->documentElement) {
                /* 5. Otherwise, if node is an html element (root element), terminate
                in a failure state. (This can only happen if the node is the topmost
                node of the    stack of open elements, and prevents the next step from
                being invoked if there are no more elements in the stack.) */
                return false;
            }

            /* Otherwise, set node to the previous entry in the stack of open
            elements and return to step 2. (This will never fail, since the loop
            will always terminate in the previous step if the top of the stack
            is reached.) */
        }
    }

    private function reconstructActiveFormattingElements()
    {
        /* 1. If there are no entries in the list of active formatting elements,
        then there is nothing to reconstruct; stop this algorithm. */
        $formatting_elements = count($this->a_formatting);

        if ($formatting_elements === 0) {
            return false;
        }

        /* 3. Let entry be the last (most recently added) element in the list
        of active formatting elements. */
        $entry = end($this->a_formatting);

        /* 2. If the last (most recently added) entry in the list of active
        formatting elements is a marker, or if it is an element that is in the
        stack of open elements, then there is nothing to reconstruct; stop this
        algorithm. */
        if ($entry === self::MARKER || in_array($entry, $this->stack, true)) {
            return false;
        }

        for ($a = $formatting_elements - 1; $a >= 0; true) {
            /* 4. If there are no entries before entry in the list of active
            formatting elements, then jump to step 8. */
            if ($a === 0) {
                $step_seven = false;
                break;
            }

            /* 5. Let entry be the entry one earlier than entry in the list of
            active formatting elements. */
            $a--;
            $entry = $this->a_formatting[$a];

            /* 6. If entry is neither a marker nor an element that is also in
            thetack of open elements, go to step 4. */
            if ($entry === self::MARKER || in_array($entry, $this->stack, true)) {
                break;
            }
        }

        while (true) {
            /* 7. Let entry be the element one later than entry in the list of
            active formatting elements. */
            if (isset($step_seven) && $step_seven === true) {
                $a++;
                $entry = $this->a_formatting[$a];
            }

            /* 8. Perform a shallow clone of the element entry to obtain clone. */
            $clone = $entry->cloneNode();

            /* 9. Append clone to the current node and push it onto the stack
            of open elements  so that it is the new current node. */
            end($this->stack)->appendChild($clone);
            $this->stack[] = $clone;

            /* 10. Replace the entry for entry in the list with an entry for
            clone. */
            $this->a_formatting[$a] = $clone;

            /* 11. If the entry for clone in the list of active formatting
            elements is not the last entry in the list, return to step 7. */
            if (end($this->a_formatting) !== $clone) {
                $step_seven = true;
            } else {
                break;
            }
        }
    }

    private function clearTheActiveFormattingElementsUpToTheLastMarker()
    {
        /* When the steps below require the UA to clear the list of active
        formatting elements up to the last marker, the UA must perform the
        following steps: */

        while (true) {
            /* 1. Let entry be the last (most recently added) entry in the list
            of active formatting elements. */
            $entry = end($this->a_formatting);

            /* 2. Remove entry from the list of active formatting elements. */
            array_pop($this->a_formatting);

            /* 3. If entry was a marker, then stop the algorithm at this point.
            The list has been cleared up to the last marker. */
            if ($entry === self::MARKER) {
                break;
            }
        }
    }

    private function generateImpliedEndTags($exclude = array())
    {
        /* When the steps below require the UA to generate implied end tags,
        then, if the current node is a dd element, a dt element, an li element,
        a p element, a td element, a th  element, or a tr element, the UA must
        act as if an end tag with the respective tag name had been seen and
        then generate implied end tags again. */
        $node = end($this->stack);
        $elements = array_diff(array('dd', 'dt', 'li', 'p', 'td', 'th', 'tr'), $exclude);

        while (in_array(end($this->stack)->nodeName, $elements)) {
            array_pop($this->stack);
        }
    }

    private function getElementCategory($node)
    {
        $name = $node->tagName;
        if (in_array($name, $this->special)) {
            return self::SPECIAL;
        } elseif (in_array($name, $this->scoping)) {
            return self::SCOPING;
        } elseif (in_array($name, $this->formatting)) {
            return self::FORMATTING;
        } else {
            return self::PHRASING;
        }
    }

    private function clearStackToTableContext($elements)
    {
        /* When the steps above require the UA to clear the stack back to a
        table context, it means that the UA must, while the current node is not
        a table element or an html element, pop elements from the stack of open
        elements. If this causes any elements to be popped from the stack, then
        this is a parse error. */
        while (true) {
            $node = end($this->stack)->nodeName;

            if (in_array($node, $elements)) {
                break;
            } else {
                array_pop($this->stack);
            }
        }
    }

    private function resetInsertionMode()
    {
        /* 1. Let last be false. */
        $last = false;
        $leng = count($this->stack);

        for ($n = $leng - 1; $n >= 0; $n--) {
            /* 2. Let node be the last node in the stack of open elements. */
            $node = $this->stack[$n];

            /* 3. If node is the first node in the stack of open elements, then
            set last to true. If the element whose innerHTML  attribute is being
            set is neither a td  element nor a th element, then set node to the
            element whose innerHTML  attribute is being set. (innerHTML  case) */
            if ($this->stack[0]->isSameNode($node)) {
                $last = true;
            }

            /* 4. If node is a select element, then switch the insertion mode to
            "in select" and abort these steps. (innerHTML case) */
            if ($node->nodeName === 'select') {
                $this->mode = self::IN_SELECT;
                break;

                /* 5. If node is a td or th element, then switch the insertion mode
                to "in cell" and abort these steps. */
            } elseif ($node->nodeName === 'td' || $node->nodeName === 'th') {
                $this->mode = self::IN_CELL;
                break;

                /* 6. If node is a tr element, then switch the insertion mode to
                "in    row" and abort these steps. */
            } elseif ($node->nodeName === 'tr') {
                $this->mode = self::IN_ROW;
                break;

                /* 7. If node is a tbody, thead, or tfoot element, then switch the
                insertion mode to "in table body" and abort these steps. */
            } elseif (in_array($node->nodeName, array('tbody', 'thead', 'tfoot'))) {
                $this->mode = self::IN_TBODY;
                break;

                /* 8. If node is a caption element, then switch the insertion mode
                to "in caption" and abort these steps. */
            } elseif ($node->nodeName === 'caption') {
                $this->mode = self::IN_CAPTION;
                break;

                /* 9. If node is a colgroup element, then switch the insertion mode
                to "in column group" and abort these steps. (innerHTML case) */
            } elseif ($node->nodeName === 'colgroup') {
                $this->mode = self::IN_CGROUP;
                break;

                /* 10. If node is a table element, then switch the insertion mode
                to "in table" and abort these steps. */
            } elseif ($node->nodeName === 'table') {
                $this->mode = self::IN_TABLE;
                break;

                /* 11. If node is a head element, then switch the insertion mode
                to "in body" ("in body"! not "in head"!) and abort these steps.
                (innerHTML case) */
            } elseif ($node->nodeName === 'head') {
                $this->mode = self::IN_BODY;
                break;

                /* 12. If node is a body element, then switch the insertion mode to
                "in body" and abort these steps. */
            } elseif ($node->nodeName === 'body') {
                $this->mode = self::IN_BODY;
                break;

                /* 13. If node is a frameset element, then switch the insertion
                mode to "in frameset" and abort these steps. (innerHTML case) */
            } elseif ($node->nodeName === 'frameset') {
                $this->mode = self::IN_FRAME;
                break;

                /* 14. If node is an html element, then: if the head element
                pointer is null, switch the insertion mode to "before head",
                otherwise, switch the insertion mode to "after head". In either
                case, abort these steps. (innerHTML case) */
            } elseif ($node->nodeName === 'html') {
                $this->mode = ($this->head_pointer === null)
                    ? self::BEFOR_HEAD
                    : self::AFTER_HEAD;

                break;

                /* 15. If last is true, then set the insertion mode to "in body"
                and    abort these steps. (innerHTML case) */
            } elseif ($last) {
                $this->mode = self::IN_BODY;
                break;
            }
        }
    }

    private function closeCell()
    {
        /* If the stack of open elements has a td or th element in table scope,
        then act as if an end tag token with that tag name had been seen. */
        foreach (array('td', 'th') as $cell) {
            if ($this->elementInScope($cell, true)) {
                $this->inCell(
                    array(
                        'name' => $cell,
                        'type' => HTML5::ENDTAG
                    )
                );

                break;
            }
        }
    }

    public function save()
    {
        return $this->dom;
    }
}
<?php

/**
 * Definition of the purified HTML that describes allowed children,
 * attributes, and many other things.
 *
 * Conventions:
 *
 * All member variables that are prefixed with info
 * (including the main $info array) are used by HTML Purifier internals
 * and should not be directly edited when customizing the HTMLDefinition.
 * They can usually be set via configuration directives or custom
 * modules.
 *
 * On the other hand, member variables without the info prefix are used
 * internally by the HTMLDefinition and MUST NOT be used by other HTML
 * Purifier internals. Many of them, however, are public, and may be
 * edited by userspace code to tweak the behavior of HTMLDefinition.
 *
 * @note This class is inspected by Printer_HTMLDefinition; please
 *       update that class if things here change.
 *
 * @warning Directives that change this object's structure must be in
 *          the HTML or Attr namespace!
 */
class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition
{

    // FULLY-PUBLIC VARIABLES ---------------------------------------------

    /**
     * Associative array of element names to HTMLPurifier_ElementDef.
     * @type HTMLPurifier_ElementDef[]
     */
    public $info = array();

    /**
     * Associative array of global attribute name to attribute definition.
     * @type array
     */
    public $info_global_attr = array();

    /**
     * String name of parent element HTML will be going into.
     * @type string
     */
    public $info_parent = 'div';

    /**
     * Definition for parent element, allows parent element to be a
     * tag that's not allowed inside the HTML fragment.
     * @type HTMLPurifier_ElementDef
     */
    public $info_parent_def;

    /**
     * String name of element used to wrap inline elements in block context.
     * @type string
     * @note This is rarely used except for BLOCKQUOTEs in strict mode
     */
    public $info_block_wrapper = 'p';

    /**
     * Associative array of deprecated tag name to HTMLPurifier_TagTransform.
     * @type array
     */
    public $info_tag_transform = array();

    /**
     * Indexed list of HTMLPurifier_AttrTransform to be performed before validation.
     * @type HTMLPurifier_AttrTransform[]
     */
    public $info_attr_transform_pre = array();

    /**
     * Indexed list of HTMLPurifier_AttrTransform to be performed after validation.
     * @type HTMLPurifier_AttrTransform[]
     */
    public $info_attr_transform_post = array();

    /**
     * Nested lookup array of content set name (Block, Inline) to
     * element name to whether or not it belongs in that content set.
     * @type array
     */
    public $info_content_sets = array();

    /**
     * Indexed list of HTMLPurifier_Injector to be used.
     * @type HTMLPurifier_Injector[]
     */
    public $info_injector = array();

    /**
     * Doctype object
     * @type HTMLPurifier_Doctype
     */
    public $doctype;



    // RAW CUSTOMIZATION STUFF --------------------------------------------

    /**
     * Adds a custom attribute to a pre-existing element
     * @note This is strictly convenience, and does not have a corresponding
     *       method in HTMLPurifier_HTMLModule
     * @param string $element_name Element name to add attribute to
     * @param string $attr_name Name of attribute
     * @param mixed $def Attribute definition, can be string or object, see
     *             HTMLPurifier_AttrTypes for details
     */
    public function addAttribute($element_name, $attr_name, $def)
    {
        $module = $this->getAnonymousModule();
        if (!isset($module->info[$element_name])) {
            $element = $module->addBlankElement($element_name);
        } else {
            $element = $module->info[$element_name];
        }
        $element->attr[$attr_name] = $def;
    }

    /**
     * Adds a custom element to your HTML definition
     * @see HTMLPurifier_HTMLModule::addElement() for detailed
     *       parameter and return value descriptions.
     */
    public function addElement($element_name, $type, $contents, $attr_collections, $attributes = array())
    {
        $module = $this->getAnonymousModule();
        // assume that if the user is calling this, the element
        // is safe. This may not be a good idea
        $element = $module->addElement($element_name, $type, $contents, $attr_collections, $attributes);
        return $element;
    }

    /**
     * Adds a blank element to your HTML definition, for overriding
     * existing behavior
     * @param string $element_name
     * @return HTMLPurifier_ElementDef
     * @see HTMLPurifier_HTMLModule::addBlankElement() for detailed
     *       parameter and return value descriptions.
     */
    public function addBlankElement($element_name)
    {
        $module  = $this->getAnonymousModule();
        $element = $module->addBlankElement($element_name);
        return $element;
    }

    /**
     * Retrieves a reference to the anonymous module, so you can
     * bust out advanced features without having to make your own
     * module.
     * @return HTMLPurifier_HTMLModule
     */
    public function getAnonymousModule()
    {
        if (!$this->_anonModule) {
            $this->_anonModule = new HTMLPurifier_HTMLModule();
            $this->_anonModule->name = 'Anonymous';
        }
        return $this->_anonModule;
    }

    private $_anonModule = null;

    // PUBLIC BUT INTERNAL VARIABLES --------------------------------------

    /**
     * @type string
     */
    public $type = 'HTML';

    /**
     * @type HTMLPurifier_HTMLModuleManager
     */
    public $manager;

    /**
     * Performs low-cost, preliminary initialization.
     */
    public function __construct()
    {
        $this->manager = new HTMLPurifier_HTMLModuleManager();
    }

    /**
     * @param HTMLPurifier_Config $config
     */
    protected function doSetup($config)
    {
        $this->processModules($config);
        $this->setupConfigStuff($config);
        unset($this->manager);

        // cleanup some of the element definitions
        foreach ($this->info as $k => $v) {
            unset($this->info[$k]->content_model);
            unset($this->info[$k]->content_model_type);
        }
    }

    /**
     * Extract out the information from the manager
     * @param HTMLPurifier_Config $config
     */
    protected function processModules($config)
    {
        if ($this->_anonModule) {
            // for user specific changes
            // this is late-loaded so we don't have to deal with PHP4
            // reference wonky-ness
            $this->manager->addModule($this->_anonModule);
            unset($this->_anonModule);
        }

        $this->manager->setup($config);
        $this->doctype = $this->manager->doctype;

        foreach ($this->manager->modules as $module) {
            foreach ($module->info_tag_transform as $k => $v) {
                if ($v === false) {
                    unset($this->info_tag_transform[$k]);
                } else {
                    $this->info_tag_transform[$k] = $v;
                }
            }
            foreach ($module->info_attr_transform_pre as $k => $v) {
                if ($v === false) {
                    unset($this->info_attr_transform_pre[$k]);
                } else {
                    $this->info_attr_transform_pre[$k] = $v;
                }
            }
            foreach ($module->info_attr_transform_post as $k => $v) {
                if ($v === false) {
                    unset($this->info_attr_transform_post[$k]);
                } else {
                    $this->info_attr_transform_post[$k] = $v;
                }
            }
            foreach ($module->info_injector as $k => $v) {
                if ($v === false) {
                    unset($this->info_injector[$k]);
                } else {
                    $this->info_injector[$k] = $v;
                }
            }
        }
        $this->info = $this->manager->getElements();
        $this->info_content_sets = $this->manager->contentSets->lookup;
    }

    /**
     * Sets up stuff based on config. We need a better way of doing this.
     * @param HTMLPurifier_Config $config
     */
    protected function setupConfigStuff($config)
    {
        $block_wrapper = $config->get('HTML.BlockWrapper');
        if (isset($this->info_content_sets['Block'][$block_wrapper])) {
            $this->info_block_wrapper = $block_wrapper;
        } else {
            trigger_error(
                'Cannot use non-block element as block wrapper',
                E_USER_ERROR
            );
        }

        $parent = $config->get('HTML.Parent');
        $def = $this->manager->getElement($parent, true);
        if ($def) {
            $this->info_parent = $parent;
            $this->info_parent_def = $def;
        } else {
            trigger_error(
                'Cannot use unrecognized element as parent',
                E_USER_ERROR
            );
            $this->info_parent_def = $this->manager->getElement($this->info_parent, true);
        }

        // support template text
        $support = "(for information on implementing this, see the support forums) ";

        // setup allowed elements -----------------------------------------

        $allowed_elements = $config->get('HTML.AllowedElements');
        $allowed_attributes = $config->get('HTML.AllowedAttributes'); // retrieve early

        if (!is_array($allowed_elements) && !is_array($allowed_attributes)) {
            $allowed = $config->get('HTML.Allowed');
            if (is_string($allowed)) {
                list($allowed_elements, $allowed_attributes) = $this->parseTinyMCEAllowedList($allowed);
            }
        }

        if (is_array($allowed_elements)) {
            foreach ($this->info as $name => $d) {
                if (!isset($allowed_elements[$name])) {
                    unset($this->info[$name]);
                }
                unset($allowed_elements[$name]);
            }
            // emit errors
            foreach ($allowed_elements as $element => $d) {
                $element = htmlspecialchars($element); // PHP doesn't escape errors, be careful!
                trigger_error("Element '$element' is not supported $support", E_USER_WARNING);
            }
        }

        // setup allowed attributes ---------------------------------------

        $allowed_attributes_mutable = $allowed_attributes; // by copy!
        if (is_array($allowed_attributes)) {
            // This actually doesn't do anything, since we went away from
            // global attributes. It's possible that userland code uses
            // it, but HTMLModuleManager doesn't!
            foreach ($this->info_global_attr as $attr => $x) {
                $keys = array($attr, "*@$attr", "*.$attr");
                $delete = true;
                foreach ($keys as $key) {
                    if ($delete && isset($allowed_attributes[$key])) {
                        $delete = false;
                    }
                    if (isset($allowed_attributes_mutable[$key])) {
                        unset($allowed_attributes_mutable[$key]);
                    }
                }
                if ($delete) {
                    unset($this->info_global_attr[$attr]);
                }
            }

            foreach ($this->info as $tag => $info) {
                foreach ($info->attr as $attr => $x) {
                    $keys = array("$tag@$attr", $attr, "*@$attr", "$tag.$attr", "*.$attr");
                    $delete = true;
                    foreach ($keys as $key) {
                        if ($delete && isset($allowed_attributes[$key])) {
                            $delete = false;
                        }
                        if (isset($allowed_attributes_mutable[$key])) {
                            unset($allowed_attributes_mutable[$key]);
                        }
                    }
                    if ($delete) {
                        if ($this->info[$tag]->attr[$attr]->required) {
                            trigger_error(
                                "Required attribute '$attr' in element '$tag' " .
                                "was not allowed, which means '$tag' will not be allowed either",
                                E_USER_WARNING
                            );
                        }
                        unset($this->info[$tag]->attr[$attr]);
                    }
                }
            }
            // emit errors
            foreach ($allowed_attributes_mutable as $elattr => $d) {
                $bits = preg_split('/[.@]/', $elattr, 2);
                $c = count($bits);
                switch ($c) {
                    case 2:
                        if ($bits[0] !== '*') {
                            $element = htmlspecialchars($bits[0]);
                            $attribute = htmlspecialchars($bits[1]);
                            if (!isset($this->info[$element])) {
                                trigger_error(
                                    "Cannot allow attribute '$attribute' if element " .
                                    "'$element' is not allowed/supported $support"
                                );
                            } else {
                                trigger_error(
                                    "Attribute '$attribute' in element '$element' not supported $support",
                                    E_USER_WARNING
                                );
                            }
                            break;
                        }
                        // otherwise fall through
                    case 1:
                        $attribute = htmlspecialchars($bits[0]);
                        trigger_error(
                            "Global attribute '$attribute' is not ".
                            "supported in any elements $support",
                            E_USER_WARNING
                        );
                        break;
                }
            }
        }

        // setup forbidden elements ---------------------------------------

        $forbidden_elements   = $config->get('HTML.ForbiddenElements');
        $forbidden_attributes = $config->get('HTML.ForbiddenAttributes');

        foreach ($this->info as $tag => $info) {
            if (isset($forbidden_elements[$tag])) {
                unset($this->info[$tag]);
                continue;
            }
            foreach ($info->attr as $attr => $x) {
                if (isset($forbidden_attributes["$tag@$attr"]) ||
                    isset($forbidden_attributes["*@$attr"]) ||
                    isset($forbidden_attributes[$attr])
                ) {
                    unset($this->info[$tag]->attr[$attr]);
                    continue;
                } elseif (isset($forbidden_attributes["$tag.$attr"])) { // this segment might get removed eventually
                    // $tag.$attr are not user supplied, so no worries!
                    trigger_error(
                        "Error with $tag.$attr: tag.attr syntax not supported for " .
                        "HTML.ForbiddenAttributes; use tag@attr instead",
                        E_USER_WARNING
                    );
                }
            }
        }
        foreach ($forbidden_attributes as $key => $v) {
            if (strlen($key) < 2) {
                continue;
            }
            if ($key[0] != '*') {
                continue;
            }
            if ($key[1] == '.') {
                trigger_error(
                    "Error with $key: *.attr syntax not supported for HTML.ForbiddenAttributes; use attr instead",
                    E_USER_WARNING
                );
            }
        }

        // setup injectors -----------------------------------------------------
        foreach ($this->info_injector as $i => $injector) {
            if ($injector->checkNeeded($config) !== false) {
                // remove injector that does not have it's required
                // elements/attributes present, and is thus not needed.
                unset($this->info_injector[$i]);
            }
        }
    }

    /**
     * Parses a TinyMCE-flavored Allowed Elements and Attributes list into
     * separate lists for processing. Format is element[attr1|attr2],element2...
     * @warning Although it's largely drawn from TinyMCE's implementation,
     *      it is different, and you'll probably have to modify your lists
     * @param array $list String list to parse
     * @return array
     * @todo Give this its own class, probably static interface
     */
    public function parseTinyMCEAllowedList($list)
    {
        $list = str_replace(array(' ', "\t"), '', $list);

        $elements = array();
        $attributes = array();

        $chunks = preg_split('/(,|[\n\r]+)/', $list);
        foreach ($chunks as $chunk) {
            if (empty($chunk)) {
                continue;
            }
            // remove TinyMCE element control characters
            if (!strpos($chunk, '[')) {
                $element = $chunk;
                $attr = false;
            } else {
                list($element, $attr) = explode('[', $chunk);
            }
            if ($element !== '*') {
                $elements[$element] = true;
            }
            if (!$attr) {
                continue;
            }
            $attr = substr($attr, 0, strlen($attr) - 1); // remove trailing ]
            $attr = explode('|', $attr);
            foreach ($attr as $key) {
                $attributes["$element.$key"] = true;
            }
        }
        return array($elements, $attributes);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Supertype for classes that define a strategy for modifying/purifying tokens.
 *
 * While HTMLPurifier's core purpose is fixing HTML into something proper,
 * strategies provide plug points for extra configuration or even extra
 * features, such as custom tags, custom parsing of text, etc.
 */


abstract class HTMLPurifier_Strategy
{

    /**
     * Executes the strategy on the tokens.
     *
     * @param HTMLPurifier_Token[] $tokens Array of HTMLPurifier_Token objects to be operated on.
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return HTMLPurifier_Token[] Processed array of token objects.
     */
    abstract public function execute($tokens, $config, $context);
}

// vim: et sw=4 sts=4
<?php

/**
 * This variable parser uses PHP's internal code engine. Because it does
 * this, it can represent all inputs; however, it is dangerous and cannot
 * be used by users.
 */
class HTMLPurifier_VarParser_Native extends HTMLPurifier_VarParser
{

    /**
     * @param mixed $var
     * @param int $type
     * @param bool $allow_null
     * @return null|string
     */
    protected function parseImplementation($var, $type, $allow_null)
    {
        return $this->evalExpression($var);
    }

    /**
     * @param string $expr
     * @return mixed
     * @throws HTMLPurifier_VarParserException
     */
    protected function evalExpression($expr)
    {
        $var = null;
        $result = eval("\$var = $expr;");
        if ($result === false) {
            throw new HTMLPurifier_VarParserException("Fatal error in evaluated code");
        }
        return $var;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Performs safe variable parsing based on types which can be used by
 * users. This may not be able to represent all possible data inputs,
 * however.
 */
class HTMLPurifier_VarParser_Flexible extends HTMLPurifier_VarParser
{
    /**
     * @param mixed $var
     * @param int $type
     * @param bool $allow_null
     * @return array|bool|float|int|mixed|null|string
     * @throws HTMLPurifier_VarParserException
     */
    protected function parseImplementation($var, $type, $allow_null)
    {
        if ($allow_null && $var === null) {
            return null;
        }
        switch ($type) {
            // Note: if code "breaks" from the switch, it triggers a generic
            // exception to be thrown. Specific errors can be specifically
            // done here.
            case self::MIXED:
            case self::ISTRING:
            case self::STRING:
            case self::TEXT:
            case self::ITEXT:
                return $var;
            case self::INT:
                if (is_string($var) && ctype_digit($var)) {
                    $var = (int)$var;
                }
                return $var;
            case self::FLOAT:
                if ((is_string($var) && is_numeric($var)) || is_int($var)) {
                    $var = (float)$var;
                }
                return $var;
            case self::BOOL:
                if (is_int($var) && ($var === 0 || $var === 1)) {
                    $var = (bool)$var;
                } elseif (is_string($var)) {
                    if ($var == 'on' || $var == 'true' || $var == '1') {
                        $var = true;
                    } elseif ($var == 'off' || $var == 'false' || $var == '0') {
                        $var = false;
                    } else {
                        throw new HTMLPurifier_VarParserException("Unrecognized value '$var' for $type");
                    }
                }
                return $var;
            case self::ALIST:
            case self::HASH:
            case self::LOOKUP:
                if (is_string($var)) {
                    // special case: technically, this is an array with
                    // a single empty string item, but having an empty
                    // array is more intuitive
                    if ($var == '') {
                        return array();
                    }
                    if (strpos($var, "\n") === false && strpos($var, "\r") === false) {
                        // simplistic string to array method that only works
                        // for simple lists of tag names or alphanumeric characters
                        $var = explode(',', $var);
                    } else {
                        $var = preg_split('/(,|[\n\r]+)/', $var);
                    }
                    // remove spaces
                    foreach ($var as $i => $j) {
                        $var[$i] = trim($j);
                    }
                    if ($type === self::HASH) {
                        // key:value,key2:value2
                        $nvar = array();
                        foreach ($var as $keypair) {
                            $c = explode(':', $keypair, 2);
                            if (!isset($c[1])) {
                                continue;
                            }
                            $nvar[trim($c[0])] = trim($c[1]);
                        }
                        $var = $nvar;
                    }
                }
                if (!is_array($var)) {
                    break;
                }
                $keys = array_keys($var);
                if ($keys === array_keys($keys)) {
                    if ($type == self::ALIST) {
                        return $var;
                    } elseif ($type == self::LOOKUP) {
                        $new = array();
                        foreach ($var as $key) {
                            $new[$key] = true;
                        }
                        return $new;
                    } else {
                        break;
                    }
                }
                if ($type === self::ALIST) {
                    trigger_error("Array list did not have consecutive integer indexes", E_USER_WARNING);
                    return array_values($var);
                }
                if ($type === self::LOOKUP) {
                    foreach ($var as $key => $value) {
                        if ($value !== true) {
                            trigger_error(
                                "Lookup array has non-true value at key '$key'; " .
                                "maybe your input array was not indexed numerically",
                                E_USER_WARNING
                            );
                        }
                        $var[$key] = true;
                    }
                }
                return $var;
            default:
                $this->errorInconsistent(__CLASS__, $type);
        }
        $this->errorGeneric($var, $type);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Class responsible for generating HTMLPurifier_Language objects, managing
 * caching and fallbacks.
 * @note Thanks to MediaWiki for the general logic, although this version
 *       has been entirely rewritten
 * @todo Serialized cache for languages
 */
class HTMLPurifier_LanguageFactory
{

    /**
     * Cache of language code information used to load HTMLPurifier_Language objects.
     * Structure is: $factory->cache[$language_code][$key] = $value
     * @type array
     */
    public $cache;

    /**
     * Valid keys in the HTMLPurifier_Language object. Designates which
     * variables to slurp out of a message file.
     * @type array
     */
    public $keys = array('fallback', 'messages', 'errorNames');

    /**
     * Instance to validate language codes.
     * @type HTMLPurifier_AttrDef_Lang
     *
     */
    protected $validator;

    /**
     * Cached copy of dirname(__FILE__), directory of current file without
     * trailing slash.
     * @type string
     */
    protected $dir;

    /**
     * Keys whose contents are a hash map and can be merged.
     * @type array
     */
    protected $mergeable_keys_map = array('messages' => true, 'errorNames' => true);

    /**
     * Keys whose contents are a list and can be merged.
     * @value array lookup
     */
    protected $mergeable_keys_list = array();

    /**
     * Retrieve sole instance of the factory.
     * @param HTMLPurifier_LanguageFactory $prototype Optional prototype to overload sole instance with,
     *                   or bool true to reset to default factory.
     * @return HTMLPurifier_LanguageFactory
     */
    public static function instance($prototype = null)
    {
        static $instance = null;
        if ($prototype !== null) {
            $instance = $prototype;
        } elseif ($instance === null || $prototype == true) {
            $instance = new HTMLPurifier_LanguageFactory();
            $instance->setup();
        }
        return $instance;
    }

    /**
     * Sets up the singleton, much like a constructor
     * @note Prevents people from getting this outside of the singleton
     */
    public function setup()
    {
        $this->validator = new HTMLPurifier_AttrDef_Lang();
        $this->dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier';
    }

    /**
     * Creates a language object, handles class fallbacks
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @param bool|string $code Code to override configuration with. Private parameter.
     * @return HTMLPurifier_Language
     */
    public function create($config, $context, $code = false)
    {
        // validate language code
        if ($code === false) {
            $code = $this->validator->validate(
                $config->get('Core.Language'),
                $config,
                $context
            );
        } else {
            $code = $this->validator->validate($code, $config, $context);
        }
        if ($code === false) {
            $code = 'en'; // malformed code becomes English
        }

        $pcode = str_replace('-', '_', $code); // make valid PHP classname
        static $depth = 0; // recursion protection

        if ($code == 'en') {
            $lang = new HTMLPurifier_Language($config, $context);
        } else {
            $class = 'HTMLPurifier_Language_' . $pcode;
            $file  = $this->dir . '/Language/classes/' . $code . '.php';
            if (file_exists($file) || class_exists($class, false)) {
                $lang = new $class($config, $context);
            } else {
                // Go fallback
                $raw_fallback = $this->getFallbackFor($code);
                $fallback = $raw_fallback ? $raw_fallback : 'en';
                $depth++;
                $lang = $this->create($config, $context, $fallback);
                if (!$raw_fallback) {
                    $lang->error = true;
                }
                $depth--;
            }
        }
        $lang->code = $code;
        return $lang;
    }

    /**
     * Returns the fallback language for language
     * @note Loads the original language into cache
     * @param string $code language code
     * @return string|bool
     */
    public function getFallbackFor($code)
    {
        $this->loadLanguage($code);
        return $this->cache[$code]['fallback'];
    }

    /**
     * Loads language into the cache, handles message file and fallbacks
     * @param string $code language code
     */
    public function loadLanguage($code)
    {
        static $languages_seen = array(); // recursion guard

        // abort if we've already loaded it
        if (isset($this->cache[$code])) {
            return;
        }

        // generate filename
        $filename = $this->dir . '/Language/messages/' . $code . '.php';

        // default fallback : may be overwritten by the ensuing include
        $fallback = ($code != 'en') ? 'en' : false;

        // load primary localisation
        if (!file_exists($filename)) {
            // skip the include: will rely solely on fallback
            $filename = $this->dir . '/Language/messages/en.php';
            $cache = array();
        } else {
            include $filename;
            $cache = compact($this->keys);
        }

        // load fallback localisation
        if (!empty($fallback)) {

            // infinite recursion guard
            if (isset($languages_seen[$code])) {
                trigger_error(
                    'Circular fallback reference in language ' .
                    $code,
                    E_USER_ERROR
                );
                $fallback = 'en';
            }
            $language_seen[$code] = true;

            // load the fallback recursively
            $this->loadLanguage($fallback);
            $fallback_cache = $this->cache[$fallback];

            // merge fallback with current language
            foreach ($this->keys as $key) {
                if (isset($cache[$key]) && isset($fallback_cache[$key])) {
                    if (isset($this->mergeable_keys_map[$key])) {
                        $cache[$key] = $cache[$key] + $fallback_cache[$key];
                    } elseif (isset($this->mergeable_keys_list[$key])) {
                        $cache[$key] = array_merge($fallback_cache[$key], $cache[$key]);
                    }
                } else {
                    $cache[$key] = $fallback_cache[$key];
                }
            }
        }

        // save to cache for later retrieval
        $this->cache[$code] = $cache;
        return;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Chainable filters for custom URI processing.
 *
 * These filters can perform custom actions on a URI filter object,
 * including transformation or blacklisting.  A filter named Foo
 * must have a corresponding configuration directive %URI.Foo,
 * unless always_load is specified to be true.
 *
 * The following contexts may be available while URIFilters are being
 * processed:
 *
 *      - EmbeddedURI: true if URI is an embedded resource that will
 *        be loaded automatically on page load
 *      - CurrentToken: a reference to the token that is currently
 *        being processed
 *      - CurrentAttr: the name of the attribute that is currently being
 *        processed
 *      - CurrentCSSProperty: the name of the CSS property that is
 *        currently being processed (if applicable)
 *
 * @warning This filter is called before scheme object validation occurs.
 *          Make sure, if you require a specific scheme object, you
 *          you check that it exists. This allows filters to convert
 *          proprietary URI schemes into regular ones.
 */
abstract class HTMLPurifier_URIFilter
{

    /**
     * Unique identifier of filter.
     * @type string
     */
    public $name;

    /**
     * True if this filter should be run after scheme validation.
     * @type bool
     */
    public $post = false;

    /**
     * True if this filter should always be loaded.
     * This permits a filter to be named Foo without the corresponding
     * %URI.Foo directive existing.
     * @type bool
     */
    public $always_load = false;

    /**
     * Performs initialization for the filter.  If the filter returns
     * false, this means that it shouldn't be considered active.
     * @param HTMLPurifier_Config $config
     * @return bool
     */
    public function prepare($config)
    {
        return true;
    }

    /**
     * Filter a URI object
     * @param HTMLPurifier_URI $uri Reference to URI object variable
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool Whether or not to continue processing: false indicates
     *         URL is no good, true indicates continue processing. Note that
     *         all changes are committed directly on the URI object
     */
    abstract public function filter(&$uri, $config, $context);
}

// vim: et sw=4 sts=4
<?php

class HTMLPurifier_HTMLModuleManager
{

    /**
     * @type HTMLPurifier_DoctypeRegistry
     */
    public $doctypes;

    /**
     * Instance of current doctype.
     * @type string
     */
    public $doctype;

    /**
     * @type HTMLPurifier_AttrTypes
     */
    public $attrTypes;

    /**
     * Active instances of modules for the specified doctype are
     * indexed, by name, in this array.
     * @type HTMLPurifier_HTMLModule[]
     */
    public $modules = array();

    /**
     * Array of recognized HTMLPurifier_HTMLModule instances,
     * indexed by module's class name. This array is usually lazy loaded, but a
     * user can overload a module by pre-emptively registering it.
     * @type HTMLPurifier_HTMLModule[]
     */
    public $registeredModules = array();

    /**
     * List of extra modules that were added by the user
     * using addModule(). These get unconditionally merged into the current doctype, whatever
     * it may be.
     * @type HTMLPurifier_HTMLModule[]
     */
    public $userModules = array();

    /**
     * Associative array of element name to list of modules that have
     * definitions for the element; this array is dynamically filled.
     * @type array
     */
    public $elementLookup = array();

    /**
     * List of prefixes we should use for registering small names.
     * @type array
     */
    public $prefixes = array('HTMLPurifier_HTMLModule_');

    /**
     * @type HTMLPurifier_ContentSets
     */
    public $contentSets;

    /**
     * @type HTMLPurifier_AttrCollections
     */
    public $attrCollections;

    /**
     * If set to true, unsafe elements and attributes will be allowed.
     * @type bool
     */
    public $trusted = false;

    public function __construct()
    {
        // editable internal objects
        $this->attrTypes = new HTMLPurifier_AttrTypes();
        $this->doctypes  = new HTMLPurifier_DoctypeRegistry();

        // setup basic modules
        $common = array(
            'CommonAttributes', 'Text', 'Hypertext', 'List',
            'Presentation', 'Edit', 'Bdo', 'Tables', 'Image',
            'StyleAttribute',
            // Unsafe:
            'Scripting', 'Object', 'Forms',
            // Sorta legacy, but present in strict:
            'Name',
        );
        $transitional = array('Legacy', 'Target', 'Iframe');
        $xml = array('XMLCommonAttributes');
        $non_xml = array('NonXMLCommonAttributes');

        // setup basic doctypes
        $this->doctypes->register(
            'HTML 4.01 Transitional',
            false,
            array_merge($common, $transitional, $non_xml),
            array('Tidy_Transitional', 'Tidy_Proprietary'),
            array(),
            '-//W3C//DTD HTML 4.01 Transitional//EN',
            'http://www.w3.org/TR/html4/loose.dtd'
        );

        $this->doctypes->register(
            'HTML 4.01 Strict',
            false,
            array_merge($common, $non_xml),
            array('Tidy_Strict', 'Tidy_Proprietary', 'Tidy_Name'),
            array(),
            '-//W3C//DTD HTML 4.01//EN',
            'http://www.w3.org/TR/html4/strict.dtd'
        );

        $this->doctypes->register(
            'XHTML 1.0 Transitional',
            true,
            array_merge($common, $transitional, $xml, $non_xml),
            array('Tidy_Transitional', 'Tidy_XHTML', 'Tidy_Proprietary', 'Tidy_Name'),
            array(),
            '-//W3C//DTD XHTML 1.0 Transitional//EN',
            'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'
        );

        $this->doctypes->register(
            'XHTML 1.0 Strict',
            true,
            array_merge($common, $xml, $non_xml),
            array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Strict', 'Tidy_Proprietary', 'Tidy_Name'),
            array(),
            '-//W3C//DTD XHTML 1.0 Strict//EN',
            'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'
        );

        $this->doctypes->register(
            'XHTML 1.1',
            true,
            // Iframe is a real XHTML 1.1 module, despite being
            // "transitional"!
            array_merge($common, $xml, array('Ruby', 'Iframe')),
            array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Proprietary', 'Tidy_Strict', 'Tidy_Name'), // Tidy_XHTML1_1
            array(),
            '-//W3C//DTD XHTML 1.1//EN',
            'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd'
        );

    }

    /**
     * Registers a module to the recognized module list, useful for
     * overloading pre-existing modules.
     * @param $module Mixed: string module name, with or without
     *                HTMLPurifier_HTMLModule prefix, or instance of
     *                subclass of HTMLPurifier_HTMLModule.
     * @param $overload Boolean whether or not to overload previous modules.
     *                  If this is not set, and you do overload a module,
     *                  HTML Purifier will complain with a warning.
     * @note This function will not call autoload, you must instantiate
     *       (and thus invoke) autoload outside the method.
     * @note If a string is passed as a module name, different variants
     *       will be tested in this order:
     *          - Check for HTMLPurifier_HTMLModule_$name
     *          - Check all prefixes with $name in order they were added
     *          - Check for literal object name
     *          - Throw fatal error
     *       If your object name collides with an internal class, specify
     *       your module manually. All modules must have been included
     *       externally: registerModule will not perform inclusions for you!
     */
    public function registerModule($module, $overload = false)
    {
        if (is_string($module)) {
            // attempt to load the module
            $original_module = $module;
            $ok = false;
            foreach ($this->prefixes as $prefix) {
                $module = $prefix . $original_module;
                if (class_exists($module)) {
                    $ok = true;
                    break;
                }
            }
            if (!$ok) {
                $module = $original_module;
                if (!class_exists($module)) {
                    trigger_error(
                        $original_module . ' module does not exist',
                        E_USER_ERROR
                    );
                    return;
                }
            }
            $module = new $module();
        }
        if (empty($module->name)) {
            trigger_error('Module instance of ' . get_class($module) . ' must have name');
            return;
        }
        if (!$overload && isset($this->registeredModules[$module->name])) {
            trigger_error('Overloading ' . $module->name . ' without explicit overload parameter', E_USER_WARNING);
        }
        $this->registeredModules[$module->name] = $module;
    }

    /**
     * Adds a module to the current doctype by first registering it,
     * and then tacking it on to the active doctype
     */
    public function addModule($module)
    {
        $this->registerModule($module);
        if (is_object($module)) {
            $module = $module->name;
        }
        $this->userModules[] = $module;
    }

    /**
     * Adds a class prefix that registerModule() will use to resolve a
     * string name to a concrete class
     */
    public function addPrefix($prefix)
    {
        $this->prefixes[] = $prefix;
    }

    /**
     * Performs processing on modules, after being called you may
     * use getElement() and getElements()
     * @param HTMLPurifier_Config $config
     */
    public function setup($config)
    {
        $this->trusted = $config->get('HTML.Trusted');

        // generate
        $this->doctype = $this->doctypes->make($config);
        $modules = $this->doctype->modules;

        // take out the default modules that aren't allowed
        $lookup = $config->get('HTML.AllowedModules');
        $special_cases = $config->get('HTML.CoreModules');

        if (is_array($lookup)) {
            foreach ($modules as $k => $m) {
                if (isset($special_cases[$m])) {
                    continue;
                }
                if (!isset($lookup[$m])) {
                    unset($modules[$k]);
                }
            }
        }

        // custom modules
        if ($config->get('HTML.Proprietary')) {
            $modules[] = 'Proprietary';
        }
        if ($config->get('HTML.SafeObject')) {
            $modules[] = 'SafeObject';
        }
        if ($config->get('HTML.SafeEmbed')) {
            $modules[] = 'SafeEmbed';
        }
        if ($config->get('HTML.SafeScripting') !== array()) {
            $modules[] = 'SafeScripting';
        }
        if ($config->get('HTML.Nofollow')) {
            $modules[] = 'Nofollow';
        }
        if ($config->get('HTML.TargetBlank')) {
            $modules[] = 'TargetBlank';
        }
        // NB: HTML.TargetNoreferrer and HTML.TargetNoopener must be AFTER HTML.TargetBlank
        // so that its post-attr-transform gets run afterwards.
        if ($config->get('HTML.TargetNoreferrer')) {
            $modules[] = 'TargetNoreferrer';
        }
        if ($config->get('HTML.TargetNoopener')) {
            $modules[] = 'TargetNoopener';
        }

        // merge in custom modules
        $modules = array_merge($modules, $this->userModules);

        foreach ($modules as $module) {
            $this->processModule($module);
            $this->modules[$module]->setup($config);
        }

        foreach ($this->doctype->tidyModules as $module) {
            $this->processModule($module);
            $this->modules[$module]->setup($config);
        }

        // prepare any injectors
        foreach ($this->modules as $module) {
            $n = array();
            foreach ($module->info_injector as $injector) {
                if (!is_object($injector)) {
                    $class = "HTMLPurifier_Injector_$injector";
                    $injector = new $class;
                }
                $n[$injector->name] = $injector;
            }
            $module->info_injector = $n;
        }

        // setup lookup table based on all valid modules
        foreach ($this->modules as $module) {
            foreach ($module->info as $name => $def) {
                if (!isset($this->elementLookup[$name])) {
                    $this->elementLookup[$name] = array();
                }
                $this->elementLookup[$name][] = $module->name;
            }
        }

        // note the different choice
        $this->contentSets = new HTMLPurifier_ContentSets(
            // content set assembly deals with all possible modules,
            // not just ones deemed to be "safe"
            $this->modules
        );
        $this->attrCollections = new HTMLPurifier_AttrCollections(
            $this->attrTypes,
            // there is no way to directly disable a global attribute,
            // but using AllowedAttributes or simply not including
            // the module in your custom doctype should be sufficient
            $this->modules
        );
    }

    /**
     * Takes a module and adds it to the active module collection,
     * registering it if necessary.
     */
    public function processModule($module)
    {
        if (!isset($this->registeredModules[$module]) || is_object($module)) {
            $this->registerModule($module);
        }
        $this->modules[$module] = $this->registeredModules[$module];
    }

    /**
     * Retrieves merged element definitions.
     * @return Array of HTMLPurifier_ElementDef
     */
    public function getElements()
    {
        $elements = array();
        foreach ($this->modules as $module) {
            if (!$this->trusted && !$module->safe) {
                continue;
            }
            foreach ($module->info as $name => $v) {
                if (isset($elements[$name])) {
                    continue;
                }
                $elements[$name] = $this->getElement($name);
            }
        }

        // remove dud elements, this happens when an element that
        // appeared to be safe actually wasn't
        foreach ($elements as $n => $v) {
            if ($v === false) {
                unset($elements[$n]);
            }
        }

        return $elements;

    }

    /**
     * Retrieves a single merged element definition
     * @param string $name Name of element
     * @param bool $trusted Boolean trusted overriding parameter: set to true
     *                 if you want the full version of an element
     * @return HTMLPurifier_ElementDef Merged HTMLPurifier_ElementDef
     * @note You may notice that modules are getting iterated over twice (once
     *       in getElements() and once here). This
     *       is because
     */
    public function getElement($name, $trusted = null)
    {
        if (!isset($this->elementLookup[$name])) {
            return false;
        }

        // setup global state variables
        $def = false;
        if ($trusted === null) {
            $trusted = $this->trusted;
        }

        // iterate through each module that has registered itself to this
        // element
        foreach ($this->elementLookup[$name] as $module_name) {
            $module = $this->modules[$module_name];

            // refuse to create/merge from a module that is deemed unsafe--
            // pretend the module doesn't exist--when trusted mode is not on.
            if (!$trusted && !$module->safe) {
                continue;
            }

            // clone is used because, ideally speaking, the original
            // definition should not be modified. Usually, this will
            // make no difference, but for consistency's sake
            $new_def = clone $module->info[$name];

            if (!$def && $new_def->standalone) {
                $def = $new_def;
            } elseif ($def) {
                // This will occur even if $new_def is standalone. In practice,
                // this will usually result in a full replacement.
                $def->mergeIn($new_def);
            } else {
                // :TODO:
                // non-standalone definitions that don't have a standalone
                // to merge into could be deferred to the end
                // HOWEVER, it is perfectly valid for a non-standalone
                // definition to lack a standalone definition, even
                // after all processing: this allows us to safely
                // specify extra attributes for elements that may not be
                // enabled all in one place.  In particular, this might
                // be the case for trusted elements.  WARNING: care must
                // be taken that the /extra/ definitions are all safe.
                continue;
            }

            // attribute value expansions
            $this->attrCollections->performInclusions($def->attr);
            $this->attrCollections->expandIdentifiers($def->attr, $this->attrTypes);

            // descendants_are_inline, for ChildDef_Chameleon
            if (is_string($def->content_model) &&
                strpos($def->content_model, 'Inline') !== false) {
                if ($name != 'del' && $name != 'ins') {
                    // this is for you, ins/del
                    $def->descendants_are_inline = true;
                }
            }

            $this->contentSets->generateChildDef($def, $module);
        }

        // This can occur if there is a blank definition, but no base to
        // mix it in with
        if (!$def) {
            return false;
        }

        // add information on required attributes
        foreach ($def->attr as $attr_name => $attr_def) {
            if ($attr_def->required) {
                $def->required_attr[] = $attr_name;
            }
        }
        return $def;
    }
}

// vim: et sw=4 sts=4
<?php

class HTMLPurifier_Printer_CSSDefinition extends HTMLPurifier_Printer
{
    /**
     * @type HTMLPurifier_CSSDefinition
     */
    protected $def;

    /**
     * @param HTMLPurifier_Config $config
     * @return string
     */
    public function render($config)
    {
        $this->def = $config->getCSSDefinition();
        $ret = '';

        $ret .= $this->start('div', array('class' => 'HTMLPurifier_Printer'));
        $ret .= $this->start('table');

        $ret .= $this->element('caption', 'Properties ($info)');

        $ret .= $this->start('thead');
        $ret .= $this->start('tr');
        $ret .= $this->element('th', 'Property', array('class' => 'heavy'));
        $ret .= $this->element('th', 'Definition', array('class' => 'heavy', 'style' => 'width:auto;'));
        $ret .= $this->end('tr');
        $ret .= $this->end('thead');

        ksort($this->def->info);
        foreach ($this->def->info as $property => $obj) {
            $name = $this->getClass($obj, 'AttrDef_');
            $ret .= $this->row($property, $name);
        }

        $ret .= $this->end('table');
        $ret .= $this->end('div');

        return $ret;
    }
}

// vim: et sw=4 sts=4
<?php

class HTMLPurifier_Printer_HTMLDefinition extends HTMLPurifier_Printer
{

    /**
     * @type HTMLPurifier_HTMLDefinition, for easy access
     */
    protected $def;

    /**
     * @param HTMLPurifier_Config $config
     * @return string
     */
    public function render($config)
    {
        $ret = '';
        $this->config =& $config;

        $this->def = $config->getHTMLDefinition();

        $ret .= $this->start('div', array('class' => 'HTMLPurifier_Printer'));

        $ret .= $this->renderDoctype();
        $ret .= $this->renderEnvironment();
        $ret .= $this->renderContentSets();
        $ret .= $this->renderInfo();

        $ret .= $this->end('div');

        return $ret;
    }

    /**
     * Renders the Doctype table
     * @return string
     */
    protected function renderDoctype()
    {
        $doctype = $this->def->doctype;
        $ret = '';
        $ret .= $this->start('table');
        $ret .= $this->element('caption', 'Doctype');
        $ret .= $this->row('Name', $doctype->name);
        $ret .= $this->row('XML', $doctype->xml ? 'Yes' : 'No');
        $ret .= $this->row('Default Modules', implode($doctype->modules, ', '));
        $ret .= $this->row('Default Tidy Modules', implode($doctype->tidyModules, ', '));
        $ret .= $this->end('table');
        return $ret;
    }


    /**
     * Renders environment table, which is miscellaneous info
     * @return string
     */
    protected function renderEnvironment()
    {
        $def = $this->def;

        $ret = '';

        $ret .= $this->start('table');
        $ret .= $this->element('caption', 'Environment');

        $ret .= $this->row('Parent of fragment', $def->info_parent);
        $ret .= $this->renderChildren($def->info_parent_def->child);
        $ret .= $this->row('Block wrap name', $def->info_block_wrapper);

        $ret .= $this->start('tr');
        $ret .= $this->element('th', 'Global attributes');
        $ret .= $this->element('td', $this->listifyAttr($def->info_global_attr), null, 0);
        $ret .= $this->end('tr');

        $ret .= $this->start('tr');
        $ret .= $this->element('th', 'Tag transforms');
        $list = array();
        foreach ($def->info_tag_transform as $old => $new) {
            $new = $this->getClass($new, 'TagTransform_');
            $list[] = "<$old> with $new";
        }
        $ret .= $this->element('td', $this->listify($list));
        $ret .= $this->end('tr');

        $ret .= $this->start('tr');
        $ret .= $this->element('th', 'Pre-AttrTransform');
        $ret .= $this->element('td', $this->listifyObjectList($def->info_attr_transform_pre));
        $ret .= $this->end('tr');

        $ret .= $this->start('tr');
        $ret .= $this->element('th', 'Post-AttrTransform');
        $ret .= $this->element('td', $this->listifyObjectList($def->info_attr_transform_post));
        $ret .= $this->end('tr');

        $ret .= $this->end('table');
        return $ret;
    }

    /**
     * Renders the Content Sets table
     * @return string
     */
    protected function renderContentSets()
    {
        $ret = '';
        $ret .= $this->start('table');
        $ret .= $this->element('caption', 'Content Sets');
        foreach ($this->def->info_content_sets as $name => $lookup) {
            $ret .= $this->heavyHeader($name);
            $ret .= $this->start('tr');
            $ret .= $this->element('td', $this->listifyTagLookup($lookup));
            $ret .= $this->end('tr');
        }
        $ret .= $this->end('table');
        return $ret;
    }

    /**
     * Renders the Elements ($info) table
     * @return string
     */
    protected function renderInfo()
    {
        $ret = '';
        $ret .= $this->start('table');
        $ret .= $this->element('caption', 'Elements ($info)');
        ksort($this->def->info);
        $ret .= $this->heavyHeader('Allowed tags', 2);
        $ret .= $this->start('tr');
        $ret .= $this->element('td', $this->listifyTagLookup($this->def->info), array('colspan' => 2));
        $ret .= $this->end('tr');
        foreach ($this->def->info as $name => $def) {
            $ret .= $this->start('tr');
            $ret .= $this->element('th', "<$name>", array('class' => 'heavy', 'colspan' => 2));
            $ret .= $this->end('tr');
            $ret .= $this->start('tr');
            $ret .= $this->element('th', 'Inline content');
            $ret .= $this->element('td', $def->descendants_are_inline ? 'Yes' : 'No');
            $ret .= $this->end('tr');
            if (!empty($def->excludes)) {
                $ret .= $this->start('tr');
                $ret .= $this->element('th', 'Excludes');
                $ret .= $this->element('td', $this->listifyTagLookup($def->excludes));
                $ret .= $this->end('tr');
            }
            if (!empty($def->attr_transform_pre)) {
                $ret .= $this->start('tr');
                $ret .= $this->element('th', 'Pre-AttrTransform');
                $ret .= $this->element('td', $this->listifyObjectList($def->attr_transform_pre));
                $ret .= $this->end('tr');
            }
            if (!empty($def->attr_transform_post)) {
                $ret .= $this->start('tr');
                $ret .= $this->element('th', 'Post-AttrTransform');
                $ret .= $this->element('td', $this->listifyObjectList($def->attr_transform_post));
                $ret .= $this->end('tr');
            }
            if (!empty($def->auto_close)) {
                $ret .= $this->start('tr');
                $ret .= $this->element('th', 'Auto closed by');
                $ret .= $this->element('td', $this->listifyTagLookup($def->auto_close));
                $ret .= $this->end('tr');
            }
            $ret .= $this->start('tr');
            $ret .= $this->element('th', 'Allowed attributes');
            $ret .= $this->element('td', $this->listifyAttr($def->attr), array(), 0);
            $ret .= $this->end('tr');

            if (!empty($def->required_attr)) {
                $ret .= $this->row('Required attributes', $this->listify($def->required_attr));
            }

            $ret .= $this->renderChildren($def->child);
        }
        $ret .= $this->end('table');
        return $ret;
    }

    /**
     * Renders a row describing the allowed children of an element
     * @param HTMLPurifier_ChildDef $def HTMLPurifier_ChildDef of pertinent element
     * @return string
     */
    protected function renderChildren($def)
    {
        $context = new HTMLPurifier_Context();
        $ret = '';
        $ret .= $this->start('tr');
        $elements = array();
        $attr = array();
        if (isset($def->elements)) {
            if ($def->type == 'strictblockquote') {
                $def->validateChildren(array(), $this->config, $context);
            }
            $elements = $def->elements;
        }
        if ($def->type == 'chameleon') {
            $attr['rowspan'] = 2;
        } elseif ($def->type == 'empty') {
            $elements = array();
        } elseif ($def->type == 'table') {
            $elements = array_flip(
                array(
                    'col',
                    'caption',
                    'colgroup',
                    'thead',
                    'tfoot',
                    'tbody',
                    'tr'
                )
            );
        }
        $ret .= $this->element('th', 'Allowed children', $attr);

        if ($def->type == 'chameleon') {

            $ret .= $this->element(
                'td',
                '<em>Block</em>: ' .
                $this->escape($this->listifyTagLookup($def->block->elements)),
                null,
                0
            );
            $ret .= $this->end('tr');
            $ret .= $this->start('tr');
            $ret .= $this->element(
                'td',
                '<em>Inline</em>: ' .
                $this->escape($this->listifyTagLookup($def->inline->elements)),
                null,
                0
            );

        } elseif ($def->type == 'custom') {

            $ret .= $this->element(
                'td',
                '<em>' . ucfirst($def->type) . '</em>: ' .
                $def->dtd_regex
            );

        } else {
            $ret .= $this->element(
                'td',
                '<em>' . ucfirst($def->type) . '</em>: ' .
                $this->escape($this->listifyTagLookup($elements)),
                null,
                0
            );
        }
        $ret .= $this->end('tr');
        return $ret;
    }

    /**
     * Listifies a tag lookup table.
     * @param array $array Tag lookup array in form of array('tagname' => true)
     * @return string
     */
    protected function listifyTagLookup($array)
    {
        ksort($array);
        $list = array();
        foreach ($array as $name => $discard) {
            if ($name !== '#PCDATA' && !isset($this->def->info[$name])) {
                continue;
            }
            $list[] = $name;
        }
        return $this->listify($list);
    }

    /**
     * Listifies a list of objects by retrieving class names and internal state
     * @param array $array List of objects
     * @return string
     * @todo Also add information about internal state
     */
    protected function listifyObjectList($array)
    {
        ksort($array);
        $list = array();
        foreach ($array as $obj) {
            $list[] = $this->getClass($obj, 'AttrTransform_');
        }
        return $this->listify($list);
    }

    /**
     * Listifies a hash of attributes to AttrDef classes
     * @param array $array Array hash in form of array('attrname' => HTMLPurifier_AttrDef)
     * @return string
     */
    protected function listifyAttr($array)
    {
        ksort($array);
        $list = array();
        foreach ($array as $name => $obj) {
            if ($obj === false) {
                continue;
            }
            $list[] = "$name&nbsp;=&nbsp;<i>" . $this->getClass($obj, 'AttrDef_') . '</i>';
        }
        return $this->listify($list);
    }

    /**
     * Creates a heavy header row
     * @param string $text
     * @param int $num
     * @return string
     */
    protected function heavyHeader($text, $num = 1)
    {
        $ret = '';
        $ret .= $this->start('tr');
        $ret .= $this->element('th', $text, array('colspan' => $num, 'class' => 'heavy'));
        $ret .= $this->end('tr');
        return $ret;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * @todo Rewrite to use Interchange objects
 */
class HTMLPurifier_Printer_ConfigForm extends HTMLPurifier_Printer
{

    /**
     * Printers for specific fields.
     * @type HTMLPurifier_Printer[]
     */
    protected $fields = array();

    /**
     * Documentation URL, can have fragment tagged on end.
     * @type string
     */
    protected $docURL;

    /**
     * Name of form element to stuff config in.
     * @type string
     */
    protected $name;

    /**
     * Whether or not to compress directive names, clipping them off
     * after a certain amount of letters. False to disable or integer letters
     * before clipping.
     * @type bool
     */
    protected $compress = false;

    /**
     * @param string $name Form element name for directives to be stuffed into
     * @param string $doc_url String documentation URL, will have fragment tagged on
     * @param bool $compress Integer max length before compressing a directive name, set to false to turn off
     */
    public function __construct(
        $name,
        $doc_url = null,
        $compress = false
    ) {
        parent::__construct();
        $this->docURL = $doc_url;
        $this->name = $name;
        $this->compress = $compress;
        // initialize sub-printers
        $this->fields[0] = new HTMLPurifier_Printer_ConfigForm_default();
        $this->fields[HTMLPurifier_VarParser::BOOL] = new HTMLPurifier_Printer_ConfigForm_bool();
    }

    /**
     * Sets default column and row size for textareas in sub-printers
     * @param $cols Integer columns of textarea, null to use default
     * @param $rows Integer rows of textarea, null to use default
     */
    public function setTextareaDimensions($cols = null, $rows = null)
    {
        if ($cols) {
            $this->fields['default']->cols = $cols;
        }
        if ($rows) {
            $this->fields['default']->rows = $rows;
        }
    }

    /**
     * Retrieves styling, in case it is not accessible by webserver
     */
    public static function getCSS()
    {
        return file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/Printer/ConfigForm.css');
    }

    /**
     * Retrieves JavaScript, in case it is not accessible by webserver
     */
    public static function getJavaScript()
    {
        return file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/Printer/ConfigForm.js');
    }

    /**
     * Returns HTML output for a configuration form
     * @param HTMLPurifier_Config|array $config Configuration object of current form state, or an array
     *        where [0] has an HTML namespace and [1] is being rendered.
     * @param array|bool $allowed Optional namespace(s) and directives to restrict form to.
     * @param bool $render_controls
     * @return string
     */
    public function render($config, $allowed = true, $render_controls = true)
    {
        if (is_array($config) && isset($config[0])) {
            $gen_config = $config[0];
            $config = $config[1];
        } else {
            $gen_config = $config;
        }

        $this->config = $config;
        $this->genConfig = $gen_config;
        $this->prepareGenerator($gen_config);

        $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $config->def);
        $all = array();
        foreach ($allowed as $key) {
            list($ns, $directive) = $key;
            $all[$ns][$directive] = $config->get($ns . '.' . $directive);
        }

        $ret = '';
        $ret .= $this->start('table', array('class' => 'hp-config'));
        $ret .= $this->start('thead');
        $ret .= $this->start('tr');
        $ret .= $this->element('th', 'Directive', array('class' => 'hp-directive'));
        $ret .= $this->element('th', 'Value', array('class' => 'hp-value'));
        $ret .= $this->end('tr');
        $ret .= $this->end('thead');
        foreach ($all as $ns => $directives) {
            $ret .= $this->renderNamespace($ns, $directives);
        }
        if ($render_controls) {
            $ret .= $this->start('tbody');
            $ret .= $this->start('tr');
            $ret .= $this->start('td', array('colspan' => 2, 'class' => 'controls'));
            $ret .= $this->elementEmpty('input', array('type' => 'submit', 'value' => 'Submit'));
            $ret .= '[<a href="?">Reset</a>]';
            $ret .= $this->end('td');
            $ret .= $this->end('tr');
            $ret .= $this->end('tbody');
        }
        $ret .= $this->end('table');
        return $ret;
    }

    /**
     * Renders a single namespace
     * @param $ns String namespace name
     * @param array $directives array of directives to values
     * @return string
     */
    protected function renderNamespace($ns, $directives)
    {
        $ret = '';
        $ret .= $this->start('tbody', array('class' => 'namespace'));
        $ret .= $this->start('tr');
        $ret .= $this->element('th', $ns, array('colspan' => 2));
        $ret .= $this->end('tr');
        $ret .= $this->end('tbody');
        $ret .= $this->start('tbody');
        foreach ($directives as $directive => $value) {
            $ret .= $this->start('tr');
            $ret .= $this->start('th');
            if ($this->docURL) {
                $url = str_replace('%s', urlencode("$ns.$directive"), $this->docURL);
                $ret .= $this->start('a', array('href' => $url));
            }
            $attr = array('for' => "{$this->name}:$ns.$directive");

            // crop directive name if it's too long
            if (!$this->compress || (strlen($directive) < $this->compress)) {
                $directive_disp = $directive;
            } else {
                $directive_disp = substr($directive, 0, $this->compress - 2) . '...';
                $attr['title'] = $directive;
            }

            $ret .= $this->element(
                'label',
                $directive_disp,
                // component printers must create an element with this id
                $attr
            );
            if ($this->docURL) {
                $ret .= $this->end('a');
            }
            $ret .= $this->end('th');

            $ret .= $this->start('td');
            $def = $this->config->def->info["$ns.$directive"];
            if (is_int($def)) {
                $allow_null = $def < 0;
                $type = abs($def);
            } else {
                $type = $def->type;
                $allow_null = isset($def->allow_null);
            }
            if (!isset($this->fields[$type])) {
                $type = 0;
            } // default
            $type_obj = $this->fields[$type];
            if ($allow_null) {
                $type_obj = new HTMLPurifier_Printer_ConfigForm_NullDecorator($type_obj);
            }
            $ret .= $type_obj->render($ns, $directive, $value, $this->name, array($this->genConfig, $this->config));
            $ret .= $this->end('td');
            $ret .= $this->end('tr');
        }
        $ret .= $this->end('tbody');
        return $ret;
    }

}

/**
 * Printer decorator for directives that accept null
 */
class HTMLPurifier_Printer_ConfigForm_NullDecorator extends HTMLPurifier_Printer
{
    /**
     * Printer being decorated
     * @type HTMLPurifier_Printer
     */
    protected $obj;

    /**
     * @param HTMLPurifier_Printer $obj Printer to decorate
     */
    public function __construct($obj)
    {
        parent::__construct();
        $this->obj = $obj;
    }

    /**
     * @param string $ns
     * @param string $directive
     * @param string $value
     * @param string $name
     * @param HTMLPurifier_Config|array $config
     * @return string
     */
    public function render($ns, $directive, $value, $name, $config)
    {
        if (is_array($config) && isset($config[0])) {
            $gen_config = $config[0];
            $config = $config[1];
        } else {
            $gen_config = $config;
        }
        $this->prepareGenerator($gen_config);

        $ret = '';
        $ret .= $this->start('label', array('for' => "$name:Null_$ns.$directive"));
        $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose'));
        $ret .= $this->text(' Null/Disabled');
        $ret .= $this->end('label');
        $attr = array(
            'type' => 'checkbox',
            'value' => '1',
            'class' => 'null-toggle',
            'name' => "$name" . "[Null_$ns.$directive]",
            'id' => "$name:Null_$ns.$directive",
            'onclick' => "toggleWriteability('$name:$ns.$directive',checked)" // INLINE JAVASCRIPT!!!!
        );
        if ($this->obj instanceof HTMLPurifier_Printer_ConfigForm_bool) {
            // modify inline javascript slightly
            $attr['onclick'] =
                "toggleWriteability('$name:Yes_$ns.$directive',checked);" .
                "toggleWriteability('$name:No_$ns.$directive',checked)";
        }
        if ($value === null) {
            $attr['checked'] = 'checked';
        }
        $ret .= $this->elementEmpty('input', $attr);
        $ret .= $this->text(' or ');
        $ret .= $this->elementEmpty('br');
        $ret .= $this->obj->render($ns, $directive, $value, $name, array($gen_config, $config));
        return $ret;
    }
}

/**
 * Swiss-army knife configuration form field printer
 */
class HTMLPurifier_Printer_ConfigForm_default extends HTMLPurifier_Printer
{
    /**
     * @type int
     */
    public $cols = 18;

    /**
     * @type int
     */
    public $rows = 5;

    /**
     * @param string $ns
     * @param string $directive
     * @param string $value
     * @param string $name
     * @param HTMLPurifier_Config|array $config
     * @return string
     */
    public function render($ns, $directive, $value, $name, $config)
    {
        if (is_array($config) && isset($config[0])) {
            $gen_config = $config[0];
            $config = $config[1];
        } else {
            $gen_config = $config;
        }
        $this->prepareGenerator($gen_config);
        // this should probably be split up a little
        $ret = '';
        $def = $config->def->info["$ns.$directive"];
        if (is_int($def)) {
            $type = abs($def);
        } else {
            $type = $def->type;
        }
        if (is_array($value)) {
            switch ($type) {
                case HTMLPurifier_VarParser::LOOKUP:
                    $array = $value;
                    $value = array();
                    foreach ($array as $val => $b) {
                        $value[] = $val;
                    }
                    //TODO does this need a break?
                case HTMLPurifier_VarParser::ALIST:
                    $value = implode(PHP_EOL, $value);
                    break;
                case HTMLPurifier_VarParser::HASH:
                    $nvalue = '';
                    foreach ($value as $i => $v) {
                        if (is_array($v)) {
                            // HACK
                            $v = implode(";", $v);
                        }
                        $nvalue .= "$i:$v" . PHP_EOL;
                    }
                    $value = $nvalue;
                    break;
                default:
                    $value = '';
            }
        }
        if ($type === HTMLPurifier_VarParser::MIXED) {
            return 'Not supported';
            $value = serialize($value);
        }
        $attr = array(
            'name' => "$name" . "[$ns.$directive]",
            'id' => "$name:$ns.$directive"
        );
        if ($value === null) {
            $attr['disabled'] = 'disabled';
        }
        if (isset($def->allowed)) {
            $ret .= $this->start('select', $attr);
            foreach ($def->allowed as $val => $b) {
                $attr = array();
                if ($value == $val) {
                    $attr['selected'] = 'selected';
                }
                $ret .= $this->element('option', $val, $attr);
            }
            $ret .= $this->end('select');
        } elseif ($type === HTMLPurifier_VarParser::TEXT ||
                $type === HTMLPurifier_VarParser::ITEXT ||
                $type === HTMLPurifier_VarParser::ALIST ||
                $type === HTMLPurifier_VarParser::HASH ||
                $type === HTMLPurifier_VarParser::LOOKUP) {
            $attr['cols'] = $this->cols;
            $attr['rows'] = $this->rows;
            $ret .= $this->start('textarea', $attr);
            $ret .= $this->text($value);
            $ret .= $this->end('textarea');
        } else {
            $attr['value'] = $value;
            $attr['type'] = 'text';
            $ret .= $this->elementEmpty('input', $attr);
        }
        return $ret;
    }
}

/**
 * Bool form field printer
 */
class HTMLPurifier_Printer_ConfigForm_bool extends HTMLPurifier_Printer
{
    /**
     * @param string $ns
     * @param string $directive
     * @param string $value
     * @param string $name
     * @param HTMLPurifier_Config|array $config
     * @return string
     */
    public function render($ns, $directive, $value, $name, $config)
    {
        if (is_array($config) && isset($config[0])) {
            $gen_config = $config[0];
            $config = $config[1];
        } else {
            $gen_config = $config;
        }
        $this->prepareGenerator($gen_config);
        $ret = '';
        $ret .= $this->start('div', array('id' => "$name:$ns.$directive"));

        $ret .= $this->start('label', array('for' => "$name:Yes_$ns.$directive"));
        $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose'));
        $ret .= $this->text(' Yes');
        $ret .= $this->end('label');

        $attr = array(
            'type' => 'radio',
            'name' => "$name" . "[$ns.$directive]",
            'id' => "$name:Yes_$ns.$directive",
            'value' => '1'
        );
        if ($value === true) {
            $attr['checked'] = 'checked';
        }
        if ($value === null) {
            $attr['disabled'] = 'disabled';
        }
        $ret .= $this->elementEmpty('input', $attr);

        $ret .= $this->start('label', array('for' => "$name:No_$ns.$directive"));
        $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose'));
        $ret .= $this->text(' No');
        $ret .= $this->end('label');

        $attr = array(
            'type' => 'radio',
            'name' => "$name" . "[$ns.$directive]",
            'id' => "$name:No_$ns.$directive",
            'value' => '0'
        );
        if ($value === false) {
            $attr['checked'] = 'checked';
        }
        if ($value === null) {
            $attr['disabled'] = 'disabled';
        }
        $ret .= $this->elementEmpty('input', $attr);

        $ret .= $this->end('div');

        return $ret;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Defines a mutation of an obsolete tag into a valid tag.
 */
abstract class HTMLPurifier_TagTransform
{

    /**
     * Tag name to transform the tag to.
     * @type string
     */
    public $transform_to;

    /**
     * Transforms the obsolete tag into the valid tag.
     * @param HTMLPurifier_Token_Tag $tag Tag to be transformed.
     * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object
     * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object
     */
    abstract public function transform($tag, $config, $context);

    /**
     * Prepends CSS properties to the style attribute, creating the
     * attribute if it doesn't exist.
     * @warning Copied over from AttrTransform, be sure to keep in sync
     * @param array $attr Attribute array to process (passed by reference)
     * @param string $css CSS to prepend
     */
    protected function prependCSS(&$attr, $css)
    {
        $attr['style'] = isset($attr['style']) ? $attr['style'] : '';
        $attr['style'] = $css . $attr['style'];
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Converts a stream of HTMLPurifier_Token into an HTMLPurifier_Node,
 * and back again.
 *
 * @note This transformation is not an equivalence.  We mutate the input
 * token stream to make it so; see all [MUT] markers in code.
 */
class HTMLPurifier_Arborize
{
    public static function arborize($tokens, $config, $context) {
        $definition = $config->getHTMLDefinition();
        $parent = new HTMLPurifier_Token_Start($definition->info_parent);
        $stack = array($parent->toNode());
        foreach ($tokens as $token) {
            $token->skip = null; // [MUT]
            $token->carryover = null; // [MUT]
            if ($token instanceof HTMLPurifier_Token_End) {
                $token->start = null; // [MUT]
                $r = array_pop($stack);
                //assert($r->name === $token->name);
                //assert(empty($token->attr));
                $r->endCol = $token->col;
                $r->endLine = $token->line;
                $r->endArmor = $token->armor;
                continue;
            }
            $node = $token->toNode();
            $stack[count($stack)-1]->children[] = $node;
            if ($token instanceof HTMLPurifier_Token_Start) {
                $stack[] = $node;
            }
        }
        //assert(count($stack) == 1);
        return $stack[0];
    }

    public static function flatten($node, $config, $context) {
        $level = 0;
        $nodes = array($level => new HTMLPurifier_Queue(array($node)));
        $closingTokens = array();
        $tokens = array();
        do {
            while (!$nodes[$level]->isEmpty()) {
                $node = $nodes[$level]->shift(); // FIFO
                list($start, $end) = $node->toTokenPair();
                if ($level > 0) {
                    $tokens[] = $start;
                }
                if ($end !== NULL) {
                    $closingTokens[$level][] = $end;
                }
                if ($node instanceof HTMLPurifier_Node_Element) {
                    $level++;
                    $nodes[$level] = new HTMLPurifier_Queue();
                    foreach ($node->children as $childNode) {
                        $nodes[$level]->push($childNode);
                    }
                }
            }
            $level--;
            if ($level && isset($closingTokens[$level])) {
                while ($token = array_pop($closingTokens[$level])) {
                    $tokens[] = $token;
                }
            }
        } while ($level > 0);
        return $tokens;
    }
}
<?php

/**
 * Transforms FONT tags to the proper form (SPAN with CSS styling)
 *
 * This transformation takes the three proprietary attributes of FONT and
 * transforms them into their corresponding CSS attributes.  These are color,
 * face, and size.
 *
 * @note Size is an interesting case because it doesn't map cleanly to CSS.
 *       Thanks to
 *       http://style.cleverchimp.com/font_size_intervals/altintervals.html
 *       for reasonable mappings.
 * @warning This doesn't work completely correctly; specifically, this
 *          TagTransform operates before well-formedness is enforced, so
 *          the "active formatting elements" algorithm doesn't get applied.
 */
class HTMLPurifier_TagTransform_Font extends HTMLPurifier_TagTransform
{
    /**
     * @type string
     */
    public $transform_to = 'span';

    /**
     * @type array
     */
    protected $_size_lookup = array(
        '0' => 'xx-small',
        '1' => 'xx-small',
        '2' => 'small',
        '3' => 'medium',
        '4' => 'large',
        '5' => 'x-large',
        '6' => 'xx-large',
        '7' => '300%',
        '-1' => 'smaller',
        '-2' => '60%',
        '+1' => 'larger',
        '+2' => '150%',
        '+3' => '200%',
        '+4' => '300%'
    );

    /**
     * @param HTMLPurifier_Token_Tag $tag
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return HTMLPurifier_Token_End|string
     */
    public function transform($tag, $config, $context)
    {
        if ($tag instanceof HTMLPurifier_Token_End) {
            $new_tag = clone $tag;
            $new_tag->name = $this->transform_to;
            return $new_tag;
        }

        $attr = $tag->attr;
        $prepend_style = '';

        // handle color transform
        if (isset($attr['color'])) {
            $prepend_style .= 'color:' . $attr['color'] . ';';
            unset($attr['color']);
        }

        // handle face transform
        if (isset($attr['face'])) {
            $prepend_style .= 'font-family:' . $attr['face'] . ';';
            unset($attr['face']);
        }

        // handle size transform
        if (isset($attr['size'])) {
            // normalize large numbers
            if ($attr['size'] !== '') {
                if ($attr['size']{0} == '+' || $attr['size']{0} == '-') {
                    $size = (int)$attr['size'];
                    if ($size < -2) {
                        $attr['size'] = '-2';
                    }
                    if ($size > 4) {
                        $attr['size'] = '+4';
                    }
                } else {
                    $size = (int)$attr['size'];
                    if ($size > 7) {
                        $attr['size'] = '7';
                    }
                }
            }
            if (isset($this->_size_lookup[$attr['size']])) {
                $prepend_style .= 'font-size:' .
                    $this->_size_lookup[$attr['size']] . ';';
            }
            unset($attr['size']);
        }

        if ($prepend_style) {
            $attr['style'] = isset($attr['style']) ?
                $prepend_style . $attr['style'] :
                $prepend_style;
        }

        $new_tag = clone $tag;
        $new_tag->name = $this->transform_to;
        $new_tag->attr = $attr;

        return $new_tag;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Simple transformation, just change tag name to something else,
 * and possibly add some styling. This will cover most of the deprecated
 * tag cases.
 */
class HTMLPurifier_TagTransform_Simple extends HTMLPurifier_TagTransform
{
    /**
     * @type string
     */
    protected $style;

    /**
     * @param string $transform_to Tag name to transform to.
     * @param string $style CSS style to add to the tag
     */
    public function __construct($transform_to, $style = null)
    {
        $this->transform_to = $transform_to;
        $this->style = $style;
    }

    /**
     * @param HTMLPurifier_Token_Tag $tag
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return string
     */
    public function transform($tag, $config, $context)
    {
        $new_tag = clone $tag;
        $new_tag->name = $this->transform_to;
        if (!is_null($this->style) &&
            ($new_tag instanceof HTMLPurifier_Token_Start || $new_tag instanceof HTMLPurifier_Token_Empty)
        ) {
            $this->prependCSS($new_tag->attr, $this->style);
        }
        return $new_tag;
    }
}

// vim: et sw=4 sts=4
<?php

class HTMLPurifier_URIDefinition extends HTMLPurifier_Definition
{

    public $type = 'URI';
    protected $filters = array();
    protected $postFilters = array();
    protected $registeredFilters = array();

    /**
     * HTMLPurifier_URI object of the base specified at %URI.Base
     */
    public $base;

    /**
     * String host to consider "home" base, derived off of $base
     */
    public $host;

    /**
     * Name of default scheme based on %URI.DefaultScheme and %URI.Base
     */
    public $defaultScheme;

    public function __construct()
    {
        $this->registerFilter(new HTMLPurifier_URIFilter_DisableExternal());
        $this->registerFilter(new HTMLPurifier_URIFilter_DisableExternalResources());
        $this->registerFilter(new HTMLPurifier_URIFilter_DisableResources());
        $this->registerFilter(new HTMLPurifier_URIFilter_HostBlacklist());
        $this->registerFilter(new HTMLPurifier_URIFilter_SafeIframe());
        $this->registerFilter(new HTMLPurifier_URIFilter_MakeAbsolute());
        $this->registerFilter(new HTMLPurifier_URIFilter_Munge());
    }

    public function registerFilter($filter)
    {
        $this->registeredFilters[$filter->name] = $filter;
    }

    public function addFilter($filter, $config)
    {
        $r = $filter->prepare($config);
        if ($r === false) return; // null is ok, for backwards compat
        if ($filter->post) {
            $this->postFilters[$filter->name] = $filter;
        } else {
            $this->filters[$filter->name] = $filter;
        }
    }

    protected function doSetup($config)
    {
        $this->setupMemberVariables($config);
        $this->setupFilters($config);
    }

    protected function setupFilters($config)
    {
        foreach ($this->registeredFilters as $name => $filter) {
            if ($filter->always_load) {
                $this->addFilter($filter, $config);
            } else {
                $conf = $config->get('URI.' . $name);
                if ($conf !== false && $conf !== null) {
                    $this->addFilter($filter, $config);
                }
            }
        }
        unset($this->registeredFilters);
    }

    protected function setupMemberVariables($config)
    {
        $this->host = $config->get('URI.Host');
        $base_uri = $config->get('URI.Base');
        if (!is_null($base_uri)) {
            $parser = new HTMLPurifier_URIParser();
            $this->base = $parser->parse($base_uri);
            $this->defaultScheme = $this->base->scheme;
            if (is_null($this->host)) $this->host = $this->base->host;
        }
        if (is_null($this->defaultScheme)) $this->defaultScheme = $config->get('URI.DefaultScheme');
    }

    public function getDefaultScheme($config, $context)
    {
        return HTMLPurifier_URISchemeRegistry::instance()->getScheme($this->defaultScheme, $config, $context);
    }

    public function filter(&$uri, $config, $context)
    {
        foreach ($this->filters as $name => $f) {
            $result = $f->filter($uri, $config, $context);
            if (!$result) return false;
        }
        return true;
    }

    public function postFilter(&$uri, $config, $context)
    {
        foreach ($this->postFilters as $name => $f) {
            $result = $f->filter($uri, $config, $context);
            if (!$result) return false;
        }
        return true;
    }

}

// vim: et sw=4 sts=4
<?php

/**
 * Defines common attribute collections that modules reference
 */

class HTMLPurifier_AttrCollections
{

    /**
     * Associative array of attribute collections, indexed by name.
     * @type array
     */
    public $info = array();

    /**
     * Performs all expansions on internal data for use by other inclusions
     * It also collects all attribute collection extensions from
     * modules
     * @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance
     * @param HTMLPurifier_HTMLModule[] $modules Hash array of HTMLPurifier_HTMLModule members
     */
    public function __construct($attr_types, $modules)
    {
        $this->doConstruct($attr_types, $modules);
    }

    public function doConstruct($attr_types, $modules)
    {
        // load extensions from the modules
        foreach ($modules as $module) {
            foreach ($module->attr_collections as $coll_i => $coll) {
                if (!isset($this->info[$coll_i])) {
                    $this->info[$coll_i] = array();
                }
                foreach ($coll as $attr_i => $attr) {
                    if ($attr_i === 0 && isset($this->info[$coll_i][$attr_i])) {
                        // merge in includes
                        $this->info[$coll_i][$attr_i] = array_merge(
                            $this->info[$coll_i][$attr_i],
                            $attr
                        );
                        continue;
                    }
                    $this->info[$coll_i][$attr_i] = $attr;
                }
            }
        }
        // perform internal expansions and inclusions
        foreach ($this->info as $name => $attr) {
            // merge attribute collections that include others
            $this->performInclusions($this->info[$name]);
            // replace string identifiers with actual attribute objects
            $this->expandIdentifiers($this->info[$name], $attr_types);
        }
    }

    /**
     * Takes a reference to an attribute associative array and performs
     * all inclusions specified by the zero index.
     * @param array &$attr Reference to attribute array
     */
    public function performInclusions(&$attr)
    {
        if (!isset($attr[0])) {
            return;
        }
        $merge = $attr[0];
        $seen  = array(); // recursion guard
        // loop through all the inclusions
        for ($i = 0; isset($merge[$i]); $i++) {
            if (isset($seen[$merge[$i]])) {
                continue;
            }
            $seen[$merge[$i]] = true;
            // foreach attribute of the inclusion, copy it over
            if (!isset($this->info[$merge[$i]])) {
                continue;
            }
            foreach ($this->info[$merge[$i]] as $key => $value) {
                if (isset($attr[$key])) {
                    continue;
                } // also catches more inclusions
                $attr[$key] = $value;
            }
            if (isset($this->info[$merge[$i]][0])) {
                // recursion
                $merge = array_merge($merge, $this->info[$merge[$i]][0]);
            }
        }
        unset($attr[0]);
    }

    /**
     * Expands all string identifiers in an attribute array by replacing
     * them with the appropriate values inside HTMLPurifier_AttrTypes
     * @param array &$attr Reference to attribute array
     * @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance
     */
    public function expandIdentifiers(&$attr, $attr_types)
    {
        // because foreach will process new elements we add, make sure we
        // skip duplicates
        $processed = array();

        foreach ($attr as $def_i => $def) {
            // skip inclusions
            if ($def_i === 0) {
                continue;
            }

            if (isset($processed[$def_i])) {
                continue;
            }

            // determine whether or not attribute is required
            if ($required = (strpos($def_i, '*') !== false)) {
                // rename the definition
                unset($attr[$def_i]);
                $def_i = trim($def_i, '*');
                $attr[$def_i] = $def;
            }

            $processed[$def_i] = true;

            // if we've already got a literal object, move on
            if (is_object($def)) {
                // preserve previous required
                $attr[$def_i]->required = ($required || $attr[$def_i]->required);
                continue;
            }

            if ($def === false) {
                unset($attr[$def_i]);
                continue;
            }

            if ($t = $attr_types->get($def)) {
                $attr[$def_i] = $t;
                $attr[$def_i]->required = $required;
            } else {
                unset($attr[$def_i]);
            }
        }
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates the attributes of a token. Doesn't manage required attributes
 * very well. The only reason we factored this out was because RemoveForeignElements
 * also needed it besides ValidateAttributes.
 */
class HTMLPurifier_AttrValidator
{

    /**
     * Validates the attributes of a token, mutating it as necessary.
     * that has valid tokens
     * @param HTMLPurifier_Token $token Token to validate.
     * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config
     * @param HTMLPurifier_Context $context Instance of HTMLPurifier_Context
     */
    public function validateToken($token, $config, $context)
    {
        $definition = $config->getHTMLDefinition();
        $e =& $context->get('ErrorCollector', true);

        // initialize IDAccumulator if necessary
        $ok =& $context->get('IDAccumulator', true);
        if (!$ok) {
            $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context);
            $context->register('IDAccumulator', $id_accumulator);
        }

        // initialize CurrentToken if necessary
        $current_token =& $context->get('CurrentToken', true);
        if (!$current_token) {
            $context->register('CurrentToken', $token);
        }

        if (!$token instanceof HTMLPurifier_Token_Start &&
            !$token instanceof HTMLPurifier_Token_Empty
        ) {
            return;
        }

        // create alias to global definition array, see also $defs
        // DEFINITION CALL
        $d_defs = $definition->info_global_attr;

        // don't update token until the very end, to ensure an atomic update
        $attr = $token->attr;

        // do global transformations (pre)
        // nothing currently utilizes this
        foreach ($definition->info_attr_transform_pre as $transform) {
            $attr = $transform->transform($o = $attr, $config, $context);
            if ($e) {
                if ($attr != $o) {
                    $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
                }
            }
        }

        // do local transformations only applicable to this element (pre)
        // ex. <p align="right"> to <p style="text-align:right;">
        foreach ($definition->info[$token->name]->attr_transform_pre as $transform) {
            $attr = $transform->transform($o = $attr, $config, $context);
            if ($e) {
                if ($attr != $o) {
                    $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
                }
            }
        }

        // create alias to this element's attribute definition array, see
        // also $d_defs (global attribute definition array)
        // DEFINITION CALL
        $defs = $definition->info[$token->name]->attr;

        $attr_key = false;
        $context->register('CurrentAttr', $attr_key);

        // iterate through all the attribute keypairs
        // Watch out for name collisions: $key has previously been used
        foreach ($attr as $attr_key => $value) {

            // call the definition
            if (isset($defs[$attr_key])) {
                // there is a local definition defined
                if ($defs[$attr_key] === false) {
                    // We've explicitly been told not to allow this element.
                    // This is usually when there's a global definition
                    // that must be overridden.
                    // Theoretically speaking, we could have a
                    // AttrDef_DenyAll, but this is faster!
                    $result = false;
                } else {
                    // validate according to the element's definition
                    $result = $defs[$attr_key]->validate(
                        $value,
                        $config,
                        $context
                    );
                }
            } elseif (isset($d_defs[$attr_key])) {
                // there is a global definition defined, validate according
                // to the global definition
                $result = $d_defs[$attr_key]->validate(
                    $value,
                    $config,
                    $context
                );
            } else {
                // system never heard of the attribute? DELETE!
                $result = false;
            }

            // put the results into effect
            if ($result === false || $result === null) {
                // this is a generic error message that should replaced
                // with more specific ones when possible
                if ($e) {
                    $e->send(E_ERROR, 'AttrValidator: Attribute removed');
                }

                // remove the attribute
                unset($attr[$attr_key]);
            } elseif (is_string($result)) {
                // generally, if a substitution is happening, there
                // was some sort of implicit correction going on. We'll
                // delegate it to the attribute classes to say exactly what.

                // simple substitution
                $attr[$attr_key] = $result;
            } else {
                // nothing happens
            }

            // we'd also want slightly more complicated substitution
            // involving an array as the return value,
            // although we're not sure how colliding attributes would
            // resolve (certain ones would be completely overriden,
            // others would prepend themselves).
        }

        $context->destroy('CurrentAttr');

        // post transforms

        // global (error reporting untested)
        foreach ($definition->info_attr_transform_post as $transform) {
            $attr = $transform->transform($o = $attr, $config, $context);
            if ($e) {
                if ($attr != $o) {
                    $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
                }
            }
        }

        // local (error reporting untested)
        foreach ($definition->info[$token->name]->attr_transform_post as $transform) {
            $attr = $transform->transform($o = $attr, $config, $context);
            if ($e) {
                if ($attr != $o) {
                    $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
                }
            }
        }

        $token->attr = $attr;

        // destroy CurrentToken if we made it ourselves
        if (!$current_token) {
            $context->destroy('CurrentToken');
        }

    }


}

// vim: et sw=4 sts=4
<?php

/**
 * Parses string representations into their corresponding native PHP
 * variable type. The base implementation does a simple type-check.
 */
class HTMLPurifier_VarParser
{

    const STRING = 1;
    const ISTRING = 2;
    const TEXT = 3;
    const ITEXT = 4;
    const INT = 5;
    const FLOAT = 6;
    const BOOL = 7;
    const LOOKUP = 8;
    const ALIST = 9;
    const HASH = 10;
    const MIXED = 11;

    /**
     * Lookup table of allowed types. Mainly for backwards compatibility, but
     * also convenient for transforming string type names to the integer constants.
     */
    public static $types = array(
        'string' => self::STRING,
        'istring' => self::ISTRING,
        'text' => self::TEXT,
        'itext' => self::ITEXT,
        'int' => self::INT,
        'float' => self::FLOAT,
        'bool' => self::BOOL,
        'lookup' => self::LOOKUP,
        'list' => self::ALIST,
        'hash' => self::HASH,
        'mixed' => self::MIXED
    );

    /**
     * Lookup table of types that are string, and can have aliases or
     * allowed value lists.
     */
    public static $stringTypes = array(
        self::STRING => true,
        self::ISTRING => true,
        self::TEXT => true,
        self::ITEXT => true,
    );

    /**
     * Validate a variable according to type.
     * It may return NULL as a valid type if $allow_null is true.
     *
     * @param mixed $var Variable to validate
     * @param int $type Type of variable, see HTMLPurifier_VarParser->types
     * @param bool $allow_null Whether or not to permit null as a value
     * @return string Validated and type-coerced variable
     * @throws HTMLPurifier_VarParserException
     */
    final public function parse($var, $type, $allow_null = false)
    {
        if (is_string($type)) {
            if (!isset(HTMLPurifier_VarParser::$types[$type])) {
                throw new HTMLPurifier_VarParserException("Invalid type '$type'");
            } else {
                $type = HTMLPurifier_VarParser::$types[$type];
            }
        }
        $var = $this->parseImplementation($var, $type, $allow_null);
        if ($allow_null && $var === null) {
            return null;
        }
        // These are basic checks, to make sure nothing horribly wrong
        // happened in our implementations.
        switch ($type) {
            case (self::STRING):
            case (self::ISTRING):
            case (self::TEXT):
            case (self::ITEXT):
                if (!is_string($var)) {
                    break;
                }
                if ($type == self::ISTRING || $type == self::ITEXT) {
                    $var = strtolower($var);
                }
                return $var;
            case (self::INT):
                if (!is_int($var)) {
                    break;
                }
                return $var;
            case (self::FLOAT):
                if (!is_float($var)) {
                    break;
                }
                return $var;
            case (self::BOOL):
                if (!is_bool($var)) {
                    break;
                }
                return $var;
            case (self::LOOKUP):
            case (self::ALIST):
            case (self::HASH):
                if (!is_array($var)) {
                    break;
                }
                if ($type === self::LOOKUP) {
                    foreach ($var as $k) {
                        if ($k !== true) {
                            $this->error('Lookup table contains value other than true');
                        }
                    }
                } elseif ($type === self::ALIST) {
                    $keys = array_keys($var);
                    if (array_keys($keys) !== $keys) {
                        $this->error('Indices for list are not uniform');
                    }
                }
                return $var;
            case (self::MIXED):
                return $var;
            default:
                $this->errorInconsistent(get_class($this), $type);
        }
        $this->errorGeneric($var, $type);
    }

    /**
     * Actually implements the parsing. Base implementation does not
     * do anything to $var. Subclasses should overload this!
     * @param mixed $var
     * @param int $type
     * @param bool $allow_null
     * @return string
     */
    protected function parseImplementation($var, $type, $allow_null)
    {
        return $var;
    }

    /**
     * Throws an exception.
     * @throws HTMLPurifier_VarParserException
     */
    protected function error($msg)
    {
        throw new HTMLPurifier_VarParserException($msg);
    }

    /**
     * Throws an inconsistency exception.
     * @note This should not ever be called. It would be called if we
     *       extend the allowed values of HTMLPurifier_VarParser without
     *       updating subclasses.
     * @param string $class
     * @param int $type
     * @throws HTMLPurifier_Exception
     */
    protected function errorInconsistent($class, $type)
    {
        throw new HTMLPurifier_Exception(
            "Inconsistency in $class: " . HTMLPurifier_VarParser::getTypeName($type) .
            " not implemented"
        );
    }

    /**
     * Generic error for if a type didn't work.
     * @param mixed $var
     * @param int $type
     */
    protected function errorGeneric($var, $type)
    {
        $vtype = gettype($var);
        $this->error("Expected type " . HTMLPurifier_VarParser::getTypeName($type) . ", got $vtype");
    }

    /**
     * @param int $type
     * @return string
     */
    public static function getTypeName($type)
    {
        static $lookup;
        if (!$lookup) {
            // Lazy load the alternative lookup table
            $lookup = array_flip(HTMLPurifier_VarParser::$types);
        }
        if (!isset($lookup[$type])) {
            return 'unknown';
        }
        return $lookup[$type];
    }
}

// vim: et sw=4 sts=4
<?php

// private language message file for unit testing purposes

$fallback = 'en';

$messages = array(
    'HTMLPurifier' => 'HTML Purifier X'
);

// vim: et sw=4 sts=4
<?php

$fallback = false;

$messages = array(

    'HTMLPurifier' => 'HTML Purifier',
// for unit testing purposes
    'LanguageFactoryTest: Pizza' => 'Pizza',
    'LanguageTest: List' => '$1',
    'LanguageTest: Hash' => '$1.Keys; $1.Values',
    'Item separator' => ', ',
    'Item separator last' => ' and ', // non-Harvard style

    'ErrorCollector: No errors' => 'No errors detected. However, because error reporting is still incomplete, there may have been errors that the error collector was not notified of; please inspect the output HTML carefully.',
    'ErrorCollector: At line' => ' at line $line',
    'ErrorCollector: Incidental errors' => 'Incidental errors',
    'Lexer: Unclosed comment' => 'Unclosed comment',
    'Lexer: Unescaped lt' => 'Unescaped less-than sign (<) should be &lt;',
    'Lexer: Missing gt' => 'Missing greater-than sign (>), previous less-than sign (<) should be escaped',
    'Lexer: Missing attribute key' => 'Attribute declaration has no key',
    'Lexer: Missing end quote' => 'Attribute declaration has no end quote',
    'Lexer: Extracted body' => 'Removed document metadata tags',
    'Strategy_RemoveForeignElements: Tag transform' => '<$1> element transformed into $CurrentToken.Serialized',
    'Strategy_RemoveForeignElements: Missing required attribute' => '$CurrentToken.Compact element missing required attribute $1',
    'Strategy_RemoveForeignElements: Foreign element to text' => 'Unrecognized $CurrentToken.Serialized tag converted to text',
    'Strategy_RemoveForeignElements: Foreign element removed' => 'Unrecognized $CurrentToken.Serialized tag removed',
    'Strategy_RemoveForeignElements: Comment removed' => 'Comment containing "$CurrentToken.Data" removed',
    'Strategy_RemoveForeignElements: Foreign meta element removed' => 'Unrecognized $CurrentToken.Serialized meta tag and all descendants removed',
    'Strategy_RemoveForeignElements: Token removed to end' => 'Tags and text starting from $1 element where removed to end',
    'Strategy_RemoveForeignElements: Trailing hyphen in comment removed' => 'Trailing hyphen(s) in comment removed',
    'Strategy_RemoveForeignElements: Hyphens in comment collapsed' => 'Double hyphens in comments are not allowed, and were collapsed into single hyphens',
    'Strategy_MakeWellFormed: Unnecessary end tag removed' => 'Unnecessary $CurrentToken.Serialized tag removed',
    'Strategy_MakeWellFormed: Unnecessary end tag to text' => 'Unnecessary $CurrentToken.Serialized tag converted to text',
    'Strategy_MakeWellFormed: Tag auto closed' => '$1.Compact started on line $1.Line auto-closed by $CurrentToken.Compact',
    'Strategy_MakeWellFormed: Tag carryover' => '$1.Compact started on line $1.Line auto-continued into $CurrentToken.Compact',
    'Strategy_MakeWellFormed: Stray end tag removed' => 'Stray $CurrentToken.Serialized tag removed',
    'Strategy_MakeWellFormed: Stray end tag to text' => 'Stray $CurrentToken.Serialized tag converted to text',
    'Strategy_MakeWellFormed: Tag closed by element end' => '$1.Compact tag started on line $1.Line closed by end of $CurrentToken.Serialized',
    'Strategy_MakeWellFormed: Tag closed by document end' => '$1.Compact tag started on line $1.Line closed by end of document',
    'Strategy_FixNesting: Node removed' => '$CurrentToken.Compact node removed',
    'Strategy_FixNesting: Node excluded' => '$CurrentToken.Compact node removed due to descendant exclusion by ancestor element',
    'Strategy_FixNesting: Node reorganized' => 'Contents of $CurrentToken.Compact node reorganized to enforce its content model',
    'Strategy_FixNesting: Node contents removed' => 'Contents of $CurrentToken.Compact node removed',
    'AttrValidator: Attributes transformed' => 'Attributes on $CurrentToken.Compact transformed from $1.Keys to $2.Keys',
    'AttrValidator: Attribute removed' => '$CurrentAttr.Name attribute on $CurrentToken.Compact removed',
);

$errorNames = array(
    E_ERROR => 'Error',
    E_WARNING => 'Warning',
    E_NOTICE => 'Notice'
);

// vim: et sw=4 sts=4
<?php

// private language message file for unit testing purposes
// this language file has no class associated with it

$fallback = 'en';

$messages = array(
    'HTMLPurifier' => 'HTML Purifier XNone'
);

// vim: et sw=4 sts=4
<?php

// private class for unit testing

class HTMLPurifier_Language_en_x_test extends HTMLPurifier_Language
{
}

// vim: et sw=4 sts=4
<?php

/**
 * Super-class for definition datatype objects, implements serialization
 * functions for the class.
 */
abstract class HTMLPurifier_Definition
{

    /**
     * Has setup() been called yet?
     * @type bool
     */
    public $setup = false;

    /**
     * If true, write out the final definition object to the cache after
     * setup.  This will be true only if all invocations to get a raw
     * definition object are also optimized.  This does not cause file
     * system thrashing because on subsequent calls the cached object
     * is used and any writes to the raw definition object are short
     * circuited.  See enduser-customize.html for the high-level
     * picture.
     * @type bool
     */
    public $optimized = null;

    /**
     * What type of definition is it?
     * @type string
     */
    public $type;

    /**
     * Sets up the definition object into the final form, something
     * not done by the constructor
     * @param HTMLPurifier_Config $config
     */
    abstract protected function doSetup($config);

    /**
     * Setup function that aborts if already setup
     * @param HTMLPurifier_Config $config
     */
    public function setup($config)
    {
        if ($this->setup) {
            return;
        }
        $this->setup = true;
        $this->doSetup($config);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Parses string hash files. File format is as such:
 *
 *      DefaultKeyValue
 *      KEY: Value
 *      KEY2: Value2
 *      --MULTILINE-KEY--
 *      Multiline
 *      value.
 *
 * Which would output something similar to:
 *
 *      array(
 *          'ID' => 'DefaultKeyValue',
 *          'KEY' => 'Value',
 *          'KEY2' => 'Value2',
 *          'MULTILINE-KEY' => "Multiline\nvalue.\n",
 *      )
 *
 * We use this as an easy to use file-format for configuration schema
 * files, but the class itself is usage agnostic.
 *
 * You can use ---- to forcibly terminate parsing of a single string-hash;
 * this marker is used in multi string-hashes to delimit boundaries.
 */
class HTMLPurifier_StringHashParser
{

    /**
     * @type string
     */
    public $default = 'ID';

    /**
     * Parses a file that contains a single string-hash.
     * @param string $file
     * @return array
     */
    public function parseFile($file)
    {
        if (!file_exists($file)) {
            return false;
        }
        $fh = fopen($file, 'r');
        if (!$fh) {
            return false;
        }
        $ret = $this->parseHandle($fh);
        fclose($fh);
        return $ret;
    }

    /**
     * Parses a file that contains multiple string-hashes delimited by '----'
     * @param string $file
     * @return array
     */
    public function parseMultiFile($file)
    {
        if (!file_exists($file)) {
            return false;
        }
        $ret = array();
        $fh = fopen($file, 'r');
        if (!$fh) {
            return false;
        }
        while (!feof($fh)) {
            $ret[] = $this->parseHandle($fh);
        }
        fclose($fh);
        return $ret;
    }

    /**
     * Internal parser that acepts a file handle.
     * @note While it's possible to simulate in-memory parsing by using
     *       custom stream wrappers, if such a use-case arises we should
     *       factor out the file handle into its own class.
     * @param resource $fh File handle with pointer at start of valid string-hash
     *            block.
     * @return array
     */
    protected function parseHandle($fh)
    {
        $state   = false;
        $single  = false;
        $ret     = array();
        do {
            $line = fgets($fh);
            if ($line === false) {
                break;
            }
            $line = rtrim($line, "\n\r");
            if (!$state && $line === '') {
                continue;
            }
            if ($line === '----') {
                break;
            }
            if (strncmp('--#', $line, 3) === 0) {
                // Comment
                continue;
            } elseif (strncmp('--', $line, 2) === 0) {
                // Multiline declaration
                $state = trim($line, '- ');
                if (!isset($ret[$state])) {
                    $ret[$state] = '';
                }
                continue;
            } elseif (!$state) {
                $single = true;
                if (strpos($line, ':') !== false) {
                    // Single-line declaration
                    list($state, $line) = explode(':', $line, 2);
                    $line = trim($line);
                } else {
                    // Use default declaration
                    $state  = $this->default;
                }
            }
            if ($single) {
                $ret[$state] = $line;
                $single = false;
                $state  = false;
            } else {
                $ret[$state] .= "$line\n";
            }
        } while (!feof($fh));
        return $ret;
    }
}

// vim: et sw=4 sts=4
<?php

// constants are slow, so we use as few as possible
if (!defined('HTMLPURIFIER_PREFIX')) {
    define('HTMLPURIFIER_PREFIX', realpath(dirname(__FILE__) . '/..'));
}

// accomodations for versions earlier than 5.0.2
// borrowed from PHP_Compat, LGPL licensed, by Aidan Lister <aidan@php.net>
if (!defined('PHP_EOL')) {
    switch (strtoupper(substr(PHP_OS, 0, 3))) {
        case 'WIN':
            define('PHP_EOL', "\r\n");
            break;
        case 'DAR':
            define('PHP_EOL', "\r");
            break;
        default:
            define('PHP_EOL', "\n");
    }
}

/**
 * Bootstrap class that contains meta-functionality for HTML Purifier such as
 * the autoload function.
 *
 * @note
 *      This class may be used without any other files from HTML Purifier.
 */
class HTMLPurifier_Bootstrap
{

    /**
     * Autoload function for HTML Purifier
     * @param string $class Class to load
     * @return bool
     */
    public static function autoload($class)
    {
        $file = HTMLPurifier_Bootstrap::getPath($class);
        if (!$file) {
            return false;
        }
        // Technically speaking, it should be ok and more efficient to
        // just do 'require', but Antonio Parraga reports that with
        // Zend extensions such as Zend debugger and APC, this invariant
        // may be broken.  Since we have efficient alternatives, pay
        // the cost here and avoid the bug.
        require_once HTMLPURIFIER_PREFIX . '/' . $file;
        return true;
    }

    /**
     * Returns the path for a specific class.
     * @param string $class Class path to get
     * @return string
     */
    public static function getPath($class)
    {
        if (strncmp('HTMLPurifier', $class, 12) !== 0) {
            return false;
        }
        // Custom implementations
        if (strncmp('HTMLPurifier_Language_', $class, 22) === 0) {
            $code = str_replace('_', '-', substr($class, 22));
            $file = 'HTMLPurifier/Language/classes/' . $code . '.php';
        } else {
            $file = str_replace('_', '/', $class) . '.php';
        }
        if (!file_exists(HTMLPURIFIER_PREFIX . '/' . $file)) {
            return false;
        }
        return $file;
    }

    /**
     * "Pre-registers" our autoloader on the SPL stack.
     */
    public static function registerAutoload()
    {
        $autoload = array('HTMLPurifier_Bootstrap', 'autoload');
        if (($funcs = spl_autoload_functions()) === false) {
            spl_autoload_register($autoload);
        } elseif (function_exists('spl_autoload_unregister')) {
            if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
                // prepend flag exists, no need for shenanigans
                spl_autoload_register($autoload, true, true);
            } else {
                $buggy  = version_compare(PHP_VERSION, '5.2.11', '<');
                $compat = version_compare(PHP_VERSION, '5.1.2', '<=') &&
                          version_compare(PHP_VERSION, '5.1.0', '>=');
                foreach ($funcs as $func) {
                    if ($buggy && is_array($func)) {
                        // :TRICKY: There are some compatibility issues and some
                        // places where we need to error out
                        $reflector = new ReflectionMethod($func[0], $func[1]);
                        if (!$reflector->isStatic()) {
                            throw new Exception(
                                'HTML Purifier autoloader registrar is not compatible
                                with non-static object methods due to PHP Bug #44144;
                                Please do not use HTMLPurifier.autoload.php (or any
                                file that includes this file); instead, place the code:
                                spl_autoload_register(array(\'HTMLPurifier_Bootstrap\', \'autoload\'))
                                after your own autoloaders.'
                            );
                        }
                        // Suprisingly, spl_autoload_register supports the
                        // Class::staticMethod callback format, although call_user_func doesn't
                        if ($compat) {
                            $func = implode('::', $func);
                        }
                    }
                    spl_autoload_unregister($func);
                }
                spl_autoload_register($autoload);
                foreach ($funcs as $func) {
                    spl_autoload_register($func);
                }
            }
        }
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Configuration object that triggers customizable behavior.
 *
 * @warning This class is strongly defined: that means that the class
 *          will fail if an undefined directive is retrieved or set.
 *
 * @note Many classes that could (although many times don't) use the
 *       configuration object make it a mandatory parameter.  This is
 *       because a configuration object should always be forwarded,
 *       otherwise, you run the risk of missing a parameter and then
 *       being stumped when a configuration directive doesn't work.
 *
 * @todo Reconsider some of the public member variables
 */
class HTMLPurifier_Config
{

    /**
     * HTML Purifier's version
     * @type string
     */
    public $version = '4.10.0';

    /**
     * Whether or not to automatically finalize
     * the object if a read operation is done.
     * @type bool
     */
    public $autoFinalize = true;

    // protected member variables

    /**
     * Namespace indexed array of serials for specific namespaces.
     * @see getSerial() for more info.
     * @type string[]
     */
    protected $serials = array();

    /**
     * Serial for entire configuration object.
     * @type string
     */
    protected $serial;

    /**
     * Parser for variables.
     * @type HTMLPurifier_VarParser_Flexible
     */
    protected $parser = null;

    /**
     * Reference HTMLPurifier_ConfigSchema for value checking.
     * @type HTMLPurifier_ConfigSchema
     * @note This is public for introspective purposes. Please don't
     *       abuse!
     */
    public $def;

    /**
     * Indexed array of definitions.
     * @type HTMLPurifier_Definition[]
     */
    protected $definitions;

    /**
     * Whether or not config is finalized.
     * @type bool
     */
    protected $finalized = false;

    /**
     * Property list containing configuration directives.
     * @type array
     */
    protected $plist;

    /**
     * Whether or not a set is taking place due to an alias lookup.
     * @type bool
     */
    private $aliasMode;

    /**
     * Set to false if you do not want line and file numbers in errors.
     * (useful when unit testing).  This will also compress some errors
     * and exceptions.
     * @type bool
     */
    public $chatty = true;

    /**
     * Current lock; only gets to this namespace are allowed.
     * @type string
     */
    private $lock;

    /**
     * Constructor
     * @param HTMLPurifier_ConfigSchema $definition ConfigSchema that defines
     * what directives are allowed.
     * @param HTMLPurifier_PropertyList $parent
     */
    public function __construct($definition, $parent = null)
    {
        $parent = $parent ? $parent : $definition->defaultPlist;
        $this->plist = new HTMLPurifier_PropertyList($parent);
        $this->def = $definition; // keep a copy around for checking
        $this->parser = new HTMLPurifier_VarParser_Flexible();
    }

    /**
     * Convenience constructor that creates a config object based on a mixed var
     * @param mixed $config Variable that defines the state of the config
     *                      object. Can be: a HTMLPurifier_Config() object,
     *                      an array of directives based on loadArray(),
     *                      or a string filename of an ini file.
     * @param HTMLPurifier_ConfigSchema $schema Schema object
     * @return HTMLPurifier_Config Configured object
     */
    public static function create($config, $schema = null)
    {
        if ($config instanceof HTMLPurifier_Config) {
            // pass-through
            return $config;
        }
        if (!$schema) {
            $ret = HTMLPurifier_Config::createDefault();
        } else {
            $ret = new HTMLPurifier_Config($schema);
        }
        if (is_string($config)) {
            $ret->loadIni($config);
        } elseif (is_array($config)) $ret->loadArray($config);
        return $ret;
    }

    /**
     * Creates a new config object that inherits from a previous one.
     * @param HTMLPurifier_Config $config Configuration object to inherit from.
     * @return HTMLPurifier_Config object with $config as its parent.
     */
    public static function inherit(HTMLPurifier_Config $config)
    {
        return new HTMLPurifier_Config($config->def, $config->plist);
    }

    /**
     * Convenience constructor that creates a default configuration object.
     * @return HTMLPurifier_Config default object.
     */
    public static function createDefault()
    {
        $definition = HTMLPurifier_ConfigSchema::instance();
        $config = new HTMLPurifier_Config($definition);
        return $config;
    }

    /**
     * Retrieves a value from the configuration.
     *
     * @param string $key String key
     * @param mixed $a
     *
     * @return mixed
     */
    public function get($key, $a = null)
    {
        if ($a !== null) {
            $this->triggerError(
                "Using deprecated API: use \$config->get('$key.$a') instead",
                E_USER_WARNING
            );
            $key = "$key.$a";
        }
        if (!$this->finalized) {
            $this->autoFinalize();
        }
        if (!isset($this->def->info[$key])) {
            // can't add % due to SimpleTest bug
            $this->triggerError(
                'Cannot retrieve value of undefined directive ' . htmlspecialchars($key),
                E_USER_WARNING
            );
            return;
        }
        if (isset($this->def->info[$key]->isAlias)) {
            $d = $this->def->info[$key];
            $this->triggerError(
                'Cannot get value from aliased directive, use real name ' . $d->key,
                E_USER_ERROR
            );
            return;
        }
        if ($this->lock) {
            list($ns) = explode('.', $key);
            if ($ns !== $this->lock) {
                $this->triggerError(
                    'Cannot get value of namespace ' . $ns . ' when lock for ' .
                    $this->lock .
                    ' is active, this probably indicates a Definition setup method ' .
                    'is accessing directives that are not within its namespace',
                    E_USER_ERROR
                );
                return;
            }
        }
        return $this->plist->get($key);
    }

    /**
     * Retrieves an array of directives to values from a given namespace
     *
     * @param string $namespace String namespace
     *
     * @return array
     */
    public function getBatch($namespace)
    {
        if (!$this->finalized) {
            $this->autoFinalize();
        }
        $full = $this->getAll();
        if (!isset($full[$namespace])) {
            $this->triggerError(
                'Cannot retrieve undefined namespace ' .
                htmlspecialchars($namespace),
                E_USER_WARNING
            );
            return;
        }
        return $full[$namespace];
    }

    /**
     * Returns a SHA-1 signature of a segment of the configuration object
     * that uniquely identifies that particular configuration
     *
     * @param string $namespace Namespace to get serial for
     *
     * @return string
     * @note Revision is handled specially and is removed from the batch
     *       before processing!
     */
    public function getBatchSerial($namespace)
    {
        if (empty($this->serials[$namespace])) {
            $batch = $this->getBatch($namespace);
            unset($batch['DefinitionRev']);
            $this->serials[$namespace] = sha1(serialize($batch));
        }
        return $this->serials[$namespace];
    }

    /**
     * Returns a SHA-1 signature for the entire configuration object
     * that uniquely identifies that particular configuration
     *
     * @return string
     */
    public function getSerial()
    {
        if (empty($this->serial)) {
            $this->serial = sha1(serialize($this->getAll()));
        }
        return $this->serial;
    }

    /**
     * Retrieves all directives, organized by namespace
     *
     * @warning This is a pretty inefficient function, avoid if you can
     */
    public function getAll()
    {
        if (!$this->finalized) {
            $this->autoFinalize();
        }
        $ret = array();
        foreach ($this->plist->squash() as $name => $value) {
            list($ns, $key) = explode('.', $name, 2);
            $ret[$ns][$key] = $value;
        }
        return $ret;
    }

    /**
     * Sets a value to configuration.
     *
     * @param string $key key
     * @param mixed $value value
     * @param mixed $a
     */
    public function set($key, $value, $a = null)
    {
        if (strpos($key, '.') === false) {
            $namespace = $key;
            $directive = $value;
            $value = $a;
            $key = "$key.$directive";
            $this->triggerError("Using deprecated API: use \$config->set('$key', ...) instead", E_USER_NOTICE);
        } else {
            list($namespace) = explode('.', $key);
        }
        if ($this->isFinalized('Cannot set directive after finalization')) {
            return;
        }
        if (!isset($this->def->info[$key])) {
            $this->triggerError(
                'Cannot set undefined directive ' . htmlspecialchars($key) . ' to value',
                E_USER_WARNING
            );
            return;
        }
        $def = $this->def->info[$key];

        if (isset($def->isAlias)) {
            if ($this->aliasMode) {
                $this->triggerError(
                    'Double-aliases not allowed, please fix '.
                    'ConfigSchema bug with' . $key,
                    E_USER_ERROR
                );
                return;
            }
            $this->aliasMode = true;
            $this->set($def->key, $value);
            $this->aliasMode = false;
            $this->triggerError("$key is an alias, preferred directive name is {$def->key}", E_USER_NOTICE);
            return;
        }

        // Raw type might be negative when using the fully optimized form
        // of stdClass, which indicates allow_null == true
        $rtype = is_int($def) ? $def : $def->type;
        if ($rtype < 0) {
            $type = -$rtype;
            $allow_null = true;
        } else {
            $type = $rtype;
            $allow_null = isset($def->allow_null);
        }

        try {
            $value = $this->parser->parse($value, $type, $allow_null);
        } catch (HTMLPurifier_VarParserException $e) {
            $this->triggerError(
                'Value for ' . $key . ' is of invalid type, should be ' .
                HTMLPurifier_VarParser::getTypeName($type),
                E_USER_WARNING
            );
            return;
        }
        if (is_string($value) && is_object($def)) {
            // resolve value alias if defined
            if (isset($def->aliases[$value])) {
                $value = $def->aliases[$value];
            }
            // check to see if the value is allowed
            if (isset($def->allowed) && !isset($def->allowed[$value])) {
                $this->triggerError(
                    'Value not supported, valid values are: ' .
                    $this->_listify($def->allowed),
                    E_USER_WARNING
                );
                return;
            }
        }
        $this->plist->set($key, $value);

        // reset definitions if the directives they depend on changed
        // this is a very costly process, so it's discouraged
        // with finalization
        if ($namespace == 'HTML' || $namespace == 'CSS' || $namespace == 'URI') {
            $this->definitions[$namespace] = null;
        }

        $this->serials[$namespace] = false;
    }

    /**
     * Convenience function for error reporting
     *
     * @param array $lookup
     *
     * @return string
     */
    private function _listify($lookup)
    {
        $list = array();
        foreach ($lookup as $name => $b) {
            $list[] = $name;
        }
        return implode(', ', $list);
    }

    /**
     * Retrieves object reference to the HTML definition.
     *
     * @param bool $raw Return a copy that has not been setup yet. Must be
     *             called before it's been setup, otherwise won't work.
     * @param bool $optimized If true, this method may return null, to
     *             indicate that a cached version of the modified
     *             definition object is available and no further edits
     *             are necessary.  Consider using
     *             maybeGetRawHTMLDefinition, which is more explicitly
     *             named, instead.
     *
     * @return HTMLPurifier_HTMLDefinition
     */
    public function getHTMLDefinition($raw = false, $optimized = false)
    {
        return $this->getDefinition('HTML', $raw, $optimized);
    }

    /**
     * Retrieves object reference to the CSS definition
     *
     * @param bool $raw Return a copy that has not been setup yet. Must be
     *             called before it's been setup, otherwise won't work.
     * @param bool $optimized If true, this method may return null, to
     *             indicate that a cached version of the modified
     *             definition object is available and no further edits
     *             are necessary.  Consider using
     *             maybeGetRawCSSDefinition, which is more explicitly
     *             named, instead.
     *
     * @return HTMLPurifier_CSSDefinition
     */
    public function getCSSDefinition($raw = false, $optimized = false)
    {
        return $this->getDefinition('CSS', $raw, $optimized);
    }

    /**
     * Retrieves object reference to the URI definition
     *
     * @param bool $raw Return a copy that has not been setup yet. Must be
     *             called before it's been setup, otherwise won't work.
     * @param bool $optimized If true, this method may return null, to
     *             indicate that a cached version of the modified
     *             definition object is available and no further edits
     *             are necessary.  Consider using
     *             maybeGetRawURIDefinition, which is more explicitly
     *             named, instead.
     *
     * @return HTMLPurifier_URIDefinition
     */
    public function getURIDefinition($raw = false, $optimized = false)
    {
        return $this->getDefinition('URI', $raw, $optimized);
    }

    /**
     * Retrieves a definition
     *
     * @param string $type Type of definition: HTML, CSS, etc
     * @param bool $raw Whether or not definition should be returned raw
     * @param bool $optimized Only has an effect when $raw is true.  Whether
     *        or not to return null if the result is already present in
     *        the cache.  This is off by default for backwards
     *        compatibility reasons, but you need to do things this
     *        way in order to ensure that caching is done properly.
     *        Check out enduser-customize.html for more details.
     *        We probably won't ever change this default, as much as the
     *        maybe semantics is the "right thing to do."
     *
     * @throws HTMLPurifier_Exception
     * @return HTMLPurifier_Definition
     */
    public function getDefinition($type, $raw = false, $optimized = false)
    {
        if ($optimized && !$raw) {
            throw new HTMLPurifier_Exception("Cannot set optimized = true when raw = false");
        }
        if (!$this->finalized) {
            $this->autoFinalize();
        }
        // temporarily suspend locks, so we can handle recursive definition calls
        $lock = $this->lock;
        $this->lock = null;
        $factory = HTMLPurifier_DefinitionCacheFactory::instance();
        $cache = $factory->create($type, $this);
        $this->lock = $lock;
        if (!$raw) {
            // full definition
            // ---------------
            // check if definition is in memory
            if (!empty($this->definitions[$type])) {
                $def = $this->definitions[$type];
                // check if the definition is setup
                if ($def->setup) {
                    return $def;
                } else {
                    $def->setup($this);
                    if ($def->optimized) {
                        $cache->add($def, $this);
                    }
                    return $def;
                }
            }
            // check if definition is in cache
            $def = $cache->get($this);
            if ($def) {
                // definition in cache, save to memory and return it
                $this->definitions[$type] = $def;
                return $def;
            }
            // initialize it
            $def = $this->initDefinition($type);
            // set it up
            $this->lock = $type;
            $def->setup($this);
            $this->lock = null;
            // save in cache
            $cache->add($def, $this);
            // return it
            return $def;
        } else {
            // raw definition
            // --------------
            // check preconditions
            $def = null;
            if ($optimized) {
                if (is_null($this->get($type . '.DefinitionID'))) {
                    // fatally error out if definition ID not set
                    throw new HTMLPurifier_Exception(
                        "Cannot retrieve raw version without specifying %$type.DefinitionID"
                    );
                }
            }
            if (!empty($this->definitions[$type])) {
                $def = $this->definitions[$type];
                if ($def->setup && !$optimized) {
                    $extra = $this->chatty ?
                        " (try moving this code block earlier in your initialization)" :
                        "";
                    throw new HTMLPurifier_Exception(
                        "Cannot retrieve raw definition after it has already been setup" .
                        $extra
                    );
                }
                if ($def->optimized === null) {
                    $extra = $this->chatty ? " (try flushing your cache)" : "";
                    throw new HTMLPurifier_Exception(
                        "Optimization status of definition is unknown" . $extra
                    );
                }
                if ($def->optimized !== $optimized) {
                    $msg = $optimized ? "optimized" : "unoptimized";
                    $extra = $this->chatty ?
                        " (this backtrace is for the first inconsistent call, which was for a $msg raw definition)"
                        : "";
                    throw new HTMLPurifier_Exception(
                        "Inconsistent use of optimized and unoptimized raw definition retrievals" . $extra
                    );
                }
            }
            // check if definition was in memory
            if ($def) {
                if ($def->setup) {
                    // invariant: $optimized === true (checked above)
                    return null;
                } else {
                    return $def;
                }
            }
            // if optimized, check if definition was in cache
            // (because we do the memory check first, this formulation
            // is prone to cache slamming, but I think
            // guaranteeing that either /all/ of the raw
            // setup code or /none/ of it is run is more important.)
            if ($optimized) {
                // This code path only gets run once; once we put
                // something in $definitions (which is guaranteed by the
                // trailing code), we always short-circuit above.
                $def = $cache->get($this);
                if ($def) {
                    // save the full definition for later, but don't
                    // return it yet
                    $this->definitions[$type] = $def;
                    return null;
                }
            }
            // check invariants for creation
            if (!$optimized) {
                if (!is_null($this->get($type . '.DefinitionID'))) {
                    if ($this->chatty) {
                        $this->triggerError(
                            'Due to a documentation error in previous version of HTML Purifier, your ' .
                            'definitions are not being cached.  If this is OK, you can remove the ' .
                            '%$type.DefinitionRev and %$type.DefinitionID declaration.  Otherwise, ' .
                            'modify your code to use maybeGetRawDefinition, and test if the returned ' .
                            'value is null before making any edits (if it is null, that means that a ' .
                            'cached version is available, and no raw operations are necessary).  See ' .
                            '<a href="http://htmlpurifier.org/docs/enduser-customize.html#optimized">' .
                            'Customize</a> for more details',
                            E_USER_WARNING
                        );
                    } else {
                        $this->triggerError(
                            "Useless DefinitionID declaration",
                            E_USER_WARNING
                        );
                    }
                }
            }
            // initialize it
            $def = $this->initDefinition($type);
            $def->optimized = $optimized;
            return $def;
        }
        throw new HTMLPurifier_Exception("The impossible happened!");
    }

    /**
     * Initialise definition
     *
     * @param string $type What type of definition to create
     *
     * @return HTMLPurifier_CSSDefinition|HTMLPurifier_HTMLDefinition|HTMLPurifier_URIDefinition
     * @throws HTMLPurifier_Exception
     */
    private function initDefinition($type)
    {
        // quick checks failed, let's create the object
        if ($type == 'HTML') {
            $def = new HTMLPurifier_HTMLDefinition();
        } elseif ($type == 'CSS') {
            $def = new HTMLPurifier_CSSDefinition();
        } elseif ($type == 'URI') {
            $def = new HTMLPurifier_URIDefinition();
        } else {
            throw new HTMLPurifier_Exception(
                "Definition of $type type not supported"
            );
        }
        $this->definitions[$type] = $def;
        return $def;
    }

    public function maybeGetRawDefinition($name)
    {
        return $this->getDefinition($name, true, true);
    }

    /**
     * @return HTMLPurifier_HTMLDefinition
     */
    public function maybeGetRawHTMLDefinition()
    {
        return $this->getDefinition('HTML', true, true);
    }
    
    /**
     * @return HTMLPurifier_CSSDefinition
     */
    public function maybeGetRawCSSDefinition()
    {
        return $this->getDefinition('CSS', true, true);
    }
    
    /**
     * @return HTMLPurifier_URIDefinition
     */
    public function maybeGetRawURIDefinition()
    {
        return $this->getDefinition('URI', true, true);
    }

    /**
     * Loads configuration values from an array with the following structure:
     * Namespace.Directive => Value
     *
     * @param array $config_array Configuration associative array
     */
    public function loadArray($config_array)
    {
        if ($this->isFinalized('Cannot load directives after finalization')) {
            return;
        }
        foreach ($config_array as $key => $value) {
            $key = str_replace('_', '.', $key);
            if (strpos($key, '.') !== false) {
                $this->set($key, $value);
            } else {
                $namespace = $key;
                $namespace_values = $value;
                foreach ($namespace_values as $directive => $value2) {
                    $this->set($namespace .'.'. $directive, $value2);
                }
            }
        }
    }

    /**
     * Returns a list of array(namespace, directive) for all directives
     * that are allowed in a web-form context as per an allowed
     * namespaces/directives list.
     *
     * @param array $allowed List of allowed namespaces/directives
     * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
     *
     * @return array
     */
    public static function getAllowedDirectivesForForm($allowed, $schema = null)
    {
        if (!$schema) {
            $schema = HTMLPurifier_ConfigSchema::instance();
        }
        if ($allowed !== true) {
            if (is_string($allowed)) {
                $allowed = array($allowed);
            }
            $allowed_ns = array();
            $allowed_directives = array();
            $blacklisted_directives = array();
            foreach ($allowed as $ns_or_directive) {
                if (strpos($ns_or_directive, '.') !== false) {
                    // directive
                    if ($ns_or_directive[0] == '-') {
                        $blacklisted_directives[substr($ns_or_directive, 1)] = true;
                    } else {
                        $allowed_directives[$ns_or_directive] = true;
                    }
                } else {
                    // namespace
                    $allowed_ns[$ns_or_directive] = true;
                }
            }
        }
        $ret = array();
        foreach ($schema->info as $key => $def) {
            list($ns, $directive) = explode('.', $key, 2);
            if ($allowed !== true) {
                if (isset($blacklisted_directives["$ns.$directive"])) {
                    continue;
                }
                if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) {
                    continue;
                }
            }
            if (isset($def->isAlias)) {
                continue;
            }
            if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') {
                continue;
            }
            $ret[] = array($ns, $directive);
        }
        return $ret;
    }

    /**
     * Loads configuration values from $_GET/$_POST that were posted
     * via ConfigForm
     *
     * @param array $array $_GET or $_POST array to import
     * @param string|bool $index Index/name that the config variables are in
     * @param array|bool $allowed List of allowed namespaces/directives
     * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
     * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
     *
     * @return mixed
     */
    public static function loadArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null)
    {
        $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema);
        $config = HTMLPurifier_Config::create($ret, $schema);
        return $config;
    }

    /**
     * Merges in configuration values from $_GET/$_POST to object. NOT STATIC.
     *
     * @param array $array $_GET or $_POST array to import
     * @param string|bool $index Index/name that the config variables are in
     * @param array|bool $allowed List of allowed namespaces/directives
     * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
     */
    public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true)
    {
         $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def);
         $this->loadArray($ret);
    }

    /**
     * Prepares an array from a form into something usable for the more
     * strict parts of HTMLPurifier_Config
     *
     * @param array $array $_GET or $_POST array to import
     * @param string|bool $index Index/name that the config variables are in
     * @param array|bool $allowed List of allowed namespaces/directives
     * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
     * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
     *
     * @return array
     */
    public static function prepareArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null)
    {
        if ($index !== false) {
            $array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array();
        }
        $mq = $mq_fix && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc();

        $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema);
        $ret = array();
        foreach ($allowed as $key) {
            list($ns, $directive) = $key;
            $skey = "$ns.$directive";
            if (!empty($array["Null_$skey"])) {
                $ret[$ns][$directive] = null;
                continue;
            }
            if (!isset($array[$skey])) {
                continue;
            }
            $value = $mq ? stripslashes($array[$skey]) : $array[$skey];
            $ret[$ns][$directive] = $value;
        }
        return $ret;
    }

    /**
     * Loads configuration values from an ini file
     *
     * @param string $filename Name of ini file
     */
    public function loadIni($filename)
    {
        if ($this->isFinalized('Cannot load directives after finalization')) {
            return;
        }
        $array = parse_ini_file($filename, true);
        $this->loadArray($array);
    }

    /**
     * Checks whether or not the configuration object is finalized.
     *
     * @param string|bool $error String error message, or false for no error
     *
     * @return bool
     */
    public function isFinalized($error = false)
    {
        if ($this->finalized && $error) {
            $this->triggerError($error, E_USER_ERROR);
        }
        return $this->finalized;
    }

    /**
     * Finalizes configuration only if auto finalize is on and not
     * already finalized
     */
    public function autoFinalize()
    {
        if ($this->autoFinalize) {
            $this->finalize();
        } else {
            $this->plist->squash(true);
        }
    }

    /**
     * Finalizes a configuration object, prohibiting further change
     */
    public function finalize()
    {
        $this->finalized = true;
        $this->parser = null;
    }

    /**
     * Produces a nicely formatted error message by supplying the
     * stack frame information OUTSIDE of HTMLPurifier_Config.
     *
     * @param string $msg An error message
     * @param int $no An error number
     */
    protected function triggerError($msg, $no)
    {
        // determine previous stack frame
        $extra = '';
        if ($this->chatty) {
            $trace = debug_backtrace();
            // zip(tail(trace), trace) -- but PHP is not Haskell har har
            for ($i = 0, $c = count($trace); $i < $c - 1; $i++) {
                // XXX this is not correct on some versions of HTML Purifier
                if ($trace[$i + 1]['class'] === 'HTMLPurifier_Config') {
                    continue;
                }
                $frame = $trace[$i];
                $extra = " invoked on line {$frame['line']} in file {$frame['file']}";
                break;
            }
        }
        trigger_error($msg . $extra, $no);
    }

    /**
     * Returns a serialized form of the configuration object that can
     * be reconstituted.
     *
     * @return string
     */
    public function serialize()
    {
        $this->getDefinition('HTML');
        $this->getDefinition('CSS');
        $this->getDefinition('URI');
        return serialize($this);
    }

}

// vim: et sw=4 sts=4
<?php

/**
 * Generic property list implementation
 */
class HTMLPurifier_PropertyList
{
    /**
     * Internal data-structure for properties.
     * @type array
     */
    protected $data = array();

    /**
     * Parent plist.
     * @type HTMLPurifier_PropertyList
     */
    protected $parent;

    /**
     * Cache.
     * @type array
     */
    protected $cache;

    /**
     * @param HTMLPurifier_PropertyList $parent Parent plist
     */
    public function __construct($parent = null)
    {
        $this->parent = $parent;
    }

    /**
     * Recursively retrieves the value for a key
     * @param string $name
     * @throws HTMLPurifier_Exception
     */
    public function get($name)
    {
        if ($this->has($name)) {
            return $this->data[$name];
        }
        // possible performance bottleneck, convert to iterative if necessary
        if ($this->parent) {
            return $this->parent->get($name);
        }
        throw new HTMLPurifier_Exception("Key '$name' not found");
    }

    /**
     * Sets the value of a key, for this plist
     * @param string $name
     * @param mixed $value
     */
    public function set($name, $value)
    {
        $this->data[$name] = $value;
    }

    /**
     * Returns true if a given key exists
     * @param string $name
     * @return bool
     */
    public function has($name)
    {
        return array_key_exists($name, $this->data);
    }

    /**
     * Resets a value to the value of it's parent, usually the default. If
     * no value is specified, the entire plist is reset.
     * @param string $name
     */
    public function reset($name = null)
    {
        if ($name == null) {
            $this->data = array();
        } else {
            unset($this->data[$name]);
        }
    }

    /**
     * Squashes this property list and all of its property lists into a single
     * array, and returns the array. This value is cached by default.
     * @param bool $force If true, ignores the cache and regenerates the array.
     * @return array
     */
    public function squash($force = false)
    {
        if ($this->cache !== null && !$force) {
            return $this->cache;
        }
        if ($this->parent) {
            return $this->cache = array_merge($this->parent->squash($force), $this->data);
        } else {
            return $this->cache = $this->data;
        }
    }

    /**
     * Returns the parent plist.
     * @return HTMLPurifier_PropertyList
     */
    public function getParent()
    {
        return $this->parent;
    }

    /**
     * Sets the parent plist.
     * @param HTMLPurifier_PropertyList $plist Parent plist
     */
    public function setParent($plist)
    {
        $this->parent = $plist;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * A simple array-backed queue, based off of the classic Okasaki
 * persistent amortized queue.  The basic idea is to maintain two
 * stacks: an input stack and an output stack.  When the output
 * stack runs out, reverse the input stack and use it as the output
 * stack.
 *
 * We don't use the SPL implementation because it's only supported
 * on PHP 5.3 and later.
 *
 * Exercise: Prove that push/pop on this queue take amortized O(1) time.
 *
 * Exercise: Extend this queue to be a deque, while preserving amortized
 * O(1) time.  Some care must be taken on rebalancing to avoid quadratic
 * behaviour caused by repeatedly shuffling data from the input stack
 * to the output stack and back.
 */
class HTMLPurifier_Queue {
    private $input;
    private $output;

    public function __construct($input = array()) {
        $this->input = $input;
        $this->output = array();
    }

    /**
     * Shifts an element off the front of the queue.
     */
    public function shift() {
        if (empty($this->output)) {
            $this->output = array_reverse($this->input);
            $this->input = array();
        }
        if (empty($this->output)) {
            return NULL;
        }
        return array_pop($this->output);
    }

    /**
     * Pushes an element onto the front of the queue.
     */
    public function push($x) {
        array_push($this->input, $x);
    }

    /**
     * Checks if it's empty.
     */
    public function isEmpty() {
        return empty($this->input) && empty($this->output);
    }
}
<?php

/**
 * A zipper is a purely-functional data structure which contains
 * a focus that can be efficiently manipulated.  It is known as
 * a "one-hole context".  This mutable variant implements a zipper
 * for a list as a pair of two arrays, laid out as follows:
 *
 *      Base list: 1 2 3 4 [ ] 6 7 8 9
 *      Front list: 1 2 3 4
 *      Back list: 9 8 7 6
 *
 * User is expected to keep track of the "current element" and properly
 * fill it back in as necessary.  (ToDo: Maybe it's more user friendly
 * to implicitly track the current element?)
 *
 * Nota bene: the current class gets confused if you try to store NULLs
 * in the list.
 */

class HTMLPurifier_Zipper
{
    public $front, $back;

    public function __construct($front, $back) {
        $this->front = $front;
        $this->back = $back;
    }

    /**
     * Creates a zipper from an array, with a hole in the
     * 0-index position.
     * @param Array to zipper-ify.
     * @return Tuple of zipper and element of first position.
     */
    static public function fromArray($array) {
        $z = new self(array(), array_reverse($array));
        $t = $z->delete(); // delete the "dummy hole"
        return array($z, $t);
    }

    /**
     * Convert zipper back into a normal array, optionally filling in
     * the hole with a value. (Usually you should supply a $t, unless you
     * are at the end of the array.)
     */
    public function toArray($t = NULL) {
        $a = $this->front;
        if ($t !== NULL) $a[] = $t;
        for ($i = count($this->back)-1; $i >= 0; $i--) {
            $a[] = $this->back[$i];
        }
        return $a;
    }

    /**
     * Move hole to the next element.
     * @param $t Element to fill hole with
     * @return Original contents of new hole.
     */
    public function next($t) {
        if ($t !== NULL) array_push($this->front, $t);
        return empty($this->back) ? NULL : array_pop($this->back);
    }

    /**
     * Iterated hole advancement.
     * @param $t Element to fill hole with
     * @param $i How many forward to advance hole
     * @return Original contents of new hole, i away
     */
    public function advance($t, $n) {
        for ($i = 0; $i < $n; $i++) {
            $t = $this->next($t);
        }
        return $t;
    }

    /**
     * Move hole to the previous element
     * @param $t Element to fill hole with
     * @return Original contents of new hole.
     */
    public function prev($t) {
        if ($t !== NULL) array_push($this->back, $t);
        return empty($this->front) ? NULL : array_pop($this->front);
    }

    /**
     * Delete contents of current hole, shifting hole to
     * next element.
     * @return Original contents of new hole.
     */
    public function delete() {
        return empty($this->back) ? NULL : array_pop($this->back);
    }

    /**
     * Returns true if we are at the end of the list.
     * @return bool
     */
    public function done() {
        return empty($this->back);
    }

    /**
     * Insert element before hole.
     * @param Element to insert
     */
    public function insertBefore($t) {
        if ($t !== NULL) array_push($this->front, $t);
    }

    /**
     * Insert element after hole.
     * @param Element to insert
     */
    public function insertAfter($t) {
        if ($t !== NULL) array_push($this->back, $t);
    }

    /**
     * Splice in multiple elements at hole.  Functional specification
     * in terms of array_splice:
     *
     *      $arr1 = $arr;
     *      $old1 = array_splice($arr1, $i, $delete, $replacement);
     *
     *      list($z, $t) = HTMLPurifier_Zipper::fromArray($arr);
     *      $t = $z->advance($t, $i);
     *      list($old2, $t) = $z->splice($t, $delete, $replacement);
     *      $arr2 = $z->toArray($t);
     *
     *      assert($old1 === $old2);
     *      assert($arr1 === $arr2);
     *
     * NB: the absolute index location after this operation is
     * *unchanged!*
     *
     * @param Current contents of hole.
     */
    public function splice($t, $delete, $replacement) {
        // delete
        $old = array();
        $r = $t;
        for ($i = $delete; $i > 0; $i--) {
            $old[] = $r;
            $r = $this->delete();
        }
        // insert
        for ($i = count($replacement)-1; $i >= 0; $i--) {
            $this->insertAfter($r);
            $r = $replacement[$i];
        }
        return array($old, $r);
    }
}
<?php

/**
 * Implements safety checks for safe iframes.
 *
 * @warning This filter is *critical* for ensuring that %HTML.SafeIframe
 * works safely.
 */
class HTMLPurifier_URIFilter_SafeIframe extends HTMLPurifier_URIFilter
{
    /**
     * @type string
     */
    public $name = 'SafeIframe';

    /**
     * @type bool
     */
    public $always_load = true;

    /**
     * @type string
     */
    protected $regexp = null;

    // XXX: The not so good bit about how this is all set up now is we
    // can't check HTML.SafeIframe in the 'prepare' step: we have to
    // defer till the actual filtering.
    /**
     * @param HTMLPurifier_Config $config
     * @return bool
     */
    public function prepare($config)
    {
        $this->regexp = $config->get('URI.SafeIframeRegexp');
        return true;
    }

    /**
     * @param HTMLPurifier_URI $uri
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool
     */
    public function filter(&$uri, $config, $context)
    {
        // check if filter not applicable
        if (!$config->get('HTML.SafeIframe')) {
            return true;
        }
        // check if the filter should actually trigger
        if (!$context->get('EmbeddedURI', true)) {
            return true;
        }
        $token = $context->get('CurrentToken', true);
        if (!($token && $token->name == 'iframe')) {
            return true;
        }
        // check if we actually have some whitelists enabled
        if ($this->regexp === null) {
            return false;
        }
        // actually check the whitelists
        return preg_match($this->regexp, $uri->toString());
    }
}

// vim: et sw=4 sts=4
<?php

class HTMLPurifier_URIFilter_Munge extends HTMLPurifier_URIFilter
{
    /**
     * @type string
     */
    public $name = 'Munge';

    /**
     * @type bool
     */
    public $post = true;

    /**
     * @type string
     */
    private $target;

    /**
     * @type HTMLPurifier_URIParser
     */
    private $parser;

    /**
     * @type bool
     */
    private $doEmbed;

    /**
     * @type string
     */
    private $secretKey;

    /**
     * @type array
     */
    protected $replace = array();

    /**
     * @param HTMLPurifier_Config $config
     * @return bool
     */
    public function prepare($config)
    {
        $this->target = $config->get('URI.' . $this->name);
        $this->parser = new HTMLPurifier_URIParser();
        $this->doEmbed = $config->get('URI.MungeResources');
        $this->secretKey = $config->get('URI.MungeSecretKey');
        if ($this->secretKey && !function_exists('hash_hmac')) {
            throw new Exception("Cannot use %URI.MungeSecretKey without hash_hmac support.");
        }
        return true;
    }

    /**
     * @param HTMLPurifier_URI $uri
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool
     */
    public function filter(&$uri, $config, $context)
    {
        if ($context->get('EmbeddedURI', true) && !$this->doEmbed) {
            return true;
        }

        $scheme_obj = $uri->getSchemeObj($config, $context);
        if (!$scheme_obj) {
            return true;
        } // ignore unknown schemes, maybe another postfilter did it
        if (!$scheme_obj->browsable) {
            return true;
        } // ignore non-browseable schemes, since we can't munge those in a reasonable way
        if ($uri->isBenign($config, $context)) {
            return true;
        } // don't redirect if a benign URL

        $this->makeReplace($uri, $config, $context);
        $this->replace = array_map('rawurlencode', $this->replace);

        $new_uri = strtr($this->target, $this->replace);
        $new_uri = $this->parser->parse($new_uri);
        // don't redirect if the target host is the same as the
        // starting host
        if ($uri->host === $new_uri->host) {
            return true;
        }
        $uri = $new_uri; // overwrite
        return true;
    }

    /**
     * @param HTMLPurifier_URI $uri
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     */
    protected function makeReplace($uri, $config, $context)
    {
        $string = $uri->toString();
        // always available
        $this->replace['%s'] = $string;
        $this->replace['%r'] = $context->get('EmbeddedURI', true);
        $token = $context->get('CurrentToken', true);
        $this->replace['%n'] = $token ? $token->name : null;
        $this->replace['%m'] = $context->get('CurrentAttr', true);
        $this->replace['%p'] = $context->get('CurrentCSSProperty', true);
        // not always available
        if ($this->secretKey) {
            $this->replace['%t'] = hash_hmac("sha256", $string, $this->secretKey);
        }
    }
}

// vim: et sw=4 sts=4
<?php

// It's not clear to me whether or not Punycode means that hostnames
// do not have canonical forms anymore. As far as I can tell, it's
// not a problem (punycoding should be identity when no Unicode
// points are involved), but I'm not 100% sure
class HTMLPurifier_URIFilter_HostBlacklist extends HTMLPurifier_URIFilter
{
    /**
     * @type string
     */
    public $name = 'HostBlacklist';

    /**
     * @type array
     */
    protected $blacklist = array();

    /**
     * @param HTMLPurifier_Config $config
     * @return bool
     */
    public function prepare($config)
    {
        $this->blacklist = $config->get('URI.HostBlacklist');
        return true;
    }

    /**
     * @param HTMLPurifier_URI $uri
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool
     */
    public function filter(&$uri, $config, $context)
    {
        foreach ($this->blacklist as $blacklisted_host_fragment) {
            if (strpos($uri->host, $blacklisted_host_fragment) !== false) {
                return false;
            }
        }
        return true;
    }
}

// vim: et sw=4 sts=4
<?php

class HTMLPurifier_URIFilter_DisableResources extends HTMLPurifier_URIFilter
{
    /**
     * @type string
     */
    public $name = 'DisableResources';

    /**
     * @param HTMLPurifier_URI $uri
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool
     */
    public function filter(&$uri, $config, $context)
    {
        return !$context->get('EmbeddedURI', true);
    }
}

// vim: et sw=4 sts=4
<?php

// does not support network paths

class HTMLPurifier_URIFilter_MakeAbsolute extends HTMLPurifier_URIFilter
{
    /**
     * @type string
     */
    public $name = 'MakeAbsolute';

    /**
     * @type
     */
    protected $base;

    /**
     * @type array
     */
    protected $basePathStack = array();

    /**
     * @param HTMLPurifier_Config $config
     * @return bool
     */
    public function prepare($config)
    {
        $def = $config->getDefinition('URI');
        $this->base = $def->base;
        if (is_null($this->base)) {
            trigger_error(
                'URI.MakeAbsolute is being ignored due to lack of ' .
                'value for URI.Base configuration',
                E_USER_WARNING
            );
            return false;
        }
        $this->base->fragment = null; // fragment is invalid for base URI
        $stack = explode('/', $this->base->path);
        array_pop($stack); // discard last segment
        $stack = $this->_collapseStack($stack); // do pre-parsing
        $this->basePathStack = $stack;
        return true;
    }

    /**
     * @param HTMLPurifier_URI $uri
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool
     */
    public function filter(&$uri, $config, $context)
    {
        if (is_null($this->base)) {
            return true;
        } // abort early
        if ($uri->path === '' && is_null($uri->scheme) &&
            is_null($uri->host) && is_null($uri->query) && is_null($uri->fragment)) {
            // reference to current document
            $uri = clone $this->base;
            return true;
        }
        if (!is_null($uri->scheme)) {
            // absolute URI already: don't change
            if (!is_null($uri->host)) {
                return true;
            }
            $scheme_obj = $uri->getSchemeObj($config, $context);
            if (!$scheme_obj) {
                // scheme not recognized
                return false;
            }
            if (!$scheme_obj->hierarchical) {
                // non-hierarchal URI with explicit scheme, don't change
                return true;
            }
            // special case: had a scheme but always is hierarchical and had no authority
        }
        if (!is_null($uri->host)) {
            // network path, don't bother
            return true;
        }
        if ($uri->path === '') {
            $uri->path = $this->base->path;
        } elseif ($uri->path[0] !== '/') {
            // relative path, needs more complicated processing
            $stack = explode('/', $uri->path);
            $new_stack = array_merge($this->basePathStack, $stack);
            if ($new_stack[0] !== '' && !is_null($this->base->host)) {
                array_unshift($new_stack, '');
            }
            $new_stack = $this->_collapseStack($new_stack);
            $uri->path = implode('/', $new_stack);
        } else {
            // absolute path, but still we should collapse
            $uri->path = implode('/', $this->_collapseStack(explode('/', $uri->path)));
        }
        // re-combine
        $uri->scheme = $this->base->scheme;
        if (is_null($uri->userinfo)) {
            $uri->userinfo = $this->base->userinfo;
        }
        if (is_null($uri->host)) {
            $uri->host = $this->base->host;
        }
        if (is_null($uri->port)) {
            $uri->port = $this->base->port;
        }
        return true;
    }

    /**
     * Resolve dots and double-dots in a path stack
     * @param array $stack
     * @return array
     */
    private function _collapseStack($stack)
    {
        $result = array();
        $is_folder = false;
        for ($i = 0; isset($stack[$i]); $i++) {
            $is_folder = false;
            // absorb an internally duplicated slash
            if ($stack[$i] == '' && $i && isset($stack[$i + 1])) {
                continue;
            }
            if ($stack[$i] == '..') {
                if (!empty($result)) {
                    $segment = array_pop($result);
                    if ($segment === '' && empty($result)) {
                        // error case: attempted to back out too far:
                        // restore the leading slash
                        $result[] = '';
                    } elseif ($segment === '..') {
                        $result[] = '..'; // cannot remove .. with ..
                    }
                } else {
                    // relative path, preserve the double-dots
                    $result[] = '..';
                }
                $is_folder = true;
                continue;
            }
            if ($stack[$i] == '.') {
                // silently absorb
                $is_folder = true;
                continue;
            }
            $result[] = $stack[$i];
        }
        if ($is_folder) {
            $result[] = '';
        }
        return $result;
    }
}

// vim: et sw=4 sts=4
<?php

class HTMLPurifier_URIFilter_DisableExternalResources extends HTMLPurifier_URIFilter_DisableExternal
{
    /**
     * @type string
     */
    public $name = 'DisableExternalResources';

    /**
     * @param HTMLPurifier_URI $uri
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool
     */
    public function filter(&$uri, $config, $context)
    {
        if (!$context->get('EmbeddedURI', true)) {
            return true;
        }
        return parent::filter($uri, $config, $context);
    }
}

// vim: et sw=4 sts=4
<?php

class HTMLPurifier_URIFilter_DisableExternal extends HTMLPurifier_URIFilter
{
    /**
     * @type string
     */
    public $name = 'DisableExternal';

    /**
     * @type array
     */
    protected $ourHostParts = false;

    /**
     * @param HTMLPurifier_Config $config
     * @return void
     */
    public function prepare($config)
    {
        $our_host = $config->getDefinition('URI')->host;
        if ($our_host !== null) {
            $this->ourHostParts = array_reverse(explode('.', $our_host));
        }
    }

    /**
     * @param HTMLPurifier_URI $uri Reference
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool
     */
    public function filter(&$uri, $config, $context)
    {
        if (is_null($uri->host)) {
            return true;
        }
        if ($this->ourHostParts === false) {
            return false;
        }
        $host_parts = array_reverse(explode('.', $uri->host));
        foreach ($this->ourHostParts as $i => $x) {
            if (!isset($host_parts[$i])) {
                return false;
            }
            if ($host_parts[$i] != $this->ourHostParts[$i]) {
                return false;
            }
        }
        return true;
    }
}

// vim: et sw=4 sts=4
<?php

class HTMLPurifier_ConfigSchema_InterchangeBuilder
{

    /**
     * Used for processing DEFAULT, nothing else.
     * @type HTMLPurifier_VarParser
     */
    protected $varParser;

    /**
     * @param HTMLPurifier_VarParser $varParser
     */
    public function __construct($varParser = null)
    {
        $this->varParser = $varParser ? $varParser : new HTMLPurifier_VarParser_Native();
    }

    /**
     * @param string $dir
     * @return HTMLPurifier_ConfigSchema_Interchange
     */
    public static function buildFromDirectory($dir = null)
    {
        $builder = new HTMLPurifier_ConfigSchema_InterchangeBuilder();
        $interchange = new HTMLPurifier_ConfigSchema_Interchange();
        return $builder->buildDir($interchange, $dir);
    }

    /**
     * @param HTMLPurifier_ConfigSchema_Interchange $interchange
     * @param string $dir
     * @return HTMLPurifier_ConfigSchema_Interchange
     */
    public function buildDir($interchange, $dir = null)
    {
        if (!$dir) {
            $dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema';
        }
        if (file_exists($dir . '/info.ini')) {
            $info = parse_ini_file($dir . '/info.ini');
            $interchange->name = $info['name'];
        }

        $files = array();
        $dh = opendir($dir);
        while (false !== ($file = readdir($dh))) {
            if (!$file || $file[0] == '.' || strrchr($file, '.') !== '.txt') {
                continue;
            }
            $files[] = $file;
        }
        closedir($dh);

        sort($files);
        foreach ($files as $file) {
            $this->buildFile($interchange, $dir . '/' . $file);
        }
        return $interchange;
    }

    /**
     * @param HTMLPurifier_ConfigSchema_Interchange $interchange
     * @param string $file
     */
    public function buildFile($interchange, $file)
    {
        $parser = new HTMLPurifier_StringHashParser();
        $this->build(
            $interchange,
            new HTMLPurifier_StringHash($parser->parseFile($file))
        );
    }

    /**
     * Builds an interchange object based on a hash.
     * @param HTMLPurifier_ConfigSchema_Interchange $interchange HTMLPurifier_ConfigSchema_Interchange object to build
     * @param HTMLPurifier_StringHash $hash source data
     * @throws HTMLPurifier_ConfigSchema_Exception
     */
    public function build($interchange, $hash)
    {
        if (!$hash instanceof HTMLPurifier_StringHash) {
            $hash = new HTMLPurifier_StringHash($hash);
        }
        if (!isset($hash['ID'])) {
            throw new HTMLPurifier_ConfigSchema_Exception('Hash does not have any ID');
        }
        if (strpos($hash['ID'], '.') === false) {
            if (count($hash) == 2 && isset($hash['DESCRIPTION'])) {
                $hash->offsetGet('DESCRIPTION'); // prevent complaining
            } else {
                throw new HTMLPurifier_ConfigSchema_Exception('All directives must have a namespace');
            }
        } else {
            $this->buildDirective($interchange, $hash);
        }
        $this->_findUnused($hash);
    }

    /**
     * @param HTMLPurifier_ConfigSchema_Interchange $interchange
     * @param HTMLPurifier_StringHash $hash
     * @throws HTMLPurifier_ConfigSchema_Exception
     */
    public function buildDirective($interchange, $hash)
    {
        $directive = new HTMLPurifier_ConfigSchema_Interchange_Directive();

        // These are required elements:
        $directive->id = $this->id($hash->offsetGet('ID'));
        $id = $directive->id->toString(); // convenience

        if (isset($hash['TYPE'])) {
            $type = explode('/', $hash->offsetGet('TYPE'));
            if (isset($type[1])) {
                $directive->typeAllowsNull = true;
            }
            $directive->type = $type[0];
        } else {
            throw new HTMLPurifier_ConfigSchema_Exception("TYPE in directive hash '$id' not defined");
        }

        if (isset($hash['DEFAULT'])) {
            try {
                $directive->default = $this->varParser->parse(
                    $hash->offsetGet('DEFAULT'),
                    $directive->type,
                    $directive->typeAllowsNull
                );
            } catch (HTMLPurifier_VarParserException $e) {
                throw new HTMLPurifier_ConfigSchema_Exception($e->getMessage() . " in DEFAULT in directive hash '$id'");
            }
        }

        if (isset($hash['DESCRIPTION'])) {
            $directive->description = $hash->offsetGet('DESCRIPTION');
        }

        if (isset($hash['ALLOWED'])) {
            $directive->allowed = $this->lookup($this->evalArray($hash->offsetGet('ALLOWED')));
        }

        if (isset($hash['VALUE-ALIASES'])) {
            $directive->valueAliases = $this->evalArray($hash->offsetGet('VALUE-ALIASES'));
        }

        if (isset($hash['ALIASES'])) {
            $raw_aliases = trim($hash->offsetGet('ALIASES'));
            $aliases = preg_split('/\s*,\s*/', $raw_aliases);
            foreach ($aliases as $alias) {
                $directive->aliases[] = $this->id($alias);
            }
        }

        if (isset($hash['VERSION'])) {
            $directive->version = $hash->offsetGet('VERSION');
        }

        if (isset($hash['DEPRECATED-USE'])) {
            $directive->deprecatedUse = $this->id($hash->offsetGet('DEPRECATED-USE'));
        }

        if (isset($hash['DEPRECATED-VERSION'])) {
            $directive->deprecatedVersion = $hash->offsetGet('DEPRECATED-VERSION');
        }

        if (isset($hash['EXTERNAL'])) {
            $directive->external = preg_split('/\s*,\s*/', trim($hash->offsetGet('EXTERNAL')));
        }

        $interchange->addDirective($directive);
    }

    /**
     * Evaluates an array PHP code string without array() wrapper
     * @param string $contents
     */
    protected function evalArray($contents)
    {
        return eval('return array(' . $contents . ');');
    }

    /**
     * Converts an array list into a lookup array.
     * @param array $array
     * @return array
     */
    protected function lookup($array)
    {
        $ret = array();
        foreach ($array as $val) {
            $ret[$val] = true;
        }
        return $ret;
    }

    /**
     * Convenience function that creates an HTMLPurifier_ConfigSchema_Interchange_Id
     * object based on a string Id.
     * @param string $id
     * @return HTMLPurifier_ConfigSchema_Interchange_Id
     */
    protected function id($id)
    {
        return HTMLPurifier_ConfigSchema_Interchange_Id::make($id);
    }

    /**
     * Triggers errors for any unused keys passed in the hash; such keys
     * may indicate typos, missing values, etc.
     * @param HTMLPurifier_StringHash $hash Hash to check.
     */
    protected function _findUnused($hash)
    {
        $accessed = $hash->getAccessed();
        foreach ($hash as $k => $v) {
            if (!isset($accessed[$k])) {
                trigger_error("String hash key '$k' not used by builder", E_USER_NOTICE);
            }
        }
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Performs validations on HTMLPurifier_ConfigSchema_Interchange
 *
 * @note If you see '// handled by InterchangeBuilder', that means a
 *       design decision in that class would prevent this validation from
 *       ever being necessary. We have them anyway, however, for
 *       redundancy.
 */
class HTMLPurifier_ConfigSchema_Validator
{

    /**
     * @type HTMLPurifier_ConfigSchema_Interchange
     */
    protected $interchange;

    /**
     * @type array
     */
    protected $aliases;

    /**
     * Context-stack to provide easy to read error messages.
     * @type array
     */
    protected $context = array();

    /**
     * to test default's type.
     * @type HTMLPurifier_VarParser
     */
    protected $parser;

    public function __construct()
    {
        $this->parser = new HTMLPurifier_VarParser();
    }

    /**
     * Validates a fully-formed interchange object.
     * @param HTMLPurifier_ConfigSchema_Interchange $interchange
     * @return bool
     */
    public function validate($interchange)
    {
        $this->interchange = $interchange;
        $this->aliases = array();
        // PHP is a bit lax with integer <=> string conversions in
        // arrays, so we don't use the identical !== comparison
        foreach ($interchange->directives as $i => $directive) {
            $id = $directive->id->toString();
            if ($i != $id) {
                $this->error(false, "Integrity violation: key '$i' does not match internal id '$id'");
            }
            $this->validateDirective($directive);
        }
        return true;
    }

    /**
     * Validates a HTMLPurifier_ConfigSchema_Interchange_Id object.
     * @param HTMLPurifier_ConfigSchema_Interchange_Id $id
     */
    public function validateId($id)
    {
        $id_string = $id->toString();
        $this->context[] = "id '$id_string'";
        if (!$id instanceof HTMLPurifier_ConfigSchema_Interchange_Id) {
            // handled by InterchangeBuilder
            $this->error(false, 'is not an instance of HTMLPurifier_ConfigSchema_Interchange_Id');
        }
        // keys are now unconstrained (we might want to narrow down to A-Za-z0-9.)
        // we probably should check that it has at least one namespace
        $this->with($id, 'key')
            ->assertNotEmpty()
            ->assertIsString(); // implicit assertIsString handled by InterchangeBuilder
        array_pop($this->context);
    }

    /**
     * Validates a HTMLPurifier_ConfigSchema_Interchange_Directive object.
     * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d
     */
    public function validateDirective($d)
    {
        $id = $d->id->toString();
        $this->context[] = "directive '$id'";
        $this->validateId($d->id);

        $this->with($d, 'description')
            ->assertNotEmpty();

        // BEGIN - handled by InterchangeBuilder
        $this->with($d, 'type')
            ->assertNotEmpty();
        $this->with($d, 'typeAllowsNull')
            ->assertIsBool();
        try {
            // This also tests validity of $d->type
            $this->parser->parse($d->default, $d->type, $d->typeAllowsNull);
        } catch (HTMLPurifier_VarParserException $e) {
            $this->error('default', 'had error: ' . $e->getMessage());
        }
        // END - handled by InterchangeBuilder

        if (!is_null($d->allowed) || !empty($d->valueAliases)) {
            // allowed and valueAliases require that we be dealing with
            // strings, so check for that early.
            $d_int = HTMLPurifier_VarParser::$types[$d->type];
            if (!isset(HTMLPurifier_VarParser::$stringTypes[$d_int])) {
                $this->error('type', 'must be a string type when used with allowed or value aliases');
            }
        }

        $this->validateDirectiveAllowed($d);
        $this->validateDirectiveValueAliases($d);
        $this->validateDirectiveAliases($d);

        array_pop($this->context);
    }

    /**
     * Extra validation if $allowed member variable of
     * HTMLPurifier_ConfigSchema_Interchange_Directive is defined.
     * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d
     */
    public function validateDirectiveAllowed($d)
    {
        if (is_null($d->allowed)) {
            return;
        }
        $this->with($d, 'allowed')
            ->assertNotEmpty()
            ->assertIsLookup(); // handled by InterchangeBuilder
        if (is_string($d->default) && !isset($d->allowed[$d->default])) {
            $this->error('default', 'must be an allowed value');
        }
        $this->context[] = 'allowed';
        foreach ($d->allowed as $val => $x) {
            if (!is_string($val)) {
                $this->error("value $val", 'must be a string');
            }
        }
        array_pop($this->context);
    }

    /**
     * Extra validation if $valueAliases member variable of
     * HTMLPurifier_ConfigSchema_Interchange_Directive is defined.
     * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d
     */
    public function validateDirectiveValueAliases($d)
    {
        if (is_null($d->valueAliases)) {
            return;
        }
        $this->with($d, 'valueAliases')
            ->assertIsArray(); // handled by InterchangeBuilder
        $this->context[] = 'valueAliases';
        foreach ($d->valueAliases as $alias => $real) {
            if (!is_string($alias)) {
                $this->error("alias $alias", 'must be a string');
            }
            if (!is_string($real)) {
                $this->error("alias target $real from alias '$alias'", 'must be a string');
            }
            if ($alias === $real) {
                $this->error("alias '$alias'", "must not be an alias to itself");
            }
        }
        if (!is_null($d->allowed)) {
            foreach ($d->valueAliases as $alias => $real) {
                if (isset($d->allowed[$alias])) {
                    $this->error("alias '$alias'", 'must not be an allowed value');
                } elseif (!isset($d->allowed[$real])) {
                    $this->error("alias '$alias'", 'must be an alias to an allowed value');
                }
            }
        }
        array_pop($this->context);
    }

    /**
     * Extra validation if $aliases member variable of
     * HTMLPurifier_ConfigSchema_Interchange_Directive is defined.
     * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d
     */
    public function validateDirectiveAliases($d)
    {
        $this->with($d, 'aliases')
            ->assertIsArray(); // handled by InterchangeBuilder
        $this->context[] = 'aliases';
        foreach ($d->aliases as $alias) {
            $this->validateId($alias);
            $s = $alias->toString();
            if (isset($this->interchange->directives[$s])) {
                $this->error("alias '$s'", 'collides with another directive');
            }
            if (isset($this->aliases[$s])) {
                $other_directive = $this->aliases[$s];
                $this->error("alias '$s'", "collides with alias for directive '$other_directive'");
            }
            $this->aliases[$s] = $d->id->toString();
        }
        array_pop($this->context);
    }

    // protected helper functions

    /**
     * Convenience function for generating HTMLPurifier_ConfigSchema_ValidatorAtom
     * for validating simple member variables of objects.
     * @param $obj
     * @param $member
     * @return HTMLPurifier_ConfigSchema_ValidatorAtom
     */
    protected function with($obj, $member)
    {
        return new HTMLPurifier_ConfigSchema_ValidatorAtom($this->getFormattedContext(), $obj, $member);
    }

    /**
     * Emits an error, providing helpful context.
     * @throws HTMLPurifier_ConfigSchema_Exception
     */
    protected function error($target, $msg)
    {
        if ($target !== false) {
            $prefix = ucfirst($target) . ' in ' . $this->getFormattedContext();
        } else {
            $prefix = ucfirst($this->getFormattedContext());
        }
        throw new HTMLPurifier_ConfigSchema_Exception(trim($prefix . ' ' . $msg));
    }

    /**
     * Returns a formatted context string.
     * @return string
     */
    protected function getFormattedContext()
    {
        return implode(' in ', array_reverse($this->context));
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Represents a directive ID in the interchange format.
 */
class HTMLPurifier_ConfigSchema_Interchange_Id
{

    /**
     * @type string
     */
    public $key;

    /**
     * @param string $key
     */
    public function __construct($key)
    {
        $this->key = $key;
    }

    /**
     * @return string
     * @warning This is NOT magic, to ensure that people don't abuse SPL and
     *          cause problems for PHP 5.0 support.
     */
    public function toString()
    {
        return $this->key;
    }

    /**
     * @return string
     */
    public function getRootNamespace()
    {
        return substr($this->key, 0, strpos($this->key, "."));
    }

    /**
     * @return string
     */
    public function getDirective()
    {
        return substr($this->key, strpos($this->key, ".") + 1);
    }

    /**
     * @param string $id
     * @return HTMLPurifier_ConfigSchema_Interchange_Id
     */
    public static function make($id)
    {
        return new HTMLPurifier_ConfigSchema_Interchange_Id($id);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Interchange component class describing configuration directives.
 */
class HTMLPurifier_ConfigSchema_Interchange_Directive
{

    /**
     * ID of directive.
     * @type HTMLPurifier_ConfigSchema_Interchange_Id
     */
    public $id;

    /**
     * Type, e.g. 'integer' or 'istring'.
     * @type string
     */
    public $type;

    /**
     * Default value, e.g. 3 or 'DefaultVal'.
     * @type mixed
     */
    public $default;

    /**
     * HTML description.
     * @type string
     */
    public $description;

    /**
     * Whether or not null is allowed as a value.
     * @type bool
     */
    public $typeAllowsNull = false;

    /**
     * Lookup table of allowed scalar values.
     * e.g. array('allowed' => true).
     * Null if all values are allowed.
     * @type array
     */
    public $allowed;

    /**
     * List of aliases for the directive.
     * e.g. array(new HTMLPurifier_ConfigSchema_Interchange_Id('Ns', 'Dir'))).
     * @type HTMLPurifier_ConfigSchema_Interchange_Id[]
     */
    public $aliases = array();

    /**
     * Hash of value aliases, e.g. array('alt' => 'real'). Null if value
     * aliasing is disabled (necessary for non-scalar types).
     * @type array
     */
    public $valueAliases;

    /**
     * Version of HTML Purifier the directive was introduced, e.g. '1.3.1'.
     * Null if the directive has always existed.
     * @type string
     */
    public $version;

    /**
     * ID of directive that supercedes this old directive.
     * Null if not deprecated.
     * @type HTMLPurifier_ConfigSchema_Interchange_Id
     */
    public $deprecatedUse;

    /**
     * Version of HTML Purifier this directive was deprecated. Null if not
     * deprecated.
     * @type string
     */
    public $deprecatedVersion;

    /**
     * List of external projects this directive depends on, e.g. array('CSSTidy').
     * @type array
     */
    public $external = array();
}

// vim: et sw=4 sts=4
<?php

/**
 * Fluent interface for validating the contents of member variables.
 * This should be immutable. See HTMLPurifier_ConfigSchema_Validator for
 * use-cases. We name this an 'atom' because it's ONLY for validations that
 * are independent and usually scalar.
 */
class HTMLPurifier_ConfigSchema_ValidatorAtom
{
    /**
     * @type string
     */
    protected $context;

    /**
     * @type object
     */
    protected $obj;

    /**
     * @type string
     */
    protected $member;

    /**
     * @type mixed
     */
    protected $contents;

    public function __construct($context, $obj, $member)
    {
        $this->context = $context;
        $this->obj = $obj;
        $this->member = $member;
        $this->contents =& $obj->$member;
    }

    /**
     * @return HTMLPurifier_ConfigSchema_ValidatorAtom
     */
    public function assertIsString()
    {
        if (!is_string($this->contents)) {
            $this->error('must be a string');
        }
        return $this;
    }

    /**
     * @return HTMLPurifier_ConfigSchema_ValidatorAtom
     */
    public function assertIsBool()
    {
        if (!is_bool($this->contents)) {
            $this->error('must be a boolean');
        }
        return $this;
    }

    /**
     * @return HTMLPurifier_ConfigSchema_ValidatorAtom
     */
    public function assertIsArray()
    {
        if (!is_array($this->contents)) {
            $this->error('must be an array');
        }
        return $this;
    }

    /**
     * @return HTMLPurifier_ConfigSchema_ValidatorAtom
     */
    public function assertNotNull()
    {
        if ($this->contents === null) {
            $this->error('must not be null');
        }
        return $this;
    }

    /**
     * @return HTMLPurifier_ConfigSchema_ValidatorAtom
     */
    public function assertAlnum()
    {
        $this->assertIsString();
        if (!ctype_alnum($this->contents)) {
            $this->error('must be alphanumeric');
        }
        return $this;
    }

    /**
     * @return HTMLPurifier_ConfigSchema_ValidatorAtom
     */
    public function assertNotEmpty()
    {
        if (empty($this->contents)) {
            $this->error('must not be empty');
        }
        return $this;
    }

    /**
     * @return HTMLPurifier_ConfigSchema_ValidatorAtom
     */
    public function assertIsLookup()
    {
        $this->assertIsArray();
        foreach ($this->contents as $v) {
            if ($v !== true) {
                $this->error('must be a lookup array');
            }
        }
        return $this;
    }

    /**
     * @param string $msg
     * @throws HTMLPurifier_ConfigSchema_Exception
     */
    protected function error($msg)
    {
        throw new HTMLPurifier_ConfigSchema_Exception(ucfirst($this->member) . ' in ' . $this->context . ' ' . $msg);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Converts HTMLPurifier_ConfigSchema_Interchange to an XML format,
 * which can be further processed to generate documentation.
 */
class HTMLPurifier_ConfigSchema_Builder_Xml extends XMLWriter
{

    /**
     * @type HTMLPurifier_ConfigSchema_Interchange
     */
    protected $interchange;

    /**
     * @type string
     */
    private $namespace;

    /**
     * @param string $html
     */
    protected function writeHTMLDiv($html)
    {
        $this->startElement('div');

        $purifier = HTMLPurifier::getInstance();
        $html = $purifier->purify($html);
        $this->writeAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
        $this->writeRaw($html);

        $this->endElement(); // div
    }

    /**
     * @param mixed $var
     * @return string
     */
    protected function export($var)
    {
        if ($var === array()) {
            return 'array()';
        }
        return var_export($var, true);
    }

    /**
     * @param HTMLPurifier_ConfigSchema_Interchange $interchange
     */
    public function build($interchange)
    {
        // global access, only use as last resort
        $this->interchange = $interchange;

        $this->setIndent(true);
        $this->startDocument('1.0', 'UTF-8');
        $this->startElement('configdoc');
        $this->writeElement('title', $interchange->name);

        foreach ($interchange->directives as $directive) {
            $this->buildDirective($directive);
        }

        if ($this->namespace) {
            $this->endElement();
        } // namespace

        $this->endElement(); // configdoc
        $this->flush();
    }

    /**
     * @param HTMLPurifier_ConfigSchema_Interchange_Directive $directive
     */
    public function buildDirective($directive)
    {
        // Kludge, although I suppose having a notion of a "root namespace"
        // certainly makes things look nicer when documentation is built.
        // Depends on things being sorted.
        if (!$this->namespace || $this->namespace !== $directive->id->getRootNamespace()) {
            if ($this->namespace) {
                $this->endElement();
            } // namespace
            $this->namespace = $directive->id->getRootNamespace();
            $this->startElement('namespace');
            $this->writeAttribute('id', $this->namespace);
            $this->writeElement('name', $this->namespace);
        }

        $this->startElement('directive');
        $this->writeAttribute('id', $directive->id->toString());

        $this->writeElement('name', $directive->id->getDirective());

        $this->startElement('aliases');
        foreach ($directive->aliases as $alias) {
            $this->writeElement('alias', $alias->toString());
        }
        $this->endElement(); // aliases

        $this->startElement('constraints');
        if ($directive->version) {
            $this->writeElement('version', $directive->version);
        }
        $this->startElement('type');
        if ($directive->typeAllowsNull) {
            $this->writeAttribute('allow-null', 'yes');
        }
        $this->text($directive->type);
        $this->endElement(); // type
        if ($directive->allowed) {
            $this->startElement('allowed');
            foreach ($directive->allowed as $value => $x) {
                $this->writeElement('value', $value);
            }
            $this->endElement(); // allowed
        }
        $this->writeElement('default', $this->export($directive->default));
        $this->writeAttribute('xml:space', 'preserve');
        if ($directive->external) {
            $this->startElement('external');
            foreach ($directive->external as $project) {
                $this->writeElement('project', $project);
            }
            $this->endElement();
        }
        $this->endElement(); // constraints

        if ($directive->deprecatedVersion) {
            $this->startElement('deprecated');
            $this->writeElement('version', $directive->deprecatedVersion);
            $this->writeElement('use', $directive->deprecatedUse->toString());
            $this->endElement(); // deprecated
        }

        $this->startElement('description');
        $this->writeHTMLDiv($directive->description);
        $this->endElement(); // description

        $this->endElement(); // directive
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Converts HTMLPurifier_ConfigSchema_Interchange to our runtime
 * representation used to perform checks on user configuration.
 */
class HTMLPurifier_ConfigSchema_Builder_ConfigSchema
{

    /**
     * @param HTMLPurifier_ConfigSchema_Interchange $interchange
     * @return HTMLPurifier_ConfigSchema
     */
    public function build($interchange)
    {
        $schema = new HTMLPurifier_ConfigSchema();
        foreach ($interchange->directives as $d) {
            $schema->add(
                $d->id->key,
                $d->default,
                $d->type,
                $d->typeAllowsNull
            );
            if ($d->allowed !== null) {
                $schema->addAllowedValues(
                    $d->id->key,
                    $d->allowed
                );
            }
            foreach ($d->aliases as $alias) {
                $schema->addAlias(
                    $alias->key,
                    $d->id->key
                );
            }
            if ($d->valueAliases !== null) {
                $schema->addValueAliases(
                    $d->id->key,
                    $d->valueAliases
                );
            }
        }
        $schema->postProcess();
        return $schema;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Exceptions related to configuration schema
 */
class HTMLPurifier_ConfigSchema_Exception extends HTMLPurifier_Exception
{

}

// vim: et sw=4 sts=4
<?php

/**
 * Generic schema interchange format that can be converted to a runtime
 * representation (HTMLPurifier_ConfigSchema) or HTML documentation. Members
 * are completely validated.
 */
class HTMLPurifier_ConfigSchema_Interchange
{

    /**
     * Name of the application this schema is describing.
     * @type string
     */
    public $name;

    /**
     * Array of Directive ID => array(directive info)
     * @type HTMLPurifier_ConfigSchema_Interchange_Directive[]
     */
    public $directives = array();

    /**
     * Adds a directive array to $directives
     * @param HTMLPurifier_ConfigSchema_Interchange_Directive $directive
     * @throws HTMLPurifier_ConfigSchema_Exception
     */
    public function addDirective($directive)
    {
        if (isset($this->directives[$i = $directive->id->toString()])) {
            throw new HTMLPurifier_ConfigSchema_Exception("Cannot redefine directive '$i'");
        }
        $this->directives[$i] = $directive;
    }

    /**
     * Convenience function to perform standard validation. Throws exception
     * on failed validation.
     */
    public function validate()
    {
        $validator = new HTMLPurifier_ConfigSchema_Validator();
        return $validator->validate($this);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Configuration definition, defines directives and their defaults.
 */
class HTMLPurifier_ConfigSchema
{
    /**
     * Defaults of the directives and namespaces.
     * @type array
     * @note This shares the exact same structure as HTMLPurifier_Config::$conf
     */
    public $defaults = array();

    /**
     * The default property list. Do not edit this property list.
     * @type array
     */
    public $defaultPlist;

    /**
     * Definition of the directives.
     * The structure of this is:
     *
     *  array(
     *      'Namespace' => array(
     *          'Directive' => new stdClass(),
     *      )
     *  )
     *
     * The stdClass may have the following properties:
     *
     *  - If isAlias isn't set:
     *      - type: Integer type of directive, see HTMLPurifier_VarParser for definitions
     *      - allow_null: If set, this directive allows null values
     *      - aliases: If set, an associative array of value aliases to real values
     *      - allowed: If set, a lookup array of allowed (string) values
     *  - If isAlias is set:
     *      - namespace: Namespace this directive aliases to
     *      - name: Directive name this directive aliases to
     *
     * In certain degenerate cases, stdClass will actually be an integer. In
     * that case, the value is equivalent to an stdClass with the type
     * property set to the integer. If the integer is negative, type is
     * equal to the absolute value of integer, and allow_null is true.
     *
     * This class is friendly with HTMLPurifier_Config. If you need introspection
     * about the schema, you're better of using the ConfigSchema_Interchange,
     * which uses more memory but has much richer information.
     * @type array
     */
    public $info = array();

    /**
     * Application-wide singleton
     * @type HTMLPurifier_ConfigSchema
     */
    protected static $singleton;

    public function __construct()
    {
        $this->defaultPlist = new HTMLPurifier_PropertyList();
    }

    /**
     * Unserializes the default ConfigSchema.
     * @return HTMLPurifier_ConfigSchema
     */
    public static function makeFromSerial()
    {
        $contents = file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema.ser');
        $r = unserialize($contents);
        if (!$r) {
            $hash = sha1($contents);
            trigger_error("Unserialization of configuration schema failed, sha1 of file was $hash", E_USER_ERROR);
        }
        return $r;
    }

    /**
     * Retrieves an instance of the application-wide configuration definition.
     * @param HTMLPurifier_ConfigSchema $prototype
     * @return HTMLPurifier_ConfigSchema
     */
    public static function instance($prototype = null)
    {
        if ($prototype !== null) {
            HTMLPurifier_ConfigSchema::$singleton = $prototype;
        } elseif (HTMLPurifier_ConfigSchema::$singleton === null || $prototype === true) {
            HTMLPurifier_ConfigSchema::$singleton = HTMLPurifier_ConfigSchema::makeFromSerial();
        }
        return HTMLPurifier_ConfigSchema::$singleton;
    }

    /**
     * Defines a directive for configuration
     * @warning Will fail of directive's namespace is defined.
     * @warning This method's signature is slightly different from the legacy
     *          define() static method! Beware!
     * @param string $key Name of directive
     * @param mixed $default Default value of directive
     * @param string $type Allowed type of the directive. See
     *      HTMLPurifier_DirectiveDef::$type for allowed values
     * @param bool $allow_null Whether or not to allow null values
     */
    public function add($key, $default, $type, $allow_null)
    {
        $obj = new stdClass();
        $obj->type = is_int($type) ? $type : HTMLPurifier_VarParser::$types[$type];
        if ($allow_null) {
            $obj->allow_null = true;
        }
        $this->info[$key] = $obj;
        $this->defaults[$key] = $default;
        $this->defaultPlist->set($key, $default);
    }

    /**
     * Defines a directive value alias.
     *
     * Directive value aliases are convenient for developers because it lets
     * them set a directive to several values and get the same result.
     * @param string $key Name of Directive
     * @param array $aliases Hash of aliased values to the real alias
     */
    public function addValueAliases($key, $aliases)
    {
        if (!isset($this->info[$key]->aliases)) {
            $this->info[$key]->aliases = array();
        }
        foreach ($aliases as $alias => $real) {
            $this->info[$key]->aliases[$alias] = $real;
        }
    }

    /**
     * Defines a set of allowed values for a directive.
     * @warning This is slightly different from the corresponding static
     *          method definition.
     * @param string $key Name of directive
     * @param array $allowed Lookup array of allowed values
     */
    public function addAllowedValues($key, $allowed)
    {
        $this->info[$key]->allowed = $allowed;
    }

    /**
     * Defines a directive alias for backwards compatibility
     * @param string $key Directive that will be aliased
     * @param string $new_key Directive that the alias will be to
     */
    public function addAlias($key, $new_key)
    {
        $obj = new stdClass;
        $obj->key = $new_key;
        $obj->isAlias = true;
        $this->info[$key] = $obj;
    }

    /**
     * Replaces any stdClass that only has the type property with type integer.
     */
    public function postProcess()
    {
        foreach ($this->info as $key => $v) {
            if (count((array) $v) == 1) {
                $this->info[$key] = $v->type;
            } elseif (count((array) $v) == 2 && isset($v->allow_null)) {
                $this->info[$key] = -$v->type;
            }
        }
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Abstract base node class that all others inherit from.
 *
 * Why do we not use the DOM extension?  (1) It is not always available,
 * (2) it has funny constraints on the data it can represent,
 * whereas we want a maximally flexible representation, and (3) its
 * interface is a bit cumbersome.
 */
abstract class HTMLPurifier_Node
{
    /**
     * Line number of the start token in the source document
     * @type int
     */
    public $line;

    /**
     * Column number of the start token in the source document. Null if unknown.
     * @type int
     */
    public $col;

    /**
     * Lookup array of processing that this token is exempt from.
     * Currently, valid values are "ValidateAttributes".
     * @type array
     */
    public $armor = array();

    /**
     * When true, this node should be ignored as non-existent.
     *
     * Who is responsible for ignoring dead nodes?  FixNesting is
     * responsible for removing them before passing on to child
     * validators.
     */
    public $dead = false;

    /**
     * Returns a pair of start and end tokens, where the end token
     * is null if it is not necessary. Does not include children.
     * @type array
     */
    abstract public function toTokenPair();
}

// vim: et sw=4 sts=4
<?php

/**
 * Responsible for creating definition caches.
 */
class HTMLPurifier_DefinitionCacheFactory
{
    /**
     * @type array
     */
    protected $caches = array('Serializer' => array());

    /**
     * @type array
     */
    protected $implementations = array();

    /**
     * @type HTMLPurifier_DefinitionCache_Decorator[]
     */
    protected $decorators = array();

    /**
     * Initialize default decorators
     */
    public function setup()
    {
        $this->addDecorator('Cleanup');
    }

    /**
     * Retrieves an instance of global definition cache factory.
     * @param HTMLPurifier_DefinitionCacheFactory $prototype
     * @return HTMLPurifier_DefinitionCacheFactory
     */
    public static function instance($prototype = null)
    {
        static $instance;
        if ($prototype !== null) {
            $instance = $prototype;
        } elseif ($instance === null || $prototype === true) {
            $instance = new HTMLPurifier_DefinitionCacheFactory();
            $instance->setup();
        }
        return $instance;
    }

    /**
     * Registers a new definition cache object
     * @param string $short Short name of cache object, for reference
     * @param string $long Full class name of cache object, for construction
     */
    public function register($short, $long)
    {
        $this->implementations[$short] = $long;
    }

    /**
     * Factory method that creates a cache object based on configuration
     * @param string $type Name of definitions handled by cache
     * @param HTMLPurifier_Config $config Config instance
     * @return mixed
     */
    public function create($type, $config)
    {
        $method = $config->get('Cache.DefinitionImpl');
        if ($method === null) {
            return new HTMLPurifier_DefinitionCache_Null($type);
        }
        if (!empty($this->caches[$method][$type])) {
            return $this->caches[$method][$type];
        }
        if (isset($this->implementations[$method]) &&
            class_exists($class = $this->implementations[$method], false)) {
            $cache = new $class($type);
        } else {
            if ($method != 'Serializer') {
                trigger_error("Unrecognized DefinitionCache $method, using Serializer instead", E_USER_WARNING);
            }
            $cache = new HTMLPurifier_DefinitionCache_Serializer($type);
        }
        foreach ($this->decorators as $decorator) {
            $new_cache = $decorator->decorate($cache);
            // prevent infinite recursion in PHP 4
            unset($cache);
            $cache = $new_cache;
        }
        $this->caches[$method][$type] = $cache;
        return $this->caches[$method][$type];
    }

    /**
     * Registers a decorator to add to all new cache objects
     * @param HTMLPurifier_DefinitionCache_Decorator|string $decorator An instance or the name of a decorator
     */
    public function addDecorator($decorator)
    {
        if (is_string($decorator)) {
            $class = "HTMLPurifier_DefinitionCache_Decorator_$decorator";
            $decorator = new $class;
        }
        $this->decorators[$decorator->name] = $decorator;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Registry object that contains information about the current context.
 * @warning Is a bit buggy when variables are set to null: it thinks
 *          they don't exist! So use false instead, please.
 * @note Since the variables Context deals with may not be objects,
 *       references are very important here! Do not remove!
 */
class HTMLPurifier_Context
{

    /**
     * Private array that stores the references.
     * @type array
     */
    private $_storage = array();

    /**
     * Registers a variable into the context.
     * @param string $name String name
     * @param mixed $ref Reference to variable to be registered
     */
    public function register($name, &$ref)
    {
        if (array_key_exists($name, $this->_storage)) {
            trigger_error(
                "Name $name produces collision, cannot re-register",
                E_USER_ERROR
            );
            return;
        }
        $this->_storage[$name] =& $ref;
    }

    /**
     * Retrieves a variable reference from the context.
     * @param string $name String name
     * @param bool $ignore_error Boolean whether or not to ignore error
     * @return mixed
     */
    public function &get($name, $ignore_error = false)
    {
        if (!array_key_exists($name, $this->_storage)) {
            if (!$ignore_error) {
                trigger_error(
                    "Attempted to retrieve non-existent variable $name",
                    E_USER_ERROR
                );
            }
            $var = null; // so we can return by reference
            return $var;
        }
        return $this->_storage[$name];
    }

    /**
     * Destroys a variable in the context.
     * @param string $name String name
     */
    public function destroy($name)
    {
        if (!array_key_exists($name, $this->_storage)) {
            trigger_error(
                "Attempted to destroy non-existent variable $name",
                E_USER_ERROR
            );
            return;
        }
        unset($this->_storage[$name]);
    }

    /**
     * Checks whether or not the variable exists.
     * @param string $name String name
     * @return bool
     */
    public function exists($name)
    {
        return array_key_exists($name, $this->_storage);
    }

    /**
     * Loads a series of variables from an associative array
     * @param array $context_array Assoc array of variables to load
     */
    public function loadArray($context_array)
    {
        foreach ($context_array as $key => $discard) {
            $this->register($key, $context_array[$key]);
        }
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Error collection class that enables HTML Purifier to report HTML
 * problems back to the user
 */
class HTMLPurifier_ErrorCollector
{

    /**
     * Identifiers for the returned error array. These are purposely numeric
     * so list() can be used.
     */
    const LINENO   = 0;
    const SEVERITY = 1;
    const MESSAGE  = 2;
    const CHILDREN = 3;

    /**
     * @type array
     */
    protected $errors;

    /**
     * @type array
     */
    protected $_current;

    /**
     * @type array
     */
    protected $_stacks = array(array());

    /**
     * @type HTMLPurifier_Language
     */
    protected $locale;

    /**
     * @type HTMLPurifier_Generator
     */
    protected $generator;

    /**
     * @type HTMLPurifier_Context
     */
    protected $context;

    /**
     * @type array
     */
    protected $lines = array();

    /**
     * @param HTMLPurifier_Context $context
     */
    public function __construct($context)
    {
        $this->locale    =& $context->get('Locale');
        $this->context   = $context;
        $this->_current  =& $this->_stacks[0];
        $this->errors    =& $this->_stacks[0];
    }

    /**
     * Sends an error message to the collector for later use
     * @param int $severity Error severity, PHP error style (don't use E_USER_)
     * @param string $msg Error message text
     */
    public function send($severity, $msg)
    {
        $args = array();
        if (func_num_args() > 2) {
            $args = func_get_args();
            array_shift($args);
            unset($args[0]);
        }

        $token = $this->context->get('CurrentToken', true);
        $line  = $token ? $token->line : $this->context->get('CurrentLine', true);
        $col   = $token ? $token->col  : $this->context->get('CurrentCol', true);
        $attr  = $this->context->get('CurrentAttr', true);

        // perform special substitutions, also add custom parameters
        $subst = array();
        if (!is_null($token)) {
            $args['CurrentToken'] = $token;
        }
        if (!is_null($attr)) {
            $subst['$CurrentAttr.Name'] = $attr;
            if (isset($token->attr[$attr])) {
                $subst['$CurrentAttr.Value'] = $token->attr[$attr];
            }
        }

        if (empty($args)) {
            $msg = $this->locale->getMessage($msg);
        } else {
            $msg = $this->locale->formatMessage($msg, $args);
        }

        if (!empty($subst)) {
            $msg = strtr($msg, $subst);
        }

        // (numerically indexed)
        $error = array(
            self::LINENO   => $line,
            self::SEVERITY => $severity,
            self::MESSAGE  => $msg,
            self::CHILDREN => array()
        );
        $this->_current[] = $error;

        // NEW CODE BELOW ...
        // Top-level errors are either:
        //  TOKEN type, if $value is set appropriately, or
        //  "syntax" type, if $value is null
        $new_struct = new HTMLPurifier_ErrorStruct();
        $new_struct->type = HTMLPurifier_ErrorStruct::TOKEN;
        if ($token) {
            $new_struct->value = clone $token;
        }
        if (is_int($line) && is_int($col)) {
            if (isset($this->lines[$line][$col])) {
                $struct = $this->lines[$line][$col];
            } else {
                $struct = $this->lines[$line][$col] = $new_struct;
            }
            // These ksorts may present a performance problem
            ksort($this->lines[$line], SORT_NUMERIC);
        } else {
            if (isset($this->lines[-1])) {
                $struct = $this->lines[-1];
            } else {
                $struct = $this->lines[-1] = $new_struct;
            }
        }
        ksort($this->lines, SORT_NUMERIC);

        // Now, check if we need to operate on a lower structure
        if (!empty($attr)) {
            $struct = $struct->getChild(HTMLPurifier_ErrorStruct::ATTR, $attr);
            if (!$struct->value) {
                $struct->value = array($attr, 'PUT VALUE HERE');
            }
        }
        if (!empty($cssprop)) {
            $struct = $struct->getChild(HTMLPurifier_ErrorStruct::CSSPROP, $cssprop);
            if (!$struct->value) {
                // if we tokenize CSS this might be a little more difficult to do
                $struct->value = array($cssprop, 'PUT VALUE HERE');
            }
        }

        // Ok, structs are all setup, now time to register the error
        $struct->addError($severity, $msg);
    }

    /**
     * Retrieves raw error data for custom formatter to use
     */
    public function getRaw()
    {
        return $this->errors;
    }

    /**
     * Default HTML formatting implementation for error messages
     * @param HTMLPurifier_Config $config Configuration, vital for HTML output nature
     * @param array $errors Errors array to display; used for recursion.
     * @return string
     */
    public function getHTMLFormatted($config, $errors = null)
    {
        $ret = array();

        $this->generator = new HTMLPurifier_Generator($config, $this->context);
        if ($errors === null) {
            $errors = $this->errors;
        }

        // 'At line' message needs to be removed

        // generation code for new structure goes here. It needs to be recursive.
        foreach ($this->lines as $line => $col_array) {
            if ($line == -1) {
                continue;
            }
            foreach ($col_array as $col => $struct) {
                $this->_renderStruct($ret, $struct, $line, $col);
            }
        }
        if (isset($this->lines[-1])) {
            $this->_renderStruct($ret, $this->lines[-1]);
        }

        if (empty($errors)) {
            return '<p>' . $this->locale->getMessage('ErrorCollector: No errors') . '</p>';
        } else {
            return '<ul><li>' . implode('</li><li>', $ret) . '</li></ul>';
        }

    }

    private function _renderStruct(&$ret, $struct, $line = null, $col = null)
    {
        $stack = array($struct);
        $context_stack = array(array());
        while ($current = array_pop($stack)) {
            $context = array_pop($context_stack);
            foreach ($current->errors as $error) {
                list($severity, $msg) = $error;
                $string = '';
                $string .= '<div>';
                // W3C uses an icon to indicate the severity of the error.
                $error = $this->locale->getErrorName($severity);
                $string .= "<span class=\"error e$severity\"><strong>$error</strong></span> ";
                if (!is_null($line) && !is_null($col)) {
                    $string .= "<em class=\"location\">Line $line, Column $col: </em> ";
                } else {
                    $string .= '<em class="location">End of Document: </em> ';
                }
                $string .= '<strong class="description">' . $this->generator->escape($msg) . '</strong> ';
                $string .= '</div>';
                // Here, have a marker for the character on the column appropriate.
                // Be sure to clip extremely long lines.
                //$string .= '<pre>';
                //$string .= '';
                //$string .= '</pre>';
                $ret[] = $string;
            }
            foreach ($current->children as $array) {
                $context[] = $current;
                $stack = array_merge($stack, array_reverse($array, true));
                for ($i = count($array); $i > 0; $i--) {
                    $context_stack[] = $context;
                }
            }
        }
    }
}

// vim: et sw=4 sts=4
<?php

class HTMLPurifier_DoctypeRegistry
{

    /**
     * Hash of doctype names to doctype objects.
     * @type array
     */
    protected $doctypes;

    /**
     * Lookup table of aliases to real doctype names.
     * @type array
     */
    protected $aliases;

    /**
     * Registers a doctype to the registry
     * @note Accepts a fully-formed doctype object, or the
     *       parameters for constructing a doctype object
     * @param string $doctype Name of doctype or literal doctype object
     * @param bool $xml
     * @param array $modules Modules doctype will load
     * @param array $tidy_modules Modules doctype will load for certain modes
     * @param array $aliases Alias names for doctype
     * @param string $dtd_public
     * @param string $dtd_system
     * @return HTMLPurifier_Doctype Editable registered doctype
     */
    public function register(
        $doctype,
        $xml = true,
        $modules = array(),
        $tidy_modules = array(),
        $aliases = array(),
        $dtd_public = null,
        $dtd_system = null
    ) {
        if (!is_array($modules)) {
            $modules = array($modules);
        }
        if (!is_array($tidy_modules)) {
            $tidy_modules = array($tidy_modules);
        }
        if (!is_array($aliases)) {
            $aliases = array($aliases);
        }
        if (!is_object($doctype)) {
            $doctype = new HTMLPurifier_Doctype(
                $doctype,
                $xml,
                $modules,
                $tidy_modules,
                $aliases,
                $dtd_public,
                $dtd_system
            );
        }
        $this->doctypes[$doctype->name] = $doctype;
        $name = $doctype->name;
        // hookup aliases
        foreach ($doctype->aliases as $alias) {
            if (isset($this->doctypes[$alias])) {
                continue;
            }
            $this->aliases[$alias] = $name;
        }
        // remove old aliases
        if (isset($this->aliases[$name])) {
            unset($this->aliases[$name]);
        }
        return $doctype;
    }

    /**
     * Retrieves reference to a doctype of a certain name
     * @note This function resolves aliases
     * @note When possible, use the more fully-featured make()
     * @param string $doctype Name of doctype
     * @return HTMLPurifier_Doctype Editable doctype object
     */
    public function get($doctype)
    {
        if (isset($this->aliases[$doctype])) {
            $doctype = $this->aliases[$doctype];
        }
        if (!isset($this->doctypes[$doctype])) {
            trigger_error('Doctype ' . htmlspecialchars($doctype) . ' does not exist', E_USER_ERROR);
            $anon = new HTMLPurifier_Doctype($doctype);
            return $anon;
        }
        return $this->doctypes[$doctype];
    }

    /**
     * Creates a doctype based on a configuration object,
     * will perform initialization on the doctype
     * @note Use this function to get a copy of doctype that config
     *       can hold on to (this is necessary in order to tell
     *       Generator whether or not the current document is XML
     *       based or not).
     * @param HTMLPurifier_Config $config
     * @return HTMLPurifier_Doctype
     */
    public function make($config)
    {
        return clone $this->get($this->getDoctypeFromConfig($config));
    }

    /**
     * Retrieves the doctype from the configuration object
     * @param HTMLPurifier_Config $config
     * @return string
     */
    public function getDoctypeFromConfig($config)
    {
        // recommended test
        $doctype = $config->get('HTML.Doctype');
        if (!empty($doctype)) {
            return $doctype;
        }
        $doctype = $config->get('HTML.CustomDoctype');
        if (!empty($doctype)) {
            return $doctype;
        }
        // backwards-compatibility
        if ($config->get('HTML.XHTML')) {
            $doctype = 'XHTML 1.0';
        } else {
            $doctype = 'HTML 4.01';
        }
        if ($config->get('HTML.Strict')) {
            $doctype .= ' Strict';
        } else {
            $doctype .= ' Transitional';
        }
        return $doctype;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Structure that stores an HTML element definition. Used by
 * HTMLPurifier_HTMLDefinition and HTMLPurifier_HTMLModule.
 * @note This class is inspected by HTMLPurifier_Printer_HTMLDefinition.
 *       Please update that class too.
 * @warning If you add new properties to this class, you MUST update
 *          the mergeIn() method.
 */
class HTMLPurifier_ElementDef
{
    /**
     * Does the definition work by itself, or is it created solely
     * for the purpose of merging into another definition?
     * @type bool
     */
    public $standalone = true;

    /**
     * Associative array of attribute name to HTMLPurifier_AttrDef.
     * @type array
     * @note Before being processed by HTMLPurifier_AttrCollections
     *       when modules are finalized during
     *       HTMLPurifier_HTMLDefinition->setup(), this array may also
     *       contain an array at index 0 that indicates which attribute
     *       collections to load into the full array. It may also
     *       contain string indentifiers in lieu of HTMLPurifier_AttrDef,
     *       see HTMLPurifier_AttrTypes on how they are expanded during
     *       HTMLPurifier_HTMLDefinition->setup() processing.
     */
    public $attr = array();

    // XXX: Design note: currently, it's not possible to override
    // previously defined AttrTransforms without messing around with
    // the final generated config. This is by design; a previous version
    // used an associated list of attr_transform, but it was extremely
    // easy to accidentally override other attribute transforms by
    // forgetting to specify an index (and just using 0.)  While we
    // could check this by checking the index number and complaining,
    // there is a second problem which is that it is not at all easy to
    // tell when something is getting overridden. Combine this with a
    // codebase where this isn't really being used, and it's perfect for
    // nuking.

    /**
     * List of tags HTMLPurifier_AttrTransform to be done before validation.
     * @type array
     */
    public $attr_transform_pre = array();

    /**
     * List of tags HTMLPurifier_AttrTransform to be done after validation.
     * @type array
     */
    public $attr_transform_post = array();

    /**
     * HTMLPurifier_ChildDef of this tag.
     * @type HTMLPurifier_ChildDef
     */
    public $child;

    /**
     * Abstract string representation of internal ChildDef rules.
     * @see HTMLPurifier_ContentSets for how this is parsed and then transformed
     * into an HTMLPurifier_ChildDef.
     * @warning This is a temporary variable that is not available after
     *      being processed by HTMLDefinition
     * @type string
     */
    public $content_model;

    /**
     * Value of $child->type, used to determine which ChildDef to use,
     * used in combination with $content_model.
     * @warning This must be lowercase
     * @warning This is a temporary variable that is not available after
     *      being processed by HTMLDefinition
     * @type string
     */
    public $content_model_type;

    /**
     * Does the element have a content model (#PCDATA | Inline)*? This
     * is important for chameleon ins and del processing in
     * HTMLPurifier_ChildDef_Chameleon. Dynamically set: modules don't
     * have to worry about this one.
     * @type bool
     */
    public $descendants_are_inline = false;

    /**
     * List of the names of required attributes this element has.
     * Dynamically populated by HTMLPurifier_HTMLDefinition::getElement()
     * @type array
     */
    public $required_attr = array();

    /**
     * Lookup table of tags excluded from all descendants of this tag.
     * @type array
     * @note SGML permits exclusions for all descendants, but this is
     *       not possible with DTDs or XML Schemas. W3C has elected to
     *       use complicated compositions of content_models to simulate
     *       exclusion for children, but we go the simpler, SGML-style
     *       route of flat-out exclusions, which correctly apply to
     *       all descendants and not just children. Note that the XHTML
     *       Modularization Abstract Modules are blithely unaware of such
     *       distinctions.
     */
    public $excludes = array();

    /**
     * This tag is explicitly auto-closed by the following tags.
     * @type array
     */
    public $autoclose = array();

    /**
     * If a foreign element is found in this element, test if it is
     * allowed by this sub-element; if it is, instead of closing the
     * current element, place it inside this element.
     * @type string
     */
    public $wrap;

    /**
     * Whether or not this is a formatting element affected by the
     * "Active Formatting Elements" algorithm.
     * @type bool
     */
    public $formatting;

    /**
     * Low-level factory constructor for creating new standalone element defs
     */
    public static function create($content_model, $content_model_type, $attr)
    {
        $def = new HTMLPurifier_ElementDef();
        $def->content_model = $content_model;
        $def->content_model_type = $content_model_type;
        $def->attr = $attr;
        return $def;
    }

    /**
     * Merges the values of another element definition into this one.
     * Values from the new element def take precedence if a value is
     * not mergeable.
     * @param HTMLPurifier_ElementDef $def
     */
    public function mergeIn($def)
    {
        // later keys takes precedence
        foreach ($def->attr as $k => $v) {
            if ($k === 0) {
                // merge in the includes
                // sorry, no way to override an include
                foreach ($v as $v2) {
                    $this->attr[0][] = $v2;
                }
                continue;
            }
            if ($v === false) {
                if (isset($this->attr[$k])) {
                    unset($this->attr[$k]);
                }
                continue;
            }
            $this->attr[$k] = $v;
        }
        $this->_mergeAssocArray($this->excludes, $def->excludes);
        $this->attr_transform_pre = array_merge($this->attr_transform_pre, $def->attr_transform_pre);
        $this->attr_transform_post = array_merge($this->attr_transform_post, $def->attr_transform_post);

        if (!empty($def->content_model)) {
            $this->content_model =
                str_replace("#SUPER", $this->content_model, $def->content_model);
            $this->child = false;
        }
        if (!empty($def->content_model_type)) {
            $this->content_model_type = $def->content_model_type;
            $this->child = false;
        }
        if (!is_null($def->child)) {
            $this->child = $def->child;
        }
        if (!is_null($def->formatting)) {
            $this->formatting = $def->formatting;
        }
        if ($def->descendants_are_inline) {
            $this->descendants_are_inline = $def->descendants_are_inline;
        }
    }

    /**
     * Merges one array into another, removes values which equal false
     * @param $a1 Array by reference that is merged into
     * @param $a2 Array that merges into $a1
     */
    private function _mergeAssocArray(&$a1, $a2)
    {
        foreach ($a2 as $k => $v) {
            if ($v === false) {
                if (isset($a1[$k])) {
                    unset($a1[$k]);
                }
                continue;
            }
            $a1[$k] = $v;
        }
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates news (Usenet) as defined by generic RFC 1738
 */
class HTMLPurifier_URIScheme_news extends HTMLPurifier_URIScheme
{
    /**
     * @type bool
     */
    public $browsable = false;

    /**
     * @type bool
     */
    public $may_omit_host = true;

    /**
     * @param HTMLPurifier_URI $uri
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool
     */
    public function doValidate(&$uri, $config, $context)
    {
        $uri->userinfo = null;
        $uri->host = null;
        $uri->port = null;
        $uri->query = null;
        // typecode check needed on path
        return true;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates tel (for phone numbers).
 *
 * The relevant specifications for this protocol are RFC 3966 and RFC 5341,
 * but this class takes a much simpler approach: we normalize phone
 * numbers so that they only include (possibly) a leading plus,
 * and then any number of digits and x'es.
 */

class HTMLPurifier_URIScheme_tel extends HTMLPurifier_URIScheme
{
    /**
     * @type bool
     */
    public $browsable = false;

    /**
     * @type bool
     */
    public $may_omit_host = true;

    /**
     * @param HTMLPurifier_URI $uri
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool
     */
    public function doValidate(&$uri, $config, $context)
    {
        $uri->userinfo = null;
        $uri->host     = null;
        $uri->port     = null;

        // Delete all non-numeric characters, non-x characters
        // from phone number, EXCEPT for a leading plus sign.
        $uri->path = preg_replace('/(?!^\+)[^\dx]/', '',
                     // Normalize e(x)tension to lower-case
                     str_replace('X', 'x', $uri->path));

        return true;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates file as defined by RFC 1630 and RFC 1738.
 */
class HTMLPurifier_URIScheme_file extends HTMLPurifier_URIScheme
{
    /**
     * Generally file:// URLs are not accessible from most
     * machines, so placing them as an img src is incorrect.
     * @type bool
     */
    public $browsable = false;

    /**
     * Basically the *only* URI scheme for which this is true, since
     * accessing files on the local machine is very common.  In fact,
     * browsers on some operating systems don't understand the
     * authority, though I hear it is used on Windows to refer to
     * network shares.
     * @type bool
     */
    public $may_omit_host = true;

    /**
     * @param HTMLPurifier_URI $uri
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool
     */
    public function doValidate(&$uri, $config, $context)
    {
        // Authentication method is not supported
        $uri->userinfo = null;
        // file:// makes no provisions for accessing the resource
        $uri->port = null;
        // While it seems to work on Firefox, the querystring has
        // no possible effect and is thus stripped.
        $uri->query = null;
        return true;
    }
}

// vim: et sw=4 sts=4
<?php

// VERY RELAXED! Shouldn't cause problems, not even Firefox checks if the
// email is valid, but be careful!

/**
 * Validates mailto (for E-mail) according to RFC 2368
 * @todo Validate the email address
 * @todo Filter allowed query parameters
 */

class HTMLPurifier_URIScheme_mailto extends HTMLPurifier_URIScheme
{
    /**
     * @type bool
     */
    public $browsable = false;

    /**
     * @type bool
     */
    public $may_omit_host = true;

    /**
     * @param HTMLPurifier_URI $uri
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool
     */
    public function doValidate(&$uri, $config, $context)
    {
        $uri->userinfo = null;
        $uri->host     = null;
        $uri->port     = null;
        // we need to validate path against RFC 2368's addr-spec
        return true;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates http (HyperText Transfer Protocol) as defined by RFC 2616
 */
class HTMLPurifier_URIScheme_http extends HTMLPurifier_URIScheme
{
    /**
     * @type int
     */
    public $default_port = 80;

    /**
     * @type bool
     */
    public $browsable = true;

    /**
     * @type bool
     */
    public $hierarchical = true;

    /**
     * @param HTMLPurifier_URI $uri
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool
     */
    public function doValidate(&$uri, $config, $context)
    {
        $uri->userinfo = null;
        return true;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates nntp (Network News Transfer Protocol) as defined by generic RFC 1738
 */
class HTMLPurifier_URIScheme_nntp extends HTMLPurifier_URIScheme
{
    /**
     * @type int
     */
    public $default_port = 119;

    /**
     * @type bool
     */
    public $browsable = false;

    /**
     * @param HTMLPurifier_URI $uri
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool
     */
    public function doValidate(&$uri, $config, $context)
    {
        $uri->userinfo = null;
        $uri->query = null;
        return true;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates https (Secure HTTP) according to http scheme.
 */
class HTMLPurifier_URIScheme_https extends HTMLPurifier_URIScheme_http
{
    /**
     * @type int
     */
    public $default_port = 443;
    /**
     * @type bool
     */
    public $secure = true;
}

// vim: et sw=4 sts=4
<?php

/**
 * Implements data: URI for base64 encoded images supported by GD.
 */
class HTMLPurifier_URIScheme_data extends HTMLPurifier_URIScheme
{
    /**
     * @type bool
     */
    public $browsable = true;

    /**
     * @type array
     */
    public $allowed_types = array(
        // you better write validation code for other types if you
        // decide to allow them
        'image/jpeg' => true,
        'image/gif' => true,
        'image/png' => true,
    );
    // this is actually irrelevant since we only write out the path
    // component
    /**
     * @type bool
     */
    public $may_omit_host = true;

    /**
     * @param HTMLPurifier_URI $uri
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool
     */
    public function doValidate(&$uri, $config, $context)
    {
        $result = explode(',', $uri->path, 2);
        $is_base64 = false;
        $charset = null;
        $content_type = null;
        if (count($result) == 2) {
            list($metadata, $data) = $result;
            // do some legwork on the metadata
            $metas = explode(';', $metadata);
            while (!empty($metas)) {
                $cur = array_shift($metas);
                if ($cur == 'base64') {
                    $is_base64 = true;
                    break;
                }
                if (substr($cur, 0, 8) == 'charset=') {
                    // doesn't match if there are arbitrary spaces, but
                    // whatever dude
                    if ($charset !== null) {
                        continue;
                    } // garbage
                    $charset = substr($cur, 8); // not used
                } else {
                    if ($content_type !== null) {
                        continue;
                    } // garbage
                    $content_type = $cur;
                }
            }
        } else {
            $data = $result[0];
        }
        if ($content_type !== null && empty($this->allowed_types[$content_type])) {
            return false;
        }
        if ($charset !== null) {
            // error; we don't allow plaintext stuff
            $charset = null;
        }
        $data = rawurldecode($data);
        if ($is_base64) {
            $raw_data = base64_decode($data);
        } else {
            $raw_data = $data;
        }
        if ( strlen($raw_data) < 12 ) {
            // error; exif_imagetype throws exception with small files,
            // and this likely indicates a corrupt URI/failed parse anyway
            return false;
        }
        // XXX probably want to refactor this into a general mechanism
        // for filtering arbitrary content types
        if (function_exists('sys_get_temp_dir')) {
            $file = tempnam(sys_get_temp_dir(), "");
        } else {
            $file = tempnam("/tmp", "");
        }
        file_put_contents($file, $raw_data);
        if (function_exists('exif_imagetype')) {
            $image_code = exif_imagetype($file);
            unlink($file);
        } elseif (function_exists('getimagesize')) {
            set_error_handler(array($this, 'muteErrorHandler'));
            $info = getimagesize($file);
            restore_error_handler();
            unlink($file);
            if ($info == false) {
                return false;
            }
            $image_code = $info[2];
        } else {
            trigger_error("could not find exif_imagetype or getimagesize functions", E_USER_ERROR);
        }
        $real_content_type = image_type_to_mime_type($image_code);
        if ($real_content_type != $content_type) {
            // we're nice guys; if the content type is something else we
            // support, change it over
            if (empty($this->allowed_types[$real_content_type])) {
                return false;
            }
            $content_type = $real_content_type;
        }
        // ok, it's kosher, rewrite what we need
        $uri->userinfo = null;
        $uri->host = null;
        $uri->port = null;
        $uri->fragment = null;
        $uri->query = null;
        $uri->path = "$content_type;base64," . base64_encode($raw_data);
        return true;
    }

    /**
     * @param int $errno
     * @param string $errstr
     */
    public function muteErrorHandler($errno, $errstr)
    {
    }
}
<?php

/**
 * Validates ftp (File Transfer Protocol) URIs as defined by generic RFC 1738.
 */
class HTMLPurifier_URIScheme_ftp extends HTMLPurifier_URIScheme
{
    /**
     * @type int
     */
    public $default_port = 21;

    /**
     * @type bool
     */
    public $browsable = true; // usually

    /**
     * @type bool
     */
    public $hierarchical = true;

    /**
     * @param HTMLPurifier_URI $uri
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool
     */
    public function doValidate(&$uri, $config, $context)
    {
        $uri->query = null;

        // typecode check
        $semicolon_pos = strrpos($uri->path, ';'); // reverse
        if ($semicolon_pos !== false) {
            $type = substr($uri->path, $semicolon_pos + 1); // no semicolon
            $uri->path = substr($uri->path, 0, $semicolon_pos);
            $type_ret = '';
            if (strpos($type, '=') !== false) {
                // figure out whether or not the declaration is correct
                list($key, $typecode) = explode('=', $type, 2);
                if ($key !== 'type') {
                    // invalid key, tack it back on encoded
                    $uri->path .= '%3B' . $type;
                } elseif ($typecode === 'a' || $typecode === 'i' || $typecode === 'd') {
                    $type_ret = ";type=$typecode";
                }
            } else {
                $uri->path .= '%3B' . $type;
            }
            $uri->path = str_replace(';', '%3B', $uri->path);
            $uri->path .= $type_ret;
        }
        return true;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Represents a pre or post processing filter on HTML Purifier's output
 *
 * Sometimes, a little ad-hoc fixing of HTML has to be done before
 * it gets sent through HTML Purifier: you can use filters to acheive
 * this effect. For instance, YouTube videos can be preserved using
 * this manner. You could have used a decorator for this task, but
 * PHP's support for them is not terribly robust, so we're going
 * to just loop through the filters.
 *
 * Filters should be exited first in, last out. If there are three filters,
 * named 1, 2 and 3, the order of execution should go 1->preFilter,
 * 2->preFilter, 3->preFilter, purify, 3->postFilter, 2->postFilter,
 * 1->postFilter.
 *
 * @note Methods are not declared abstract as it is perfectly legitimate
 *       for an implementation not to want anything to happen on a step
 */

class HTMLPurifier_Filter
{

    /**
     * Name of the filter for identification purposes.
     * @type string
     */
    public $name;

    /**
     * Pre-processor function, handles HTML before HTML Purifier
     * @param string $html
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return string
     */
    public function preFilter($html, $config, $context)
    {
        return $html;
    }

    /**
     * Post-processor function, handles HTML after HTML Purifier
     * @param string $html
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return string
     */
    public function postFilter($html, $config, $context)
    {
        return $html;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates the HTML attribute lang, effectively a language code.
 * @note Built according to RFC 3066, which obsoleted RFC 1766
 */
class HTMLPurifier_AttrDef_Lang extends HTMLPurifier_AttrDef
{

    /**
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($string, $config, $context)
    {
        $string = trim($string);
        if (!$string) {
            return false;
        }

        $subtags = explode('-', $string);
        $num_subtags = count($subtags);

        if ($num_subtags == 0) { // sanity check
            return false;
        }

        // process primary subtag : $subtags[0]
        $length = strlen($subtags[0]);
        switch ($length) {
            case 0:
                return false;
            case 1:
                if (!($subtags[0] == 'x' || $subtags[0] == 'i')) {
                    return false;
                }
                break;
            case 2:
            case 3:
                if (!ctype_alpha($subtags[0])) {
                    return false;
                } elseif (!ctype_lower($subtags[0])) {
                    $subtags[0] = strtolower($subtags[0]);
                }
                break;
            default:
                return false;
        }

        $new_string = $subtags[0];
        if ($num_subtags == 1) {
            return $new_string;
        }

        // process second subtag : $subtags[1]
        $length = strlen($subtags[1]);
        if ($length == 0 || ($length == 1 && $subtags[1] != 'x') || $length > 8 || !ctype_alnum($subtags[1])) {
            return $new_string;
        }
        if (!ctype_lower($subtags[1])) {
            $subtags[1] = strtolower($subtags[1]);
        }

        $new_string .= '-' . $subtags[1];
        if ($num_subtags == 2) {
            return $new_string;
        }

        // process all other subtags, index 2 and up
        for ($i = 2; $i < $num_subtags; $i++) {
            $length = strlen($subtags[$i]);
            if ($length == 0 || $length > 8 || !ctype_alnum($subtags[$i])) {
                return $new_string;
            }
            if (!ctype_lower($subtags[$i])) {
                $subtags[$i] = strtolower($subtags[$i]);
            }
            $new_string .= '-' . $subtags[$i];
        }
        return $new_string;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates an integer.
 * @note While this class was modeled off the CSS definition, no currently
 *       allowed CSS uses this type.  The properties that do are: widows,
 *       orphans, z-index, counter-increment, counter-reset.  Some of the
 *       HTML attributes, however, find use for a non-negative version of this.
 */
class HTMLPurifier_AttrDef_Integer extends HTMLPurifier_AttrDef
{

    /**
     * Whether or not negative values are allowed.
     * @type bool
     */
    protected $negative = true;

    /**
     * Whether or not zero is allowed.
     * @type bool
     */
    protected $zero = true;

    /**
     * Whether or not positive values are allowed.
     * @type bool
     */
    protected $positive = true;

    /**
     * @param $negative Bool indicating whether or not negative values are allowed
     * @param $zero Bool indicating whether or not zero is allowed
     * @param $positive Bool indicating whether or not positive values are allowed
     */
    public function __construct($negative = true, $zero = true, $positive = true)
    {
        $this->negative = $negative;
        $this->zero = $zero;
        $this->positive = $positive;
    }

    /**
     * @param string $integer
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($integer, $config, $context)
    {
        $integer = $this->parseCDATA($integer);
        if ($integer === '') {
            return false;
        }

        // we could possibly simply typecast it to integer, but there are
        // certain fringe cases that must not return an integer.

        // clip leading sign
        if ($this->negative && $integer[0] === '-') {
            $digits = substr($integer, 1);
            if ($digits === '0') {
                $integer = '0';
            } // rm minus sign for zero
        } elseif ($this->positive && $integer[0] === '+') {
            $digits = $integer = substr($integer, 1); // rm unnecessary plus
        } else {
            $digits = $integer;
        }

        // test if it's numeric
        if (!ctype_digit($digits)) {
            return false;
        }

        // perform scope tests
        if (!$this->zero && $integer == 0) {
            return false;
        }
        if (!$this->positive && $integer > 0) {
            return false;
        }
        if (!$this->negative && $integer < 0) {
            return false;
        }

        return $integer;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates the border property as defined by CSS.
 */
class HTMLPurifier_AttrDef_CSS_Border extends HTMLPurifier_AttrDef
{

    /**
     * Local copy of properties this property is shorthand for.
     * @type HTMLPurifier_AttrDef[]
     */
    protected $info = array();

    /**
     * @param HTMLPurifier_Config $config
     */
    public function __construct($config)
    {
        $def = $config->getCSSDefinition();
        $this->info['border-width'] = $def->info['border-width'];
        $this->info['border-style'] = $def->info['border-style'];
        $this->info['border-top-color'] = $def->info['border-top-color'];
    }

    /**
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($string, $config, $context)
    {
        $string = $this->parseCDATA($string);
        $string = $this->mungeRgb($string);
        $bits = explode(' ', $string);
        $done = array(); // segments we've finished
        $ret = ''; // return value
        foreach ($bits as $bit) {
            foreach ($this->info as $propname => $validator) {
                if (isset($done[$propname])) {
                    continue;
                }
                $r = $validator->validate($bit, $config, $context);
                if ($r !== false) {
                    $ret .= $r . ' ';
                    $done[$propname] = true;
                    break;
                }
            }
        }
        return rtrim($ret);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates a number as defined by the CSS spec.
 */
class HTMLPurifier_AttrDef_CSS_Number extends HTMLPurifier_AttrDef
{

    /**
     * Indicates whether or not only positive values are allowed.
     * @type bool
     */
    protected $non_negative = false;

    /**
     * @param bool $non_negative indicates whether negatives are forbidden
     */
    public function __construct($non_negative = false)
    {
        $this->non_negative = $non_negative;
    }

    /**
     * @param string $number
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return string|bool
     * @warning Some contexts do not pass $config, $context. These
     *          variables should not be used without checking HTMLPurifier_Length
     */
    public function validate($number, $config, $context)
    {
        $number = $this->parseCDATA($number);

        if ($number === '') {
            return false;
        }
        if ($number === '0') {
            return '0';
        }

        $sign = '';
        switch ($number[0]) {
            case '-':
                if ($this->non_negative) {
                    return false;
                }
                $sign = '-';
            case '+':
                $number = substr($number, 1);
        }

        if (ctype_digit($number)) {
            $number = ltrim($number, '0');
            return $number ? $sign . $number : '0';
        }

        // Period is the only non-numeric character allowed
        if (strpos($number, '.') === false) {
            return false;
        }

        list($left, $right) = explode('.', $number, 2);

        if ($left === '' && $right === '') {
            return false;
        }
        if ($left !== '' && !ctype_digit($left)) {
            return false;
        }

        $left = ltrim($left, '0');
        $right = rtrim($right, '0');

        if ($right === '') {
            return $left ? $sign . $left : '0';
        } elseif (!ctype_digit($right)) {
            return false;
        }
        return $sign . $left . '.' . $right;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates a Percentage as defined by the CSS spec.
 */
class HTMLPurifier_AttrDef_CSS_Percentage extends HTMLPurifier_AttrDef
{

    /**
     * Instance to defer number validation to.
     * @type HTMLPurifier_AttrDef_CSS_Number
     */
    protected $number_def;

    /**
     * @param bool $non_negative Whether to forbid negative values
     */
    public function __construct($non_negative = false)
    {
        $this->number_def = new HTMLPurifier_AttrDef_CSS_Number($non_negative);
    }

    /**
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($string, $config, $context)
    {
        $string = $this->parseCDATA($string);

        if ($string === '') {
            return false;
        }
        $length = strlen($string);
        if ($length === 1) {
            return false;
        }
        if ($string[$length - 1] !== '%') {
            return false;
        }

        $number = substr($string, 0, $length - 1);
        $number = $this->number_def->validate($number, $config, $context);

        if ($number === false) {
            return false;
        }
        return "$number%";
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates the value for the CSS property text-decoration
 * @note This class could be generalized into a version that acts sort of
 *       like Enum except you can compound the allowed values.
 */
class HTMLPurifier_AttrDef_CSS_TextDecoration extends HTMLPurifier_AttrDef
{

    /**
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($string, $config, $context)
    {
        static $allowed_values = array(
            'line-through' => true,
            'overline' => true,
            'underline' => true,
        );

        $string = strtolower($this->parseCDATA($string));

        if ($string === 'none') {
            return $string;
        }

        $parts = explode(' ', $string);
        $final = '';
        foreach ($parts as $part) {
            if (isset($allowed_values[$part])) {
                $final .= $part . ' ';
            }
        }
        $final = rtrim($final);
        if ($final === '') {
            return false;
        }
        return $final;
    }
}

// vim: et sw=4 sts=4
<?php

/* W3C says:
    [ // adjective and number must be in correct order, even if
      // you could switch them without introducing ambiguity.
      // some browsers support that syntax
        [
            <percentage> | <length> | left | center | right
        ]
        [
            <percentage> | <length> | top | center | bottom
        ]?
    ] |
    [ // this signifies that the vertical and horizontal adjectives
      // can be arbitrarily ordered, however, there can only be two,
      // one of each, or none at all
        [
            left | center | right
        ] ||
        [
            top | center | bottom
        ]
    ]
    top, left = 0%
    center, (none) = 50%
    bottom, right = 100%
*/

/* QuirksMode says:
    keyword + length/percentage must be ordered correctly, as per W3C

    Internet Explorer and Opera, however, support arbitrary ordering. We
    should fix it up.

    Minor issue though, not strictly necessary.
*/

// control freaks may appreciate the ability to convert these to
// percentages or something, but it's not necessary

/**
 * Validates the value of background-position.
 */
class HTMLPurifier_AttrDef_CSS_BackgroundPosition extends HTMLPurifier_AttrDef
{

    /**
     * @type HTMLPurifier_AttrDef_CSS_Length
     */
    protected $length;

    /**
     * @type HTMLPurifier_AttrDef_CSS_Percentage
     */
    protected $percentage;

    public function __construct()
    {
        $this->length = new HTMLPurifier_AttrDef_CSS_Length();
        $this->percentage = new HTMLPurifier_AttrDef_CSS_Percentage();
    }

    /**
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($string, $config, $context)
    {
        $string = $this->parseCDATA($string);
        $bits = explode(' ', $string);

        $keywords = array();
        $keywords['h'] = false; // left, right
        $keywords['v'] = false; // top, bottom
        $keywords['ch'] = false; // center (first word)
        $keywords['cv'] = false; // center (second word)
        $measures = array();

        $i = 0;

        $lookup = array(
            'top' => 'v',
            'bottom' => 'v',
            'left' => 'h',
            'right' => 'h',
            'center' => 'c'
        );

        foreach ($bits as $bit) {
            if ($bit === '') {
                continue;
            }

            // test for keyword
            $lbit = ctype_lower($bit) ? $bit : strtolower($bit);
            if (isset($lookup[$lbit])) {
                $status = $lookup[$lbit];
                if ($status == 'c') {
                    if ($i == 0) {
                        $status = 'ch';
                    } else {
                        $status = 'cv';
                    }
                }
                $keywords[$status] = $lbit;
                $i++;
            }

            // test for length
            $r = $this->length->validate($bit, $config, $context);
            if ($r !== false) {
                $measures[] = $r;
                $i++;
            }

            // test for percentage
            $r = $this->percentage->validate($bit, $config, $context);
            if ($r !== false) {
                $measures[] = $r;
                $i++;
            }
        }

        if (!$i) {
            return false;
        } // no valid values were caught

        $ret = array();

        // first keyword
        if ($keywords['h']) {
            $ret[] = $keywords['h'];
        } elseif ($keywords['ch']) {
            $ret[] = $keywords['ch'];
            $keywords['cv'] = false; // prevent re-use: center = center center
        } elseif (count($measures)) {
            $ret[] = array_shift($measures);
        }

        if ($keywords['v']) {
            $ret[] = $keywords['v'];
        } elseif ($keywords['cv']) {
            $ret[] = $keywords['cv'];
        } elseif (count($measures)) {
            $ret[] = array_shift($measures);
        }

        if (empty($ret)) {
            return false;
        }
        return implode(' ', $ret);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates shorthand CSS property list-style.
 * @warning Does not support url tokens that have internal spaces.
 */
class HTMLPurifier_AttrDef_CSS_ListStyle extends HTMLPurifier_AttrDef
{

    /**
     * Local copy of validators.
     * @type HTMLPurifier_AttrDef[]
     * @note See HTMLPurifier_AttrDef_CSS_Font::$info for a similar impl.
     */
    protected $info;

    /**
     * @param HTMLPurifier_Config $config
     */
    public function __construct($config)
    {
        $def = $config->getCSSDefinition();
        $this->info['list-style-type'] = $def->info['list-style-type'];
        $this->info['list-style-position'] = $def->info['list-style-position'];
        $this->info['list-style-image'] = $def->info['list-style-image'];
    }

    /**
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($string, $config, $context)
    {
        // regular pre-processing
        $string = $this->parseCDATA($string);
        if ($string === '') {
            return false;
        }

        // assumes URI doesn't have spaces in it
        $bits = explode(' ', strtolower($string)); // bits to process

        $caught = array();
        $caught['type'] = false;
        $caught['position'] = false;
        $caught['image'] = false;

        $i = 0; // number of catches
        $none = false;

        foreach ($bits as $bit) {
            if ($i >= 3) {
                return;
            } // optimization bit
            if ($bit === '') {
                continue;
            }
            foreach ($caught as $key => $status) {
                if ($status !== false) {
                    continue;
                }
                $r = $this->info['list-style-' . $key]->validate($bit, $config, $context);
                if ($r === false) {
                    continue;
                }
                if ($r === 'none') {
                    if ($none) {
                        continue;
                    } else {
                        $none = true;
                    }
                    if ($key == 'image') {
                        continue;
                    }
                }
                $caught[$key] = $r;
                $i++;
                break;
            }
        }

        if (!$i) {
            return false;
        }

        $ret = array();

        // construct type
        if ($caught['type']) {
            $ret[] = $caught['type'];
        }

        // construct image
        if ($caught['image']) {
            $ret[] = $caught['image'];
        }

        // construct position
        if ($caught['position']) {
            $ret[] = $caught['position'];
        }

        if (empty($ret)) {
            return false;
        }
        return implode(' ', $ret);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Allows multiple validators to attempt to validate attribute.
 *
 * Composite is just what it sounds like: a composite of many validators.
 * This means that multiple HTMLPurifier_AttrDef objects will have a whack
 * at the string.  If one of them passes, that's what is returned.  This is
 * especially useful for CSS values, which often are a choice between
 * an enumerated set of predefined values or a flexible data type.
 */
class HTMLPurifier_AttrDef_CSS_Composite extends HTMLPurifier_AttrDef
{

    /**
     * List of objects that may process strings.
     * @type HTMLPurifier_AttrDef[]
     * @todo Make protected
     */
    public $defs;

    /**
     * @param HTMLPurifier_AttrDef[] $defs List of HTMLPurifier_AttrDef objects
     */
    public function __construct($defs)
    {
        $this->defs = $defs;
    }

    /**
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($string, $config, $context)
    {
        foreach ($this->defs as $i => $def) {
            $result = $this->defs[$i]->validate($string, $config, $context);
            if ($result !== false) {
                return $result;
            }
        }
        return false;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates shorthand CSS property background.
 * @warning Does not support url tokens that have internal spaces.
 */
class HTMLPurifier_AttrDef_CSS_Background extends HTMLPurifier_AttrDef
{

    /**
     * Local copy of component validators.
     * @type HTMLPurifier_AttrDef[]
     * @note See HTMLPurifier_AttrDef_Font::$info for a similar impl.
     */
    protected $info;

    /**
     * @param HTMLPurifier_Config $config
     */
    public function __construct($config)
    {
        $def = $config->getCSSDefinition();
        $this->info['background-color'] = $def->info['background-color'];
        $this->info['background-image'] = $def->info['background-image'];
        $this->info['background-repeat'] = $def->info['background-repeat'];
        $this->info['background-attachment'] = $def->info['background-attachment'];
        $this->info['background-position'] = $def->info['background-position'];
    }

    /**
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($string, $config, $context)
    {
        // regular pre-processing
        $string = $this->parseCDATA($string);
        if ($string === '') {
            return false;
        }

        // munge rgb() decl if necessary
        $string = $this->mungeRgb($string);

        // assumes URI doesn't have spaces in it
        $bits = explode(' ', $string); // bits to process

        $caught = array();
        $caught['color'] = false;
        $caught['image'] = false;
        $caught['repeat'] = false;
        $caught['attachment'] = false;
        $caught['position'] = false;

        $i = 0; // number of catches

        foreach ($bits as $bit) {
            if ($bit === '') {
                continue;
            }
            foreach ($caught as $key => $status) {
                if ($key != 'position') {
                    if ($status !== false) {
                        continue;
                    }
                    $r = $this->info['background-' . $key]->validate($bit, $config, $context);
                } else {
                    $r = $bit;
                }
                if ($r === false) {
                    continue;
                }
                if ($key == 'position') {
                    if ($caught[$key] === false) {
                        $caught[$key] = '';
                    }
                    $caught[$key] .= $r . ' ';
                } else {
                    $caught[$key] = $r;
                }
                $i++;
                break;
            }
        }

        if (!$i) {
            return false;
        }
        if ($caught['position'] !== false) {
            $caught['position'] = $this->info['background-position']->
                validate($caught['position'], $config, $context);
        }

        $ret = array();
        foreach ($caught as $value) {
            if ($value === false) {
                continue;
            }
            $ret[] = $value;
        }

        if (empty($ret)) {
            return false;
        }
        return implode(' ', $ret);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates shorthand CSS property font.
 */
class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
{

    /**
     * Local copy of validators
     * @type HTMLPurifier_AttrDef[]
     * @note If we moved specific CSS property definitions to their own
     *       classes instead of having them be assembled at run time by
     *       CSSDefinition, this wouldn't be necessary.  We'd instantiate
     *       our own copies.
     */
    protected $info = array();

    /**
     * @param HTMLPurifier_Config $config
     */
    public function __construct($config)
    {
        $def = $config->getCSSDefinition();
        $this->info['font-style'] = $def->info['font-style'];
        $this->info['font-variant'] = $def->info['font-variant'];
        $this->info['font-weight'] = $def->info['font-weight'];
        $this->info['font-size'] = $def->info['font-size'];
        $this->info['line-height'] = $def->info['line-height'];
        $this->info['font-family'] = $def->info['font-family'];
    }

    /**
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($string, $config, $context)
    {
        static $system_fonts = array(
            'caption' => true,
            'icon' => true,
            'menu' => true,
            'message-box' => true,
            'small-caption' => true,
            'status-bar' => true
        );

        // regular pre-processing
        $string = $this->parseCDATA($string);
        if ($string === '') {
            return false;
        }

        // check if it's one of the keywords
        $lowercase_string = strtolower($string);
        if (isset($system_fonts[$lowercase_string])) {
            return $lowercase_string;
        }

        $bits = explode(' ', $string); // bits to process
        $stage = 0; // this indicates what we're looking for
        $caught = array(); // which stage 0 properties have we caught?
        $stage_1 = array('font-style', 'font-variant', 'font-weight');
        $final = ''; // output

        for ($i = 0, $size = count($bits); $i < $size; $i++) {
            if ($bits[$i] === '') {
                continue;
            }
            switch ($stage) {
                case 0: // attempting to catch font-style, font-variant or font-weight
                    foreach ($stage_1 as $validator_name) {
                        if (isset($caught[$validator_name])) {
                            continue;
                        }
                        $r = $this->info[$validator_name]->validate(
                            $bits[$i],
                            $config,
                            $context
                        );
                        if ($r !== false) {
                            $final .= $r . ' ';
                            $caught[$validator_name] = true;
                            break;
                        }
                    }
                    // all three caught, continue on
                    if (count($caught) >= 3) {
                        $stage = 1;
                    }
                    if ($r !== false) {
                        break;
                    }
                case 1: // attempting to catch font-size and perhaps line-height
                    $found_slash = false;
                    if (strpos($bits[$i], '/') !== false) {
                        list($font_size, $line_height) =
                            explode('/', $bits[$i]);
                        if ($line_height === '') {
                            // ooh, there's a space after the slash!
                            $line_height = false;
                            $found_slash = true;
                        }
                    } else {
                        $font_size = $bits[$i];
                        $line_height = false;
                    }
                    $r = $this->info['font-size']->validate(
                        $font_size,
                        $config,
                        $context
                    );
                    if ($r !== false) {
                        $final .= $r;
                        // attempt to catch line-height
                        if ($line_height === false) {
                            // we need to scroll forward
                            for ($j = $i + 1; $j < $size; $j++) {
                                if ($bits[$j] === '') {
                                    continue;
                                }
                                if ($bits[$j] === '/') {
                                    if ($found_slash) {
                                        return false;
                                    } else {
                                        $found_slash = true;
                                        continue;
                                    }
                                }
                                $line_height = $bits[$j];
                                break;
                            }
                        } else {
                            // slash already found
                            $found_slash = true;
                            $j = $i;
                        }
                        if ($found_slash) {
                            $i = $j;
                            $r = $this->info['line-height']->validate(
                                $line_height,
                                $config,
                                $context
                            );
                            if ($r !== false) {
                                $final .= '/' . $r;
                            }
                        }
                        $final .= ' ';
                        $stage = 2;
                        break;
                    }
                    return false;
                case 2: // attempting to catch font-family
                    $font_family =
                        implode(' ', array_slice($bits, $i, $size - $i));
                    $r = $this->info['font-family']->validate(
                        $font_family,
                        $config,
                        $context
                    );
                    if ($r !== false) {
                        $final .= $r . ' ';
                        // processing completed successfully
                        return rtrim($final);
                    }
                    return false;
            }
        }
        return false;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Framework class for strings that involve multiple values.
 *
 * Certain CSS properties such as border-width and margin allow multiple
 * lengths to be specified.  This class can take a vanilla border-width
 * definition and multiply it, usually into a max of four.
 *
 * @note Even though the CSS specification isn't clear about it, inherit
 *       can only be used alone: it will never manifest as part of a multi
 *       shorthand declaration.  Thus, this class does not allow inherit.
 */
class HTMLPurifier_AttrDef_CSS_Multiple extends HTMLPurifier_AttrDef
{
    /**
     * Instance of component definition to defer validation to.
     * @type HTMLPurifier_AttrDef
     * @todo Make protected
     */
    public $single;

    /**
     * Max number of values allowed.
     * @todo Make protected
     */
    public $max;

    /**
     * @param HTMLPurifier_AttrDef $single HTMLPurifier_AttrDef to multiply
     * @param int $max Max number of values allowed (usually four)
     */
    public function __construct($single, $max = 4)
    {
        $this->single = $single;
        $this->max = $max;
    }

    /**
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($string, $config, $context)
    {
        $string = $this->mungeRgb($this->parseCDATA($string));
        if ($string === '') {
            return false;
        }
        $parts = explode(' ', $string); // parseCDATA replaced \r, \t and \n
        $length = count($parts);
        $final = '';
        for ($i = 0, $num = 0; $i < $length && $num < $this->max; $i++) {
            if (ctype_space($parts[$i])) {
                continue;
            }
            $result = $this->single->validate($parts[$i], $config, $context);
            if ($result !== false) {
                $final .= $result . ' ';
                $num++;
            }
        }
        if ($final === '') {
            return false;
        }
        return rtrim($final);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Decorator which enables CSS properties to be disabled for specific elements.
 */
class HTMLPurifier_AttrDef_CSS_DenyElementDecorator extends HTMLPurifier_AttrDef
{
    /**
     * @type HTMLPurifier_AttrDef
     */
    public $def;
    /**
     * @type string
     */
    public $element;

    /**
     * @param HTMLPurifier_AttrDef $def Definition to wrap
     * @param string $element Element to deny
     */
    public function __construct($def, $element)
    {
        $this->def = $def;
        $this->element = $element;
    }

    /**
     * Checks if CurrentToken is set and equal to $this->element
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($string, $config, $context)
    {
        $token = $context->get('CurrentToken', true);
        if ($token && $token->name == $this->element) {
            return false;
        }
        return $this->def->validate($string, $config, $context);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates Color as defined by CSS.
 */
class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
{

    /**
     * @type HTMLPurifier_AttrDef_CSS_AlphaValue
     */
    protected $alpha;

    public function __construct()
    {
        $this->alpha = new HTMLPurifier_AttrDef_CSS_AlphaValue();
    }

    /**
     * @param string $color
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($color, $config, $context)
    {
        static $colors = null;
        if ($colors === null) {
            $colors = $config->get('Core.ColorKeywords');
        }

        $color = trim($color);
        if ($color === '') {
            return false;
        }

        $lower = strtolower($color);
        if (isset($colors[$lower])) {
            return $colors[$lower];
        }

        if (preg_match('#(rgb|rgba|hsl|hsla)\(#', $color, $matches) === 1) {
            $length = strlen($color);
            if (strpos($color, ')') !== $length - 1) {
                return false;
            }

            // get used function : rgb, rgba, hsl or hsla
            $function = $matches[1];

            $parameters_size = 3;
            $alpha_channel = false;
            if (substr($function, -1) === 'a') {
                $parameters_size = 4;
                $alpha_channel = true;
            }

            /*
             * Allowed types for values :
             * parameter_position => [type => max_value]
             */
            $allowed_types = array(
                1 => array('percentage' => 100, 'integer' => 255),
                2 => array('percentage' => 100, 'integer' => 255),
                3 => array('percentage' => 100, 'integer' => 255),
            );
            $allow_different_types = false;

            if (strpos($function, 'hsl') !== false) {
                $allowed_types = array(
                    1 => array('integer' => 360),
                    2 => array('percentage' => 100),
                    3 => array('percentage' => 100),
                );
                $allow_different_types = true;
            }

            $values = trim(str_replace($function, '', $color), ' ()');

            $parts = explode(',', $values);
            if (count($parts) !== $parameters_size) {
                return false;
            }

            $type = false;
            $new_parts = array();
            $i = 0;

            foreach ($parts as $part) {
                $i++;
                $part = trim($part);

                if ($part === '') {
                    return false;
                }

                // different check for alpha channel
                if ($alpha_channel === true && $i === count($parts)) {
                    $result = $this->alpha->validate($part, $config, $context);

                    if ($result === false) {
                        return false;
                    }

                    $new_parts[] = (string)$result;
                    continue;
                }

                if (substr($part, -1) === '%') {
                    $current_type = 'percentage';
                } else {
                    $current_type = 'integer';
                }

                if (!array_key_exists($current_type, $allowed_types[$i])) {
                    return false;
                }

                if (!$type) {
                    $type = $current_type;
                }

                if ($allow_different_types === false && $type != $current_type) {
                    return false;
                }

                $max_value = $allowed_types[$i][$current_type];

                if ($current_type == 'integer') {
                    // Return value between range 0 -> $max_value
                    $new_parts[] = (int)max(min($part, $max_value), 0);
                } elseif ($current_type == 'percentage') {
                    $new_parts[] = (float)max(min(rtrim($part, '%'), $max_value), 0) . '%';
                }
            }

            $new_values = implode(',', $new_parts);

            $color = $function . '(' . $new_values . ')';
        } else {
            // hexadecimal handling
            if ($color[0] === '#') {
                $hex = substr($color, 1);
            } else {
                $hex = $color;
                $color = '#' . $color;
            }
            $length = strlen($hex);
            if ($length !== 3 && $length !== 6) {
                return false;
            }
            if (!ctype_xdigit($hex)) {
                return false;
            }
        }
        return $color;
    }

}

// vim: et sw=4 sts=4
<?php

class HTMLPurifier_AttrDef_CSS_AlphaValue extends HTMLPurifier_AttrDef_CSS_Number
{

    public function __construct()
    {
        parent::__construct(false); // opacity is non-negative, but we will clamp it
    }

    /**
     * @param string $number
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return string
     */
    public function validate($number, $config, $context)
    {
        $result = parent::validate($number, $config, $context);
        if ($result === false) {
            return $result;
        }
        $float = (float)$result;
        if ($float < 0.0) {
            $result = '0';
        }
        if ($float > 1.0) {
            $result = '1';
        }
        return $result;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates based on {ident} CSS grammar production
 */
class HTMLPurifier_AttrDef_CSS_Ident extends HTMLPurifier_AttrDef
{

    /**
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($string, $config, $context)
    {
        $string = trim($string);

        // early abort: '' and '0' (strings that convert to false) are invalid
        if (!$string) {
            return false;
        }

        $pattern = '/^(-?[A-Za-z_][A-Za-z_\-0-9]*)$/';
        if (!preg_match($pattern, $string)) {
            return false;
        }
        return $string;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Microsoft's proprietary filter: CSS property
 * @note Currently supports the alpha filter. In the future, this will
 *       probably need an extensible framework
 */
class HTMLPurifier_AttrDef_CSS_Filter extends HTMLPurifier_AttrDef
{
    /**
     * @type HTMLPurifier_AttrDef_Integer
     */
    protected $intValidator;

    public function __construct()
    {
        $this->intValidator = new HTMLPurifier_AttrDef_Integer();
    }

    /**
     * @param string $value
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($value, $config, $context)
    {
        $value = $this->parseCDATA($value);
        if ($value === 'none') {
            return $value;
        }
        // if we looped this we could support multiple filters
        $function_length = strcspn($value, '(');
        $function = trim(substr($value, 0, $function_length));
        if ($function !== 'alpha' &&
            $function !== 'Alpha' &&
            $function !== 'progid:DXImageTransform.Microsoft.Alpha'
        ) {
            return false;
        }
        $cursor = $function_length + 1;
        $parameters_length = strcspn($value, ')', $cursor);
        $parameters = substr($value, $cursor, $parameters_length);
        $params = explode(',', $parameters);
        $ret_params = array();
        $lookup = array();
        foreach ($params as $param) {
            list($key, $value) = explode('=', $param);
            $key = trim($key);
            $value = trim($value);
            if (isset($lookup[$key])) {
                continue;
            }
            if ($key !== 'opacity') {
                continue;
            }
            $value = $this->intValidator->validate($value, $config, $context);
            if ($value === false) {
                continue;
            }
            $int = (int)$value;
            if ($int > 100) {
                $value = '100';
            }
            if ($int < 0) {
                $value = '0';
            }
            $ret_params[] = "$key=$value";
            $lookup[$key] = true;
        }
        $ret_parameters = implode(',', $ret_params);
        $ret_function = "$function($ret_parameters)";
        return $ret_function;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates a URI in CSS syntax, which uses url('http://example.com')
 * @note While theoretically speaking a URI in a CSS document could
 *       be non-embedded, as of CSS2 there is no such usage so we're
 *       generalizing it. This may need to be changed in the future.
 * @warning Since HTMLPurifier_AttrDef_CSS blindly uses semicolons as
 *          the separator, you cannot put a literal semicolon in
 *          in the URI. Try percent encoding it, in that case.
 */
class HTMLPurifier_AttrDef_CSS_URI extends HTMLPurifier_AttrDef_URI
{

    public function __construct()
    {
        parent::__construct(true); // always embedded
    }

    /**
     * @param string $uri_string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($uri_string, $config, $context)
    {
        // parse the URI out of the string and then pass it onto
        // the parent object

        $uri_string = $this->parseCDATA($uri_string);
        if (strpos($uri_string, 'url(') !== 0) {
            return false;
        }
        $uri_string = substr($uri_string, 4);
        if (strlen($uri_string) == 0) {
            return false;
        }
        $new_length = strlen($uri_string) - 1;
        if ($uri_string[$new_length] != ')') {
            return false;
        }
        $uri = trim(substr($uri_string, 0, $new_length));

        if (!empty($uri) && ($uri[0] == "'" || $uri[0] == '"')) {
            $quote = $uri[0];
            $new_length = strlen($uri) - 1;
            if ($uri[$new_length] !== $quote) {
                return false;
            }
            $uri = substr($uri, 1, $new_length - 1);
        }

        $uri = $this->expandCSSEscape($uri);

        $result = parent::validate($uri, $config, $context);

        if ($result === false) {
            return false;
        }

        // extra sanity check; should have been done by URI
        $result = str_replace(array('"', "\\", "\n", "\x0c", "\r"), "", $result);

        // suspicious characters are ()'; we're going to percent encode
        // them for safety.
        $result = str_replace(array('(', ')', "'"), array('%28', '%29', '%27'), $result);

        // there's an extra bug where ampersands lose their escaping on
        // an innerHTML cycle, so a very unlucky query parameter could
        // then change the meaning of the URL.  Unfortunately, there's
        // not much we can do about that...
        return "url(\"$result\")";
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Decorator which enables !important to be used in CSS values.
 */
class HTMLPurifier_AttrDef_CSS_ImportantDecorator extends HTMLPurifier_AttrDef
{
    /**
     * @type HTMLPurifier_AttrDef
     */
    public $def;
    /**
     * @type bool
     */
    public $allow;

    /**
     * @param HTMLPurifier_AttrDef $def Definition to wrap
     * @param bool $allow Whether or not to allow !important
     */
    public function __construct($def, $allow = false)
    {
        $this->def = $def;
        $this->allow = $allow;
    }

    /**
     * Intercepts and removes !important if necessary
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($string, $config, $context)
    {
        // test for ! and important tokens
        $string = trim($string);
        $is_important = false;
        // :TODO: optimization: test directly for !important and ! important
        if (strlen($string) >= 9 && substr($string, -9) === 'important') {
            $temp = rtrim(substr($string, 0, -9));
            // use a temp, because we might want to restore important
            if (strlen($temp) >= 1 && substr($temp, -1) === '!') {
                $string = rtrim(substr($temp, 0, -1));
                $is_important = true;
            }
        }
        $string = $this->def->validate($string, $config, $context);
        if ($this->allow && $is_important) {
            $string .= ' !important';
        }
        return $string;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates a font family list according to CSS spec
 */
class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef
{

    protected $mask = null;

    public function __construct()
    {
        $this->mask = '_- ';
        for ($c = 'a'; $c <= 'z'; $c++) {
            $this->mask .= $c;
        }
        for ($c = 'A'; $c <= 'Z'; $c++) {
            $this->mask .= $c;
        }
        for ($c = '0'; $c <= '9'; $c++) {
            $this->mask .= $c;
        } // cast-y, but should be fine
        // special bytes used by UTF-8
        for ($i = 0x80; $i <= 0xFF; $i++) {
            // We don't bother excluding invalid bytes in this range,
            // because the our restriction of well-formed UTF-8 will
            // prevent these from ever occurring.
            $this->mask .= chr($i);
        }

        /*
            PHP's internal strcspn implementation is
            O(length of string * length of mask), making it inefficient
            for large masks.  However, it's still faster than
            preg_match 8)
          for (p = s1;;) {
            spanp = s2;
            do {
              if (*spanp == c || p == s1_end) {
                return p - s1;
              }
            } while (spanp++ < (s2_end - 1));
            c = *++p;
          }
         */
        // possible optimization: invert the mask.
    }

    /**
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($string, $config, $context)
    {
        static $generic_names = array(
            'serif' => true,
            'sans-serif' => true,
            'monospace' => true,
            'fantasy' => true,
            'cursive' => true
        );
        $allowed_fonts = $config->get('CSS.AllowedFonts');

        // assume that no font names contain commas in them
        $fonts = explode(',', $string);
        $final = '';
        foreach ($fonts as $font) {
            $font = trim($font);
            if ($font === '') {
                continue;
            }
            // match a generic name
            if (isset($generic_names[$font])) {
                if ($allowed_fonts === null || isset($allowed_fonts[$font])) {
                    $final .= $font . ', ';
                }
                continue;
            }
            // match a quoted name
            if ($font[0] === '"' || $font[0] === "'") {
                $length = strlen($font);
                if ($length <= 2) {
                    continue;
                }
                $quote = $font[0];
                if ($font[$length - 1] !== $quote) {
                    continue;
                }
                $font = substr($font, 1, $length - 2);
            }

            $font = $this->expandCSSEscape($font);

            // $font is a pure representation of the font name

            if ($allowed_fonts !== null && !isset($allowed_fonts[$font])) {
                continue;
            }

            if (ctype_alnum($font) && $font !== '') {
                // very simple font, allow it in unharmed
                $final .= $font . ', ';
                continue;
            }

            // bugger out on whitespace.  form feed (0C) really
            // shouldn't show up regardless
            $font = str_replace(array("\n", "\t", "\r", "\x0C"), ' ', $font);

            // Here, there are various classes of characters which need
            // to be treated differently:
            //  - Alphanumeric characters are essentially safe.  We
            //    handled these above.
            //  - Spaces require quoting, though most parsers will do
            //    the right thing if there aren't any characters that
            //    can be misinterpreted
            //  - Dashes rarely occur, but they fairly unproblematic
            //    for parsing/rendering purposes.
            //  The above characters cover the majority of Western font
            //  names.
            //  - Arbitrary Unicode characters not in ASCII.  Because
            //    most parsers give little thought to Unicode, treatment
            //    of these codepoints is basically uniform, even for
            //    punctuation-like codepoints.  These characters can
            //    show up in non-Western pages and are supported by most
            //    major browsers, for example: "ï¼­ï¼³ æ˜Žæœ�" is a
            //    legitimate font-name
            //    <http://ja.wikipedia.org/wiki/MS_æ˜Žæœ�>.  See
            //    the CSS3 spec for more examples:
            //    <http://www.w3.org/TR/2011/WD-css3-fonts-20110324/localizedfamilynames.png>
            //    You can see live samples of these on the Internet:
            //    <http://www.google.co.jp/search?q=font-family+ï¼­ï¼³+æ˜Žæœ�|ã‚´ã‚·ãƒƒã‚¯>
            //    However, most of these fonts have ASCII equivalents:
            //    for example, 'MS Mincho', and it's considered
            //    professional to use ASCII font names instead of
            //    Unicode font names.  Thanks Takeshi Terada for
            //    providing this information.
            //  The following characters, to my knowledge, have not been
            //  used to name font names.
            //  - Single quote.  While theoretically you might find a
            //    font name that has a single quote in its name (serving
            //    as an apostrophe, e.g. Dave's Scribble), I haven't
            //    been able to find any actual examples of this.
            //    Internet Explorer's cssText translation (which I
            //    believe is invoked by innerHTML) normalizes any
            //    quoting to single quotes, and fails to escape single
            //    quotes.  (Note that this is not IE's behavior for all
            //    CSS properties, just some sort of special casing for
            //    font-family).  So a single quote *cannot* be used
            //    safely in the font-family context if there will be an
            //    innerHTML/cssText translation.  Note that Firefox 3.x
            //    does this too.
            //  - Double quote.  In IE, these get normalized to
            //    single-quotes, no matter what the encoding.  (Fun
            //    fact, in IE8, the 'content' CSS property gained
            //    support, where they special cased to preserve encoded
            //    double quotes, but still translate unadorned double
            //    quotes into single quotes.)  So, because their
            //    fixpoint behavior is identical to single quotes, they
            //    cannot be allowed either.  Firefox 3.x displays
            //    single-quote style behavior.
            //  - Backslashes are reduced by one (so \\ -> \) every
            //    iteration, so they cannot be used safely.  This shows
            //    up in IE7, IE8 and FF3
            //  - Semicolons, commas and backticks are handled properly.
            //  - The rest of the ASCII punctuation is handled properly.
            // We haven't checked what browsers do to unadorned
            // versions, but this is not important as long as the
            // browser doesn't /remove/ surrounding quotes (as IE does
            // for HTML).
            //
            // With these results in hand, we conclude that there are
            // various levels of safety:
            //  - Paranoid: alphanumeric, spaces and dashes(?)
            //  - International: Paranoid + non-ASCII Unicode
            //  - Edgy: Everything except quotes, backslashes
            //  - NoJS: Standards compliance, e.g. sod IE. Note that
            //    with some judicious character escaping (since certain
            //    types of escaping doesn't work) this is theoretically
            //    OK as long as innerHTML/cssText is not called.
            // We believe that international is a reasonable default
            // (that we will implement now), and once we do more
            // extensive research, we may feel comfortable with dropping
            // it down to edgy.

            // Edgy: alphanumeric, spaces, dashes, underscores and Unicode.  Use of
            // str(c)spn assumes that the string was already well formed
            // Unicode (which of course it is).
            if (strspn($font, $this->mask) !== strlen($font)) {
                continue;
            }

            // Historical:
            // In the absence of innerHTML/cssText, these ugly
            // transforms don't pose a security risk (as \\ and \"
            // might--these escapes are not supported by most browsers).
            // We could try to be clever and use single-quote wrapping
            // when there is a double quote present, but I have choosen
            // not to implement that.  (NOTE: you can reduce the amount
            // of escapes by one depending on what quoting style you use)
            // $font = str_replace('\\', '\\5C ', $font);
            // $font = str_replace('"',  '\\22 ', $font);
            // $font = str_replace("'",  '\\27 ', $font);

            // font possibly with spaces, requires quoting
            $final .= "'$font', ";
        }
        $final = rtrim($final, ', ');
        if ($final === '') {
            return false;
        }
        return $final;
    }

}

// vim: et sw=4 sts=4
<?php

/**
 * Represents a Length as defined by CSS.
 */
class HTMLPurifier_AttrDef_CSS_Length extends HTMLPurifier_AttrDef
{

    /**
     * @type HTMLPurifier_Length|string
     */
    protected $min;

    /**
     * @type HTMLPurifier_Length|string
     */
    protected $max;

    /**
     * @param HTMLPurifier_Length|string $min Minimum length, or null for no bound. String is also acceptable.
     * @param HTMLPurifier_Length|string $max Maximum length, or null for no bound. String is also acceptable.
     */
    public function __construct($min = null, $max = null)
    {
        $this->min = $min !== null ? HTMLPurifier_Length::make($min) : null;
        $this->max = $max !== null ? HTMLPurifier_Length::make($max) : null;
    }

    /**
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($string, $config, $context)
    {
        $string = $this->parseCDATA($string);

        // Optimizations
        if ($string === '') {
            return false;
        }
        if ($string === '0') {
            return '0';
        }
        if (strlen($string) === 1) {
            return false;
        }

        $length = HTMLPurifier_Length::make($string);
        if (!$length->isValid()) {
            return false;
        }

        if ($this->min) {
            $c = $length->compareTo($this->min);
            if ($c === false) {
                return false;
            }
            if ($c < 0) {
                return false;
            }
        }
        if ($this->max) {
            $c = $length->compareTo($this->max);
            if ($c === false) {
                return false;
            }
            if ($c > 0) {
                return false;
            }
        }
        return $length->toString();
    }
}

// vim: et sw=4 sts=4
<?php

// Enum = Enumerated
/**
 * Validates a keyword against a list of valid values.
 * @warning The case-insensitive compare of this function uses PHP's
 *          built-in strtolower and ctype_lower functions, which may
 *          cause problems with international comparisons
 */
class HTMLPurifier_AttrDef_Enum extends HTMLPurifier_AttrDef
{

    /**
     * Lookup table of valid values.
     * @type array
     * @todo Make protected
     */
    public $valid_values = array();

    /**
     * Bool indicating whether or not enumeration is case sensitive.
     * @note In general this is always case insensitive.
     */
    protected $case_sensitive = false; // values according to W3C spec

    /**
     * @param array $valid_values List of valid values
     * @param bool $case_sensitive Whether or not case sensitive
     */
    public function __construct($valid_values = array(), $case_sensitive = false)
    {
        $this->valid_values = array_flip($valid_values);
        $this->case_sensitive = $case_sensitive;
    }

    /**
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($string, $config, $context)
    {
        $string = trim($string);
        if (!$this->case_sensitive) {
            // we may want to do full case-insensitive libraries
            $string = ctype_lower($string) ? $string : strtolower($string);
        }
        $result = isset($this->valid_values[$string]);

        return $result ? $string : false;
    }

    /**
     * @param string $string In form of comma-delimited list of case-insensitive
     *      valid values. Example: "foo,bar,baz". Prepend "s:" to make
     *      case sensitive
     * @return HTMLPurifier_AttrDef_Enum
     */
    public function make($string)
    {
        if (strlen($string) > 2 && $string[0] == 's' && $string[1] == ':') {
            $string = substr($string, 2);
            $sensitive = true;
        } else {
            $sensitive = false;
        }
        $values = explode(',', $string);
        return new HTMLPurifier_AttrDef_Enum($values, $sensitive);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Implements special behavior for class attribute (normally NMTOKENS)
 */
class HTMLPurifier_AttrDef_HTML_Class extends HTMLPurifier_AttrDef_HTML_Nmtokens
{
    /**
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    protected function split($string, $config, $context)
    {
        // really, this twiddle should be lazy loaded
        $name = $config->getDefinition('HTML')->doctype->name;
        if ($name == "XHTML 1.1" || $name == "XHTML 2.0") {
            return parent::split($string, $config, $context);
        } else {
            return preg_split('/\s+/', $string);
        }
    }

    /**
     * @param array $tokens
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    protected function filter($tokens, $config, $context)
    {
        $allowed = $config->get('Attr.AllowedClasses');
        $forbidden = $config->get('Attr.ForbiddenClasses');
        $ret = array();
        foreach ($tokens as $token) {
            if (($allowed === null || isset($allowed[$token])) &&
                !isset($forbidden[$token]) &&
                // We need this O(n) check because of PHP's array
                // implementation that casts -0 to 0.
                !in_array($token, $ret, true)
            ) {
                $ret[] = $token;
            }
        }
        return $ret;
    }
}
<?php

/**
 * Validates a boolean attribute
 */
class HTMLPurifier_AttrDef_HTML_Bool extends HTMLPurifier_AttrDef
{

    /**
     * @type bool
     */
    protected $name;

    /**
     * @type bool
     */
    public $minimized = true;

    /**
     * @param bool $name
     */
    public function __construct($name = false)
    {
        $this->name = $name;
    }

    /**
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($string, $config, $context)
    {
        return $this->name;
    }

    /**
     * @param string $string Name of attribute
     * @return HTMLPurifier_AttrDef_HTML_Bool
     */
    public function make($string)
    {
        return new HTMLPurifier_AttrDef_HTML_Bool($string);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Special-case enum attribute definition that lazy loads allowed frame targets
 */
class HTMLPurifier_AttrDef_HTML_FrameTarget extends HTMLPurifier_AttrDef_Enum
{

    /**
     * @type array
     */
    public $valid_values = false; // uninitialized value

    /**
     * @type bool
     */
    protected $case_sensitive = false;

    public function __construct()
    {
    }

    /**
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($string, $config, $context)
    {
        if ($this->valid_values === false) {
            $this->valid_values = $config->get('Attr.AllowedFrameTargets');
        }
        return parent::validate($string, $config, $context);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates an integer representation of pixels according to the HTML spec.
 */
class HTMLPurifier_AttrDef_HTML_Pixels extends HTMLPurifier_AttrDef
{

    /**
     * @type int
     */
    protected $max;

    /**
     * @param int $max
     */
    public function __construct($max = null)
    {
        $this->max = $max;
    }

    /**
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($string, $config, $context)
    {
        $string = trim($string);
        if ($string === '0') {
            return $string;
        }
        if ($string === '') {
            return false;
        }
        $length = strlen($string);
        if (substr($string, $length - 2) == 'px') {
            $string = substr($string, 0, $length - 2);
        }
        if (!is_numeric($string)) {
            return false;
        }
        $int = (int)$string;

        if ($int < 0) {
            return '0';
        }

        // upper-bound value, extremely high values can
        // crash operating systems, see <http://ha.ckers.org/imagecrash.html>
        // WARNING, above link WILL crash you if you're using Windows

        if ($this->max !== null && $int > $this->max) {
            return (string)$this->max;
        }
        return (string)$int;
    }

    /**
     * @param string $string
     * @return HTMLPurifier_AttrDef
     */
    public function make($string)
    {
        if ($string === '') {
            $max = null;
        } else {
            $max = (int)$string;
        }
        $class = get_class($this);
        return new $class($max);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates the HTML attribute ID.
 * @warning Even though this is the id processor, it
 *          will ignore the directive Attr:IDBlacklist, since it will only
 *          go according to the ID accumulator. Since the accumulator is
 *          automatically generated, it will have already absorbed the
 *          blacklist. If you're hacking around, make sure you use load()!
 */

class HTMLPurifier_AttrDef_HTML_ID extends HTMLPurifier_AttrDef
{

    // selector is NOT a valid thing to use for IDREFs, because IDREFs
    // *must* target IDs that exist, whereas selector #ids do not.

    /**
     * Determines whether or not we're validating an ID in a CSS
     * selector context.
     * @type bool
     */
    protected $selector;

    /**
     * @param bool $selector
     */
    public function __construct($selector = false)
    {
        $this->selector = $selector;
    }

    /**
     * @param string $id
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($id, $config, $context)
    {
        if (!$this->selector && !$config->get('Attr.EnableID')) {
            return false;
        }

        $id = trim($id); // trim it first

        if ($id === '') {
            return false;
        }

        $prefix = $config->get('Attr.IDPrefix');
        if ($prefix !== '') {
            $prefix .= $config->get('Attr.IDPrefixLocal');
            // prevent re-appending the prefix
            if (strpos($id, $prefix) !== 0) {
                $id = $prefix . $id;
            }
        } elseif ($config->get('Attr.IDPrefixLocal') !== '') {
            trigger_error(
                '%Attr.IDPrefixLocal cannot be used unless ' .
                '%Attr.IDPrefix is set',
                E_USER_WARNING
            );
        }

        if (!$this->selector) {
            $id_accumulator =& $context->get('IDAccumulator');
            if (isset($id_accumulator->ids[$id])) {
                return false;
            }
        }

        // we purposely avoid using regex, hopefully this is faster

        if ($config->get('Attr.ID.HTML5') === true) {
            if (preg_match('/[\t\n\x0b\x0c ]/', $id)) {
                return false;
            }
        } else {
            if (ctype_alpha($id)) {
                // OK
            } else {
                if (!ctype_alpha(@$id[0])) {
                    return false;
                }
                // primitive style of regexps, I suppose
                $trim = trim(
                    $id,
                    'A..Za..z0..9:-._'
                );
                if ($trim !== '') {
                    return false;
                }
            }
        }

        $regexp = $config->get('Attr.IDBlacklistRegexp');
        if ($regexp && preg_match($regexp, $id)) {
            return false;
        }

        if (!$this->selector) {
            $id_accumulator->add($id);
        }

        // if no change was made to the ID, return the result
        // else, return the new id if stripping whitespace made it
        //     valid, or return false.
        return $id;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates a color according to the HTML spec.
 */
class HTMLPurifier_AttrDef_HTML_Color extends HTMLPurifier_AttrDef
{

    /**
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($string, $config, $context)
    {
        static $colors = null;
        if ($colors === null) {
            $colors = $config->get('Core.ColorKeywords');
        }

        $string = trim($string);

        if (empty($string)) {
            return false;
        }
        $lower = strtolower($string);
        if (isset($colors[$lower])) {
            return $colors[$lower];
        }
        if ($string[0] === '#') {
            $hex = substr($string, 1);
        } else {
            $hex = $string;
        }

        $length = strlen($hex);
        if ($length !== 3 && $length !== 6) {
            return false;
        }
        if (!ctype_xdigit($hex)) {
            return false;
        }
        if ($length === 3) {
            $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
        }
        return "#$hex";
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates a rel/rev link attribute against a directive of allowed values
 * @note We cannot use Enum because link types allow multiple
 *       values.
 * @note Assumes link types are ASCII text
 */
class HTMLPurifier_AttrDef_HTML_LinkTypes extends HTMLPurifier_AttrDef
{

    /**
     * Name config attribute to pull.
     * @type string
     */
    protected $name;

    /**
     * @param string $name
     */
    public function __construct($name)
    {
        $configLookup = array(
            'rel' => 'AllowedRel',
            'rev' => 'AllowedRev'
        );
        if (!isset($configLookup[$name])) {
            trigger_error(
                'Unrecognized attribute name for link ' .
                'relationship.',
                E_USER_ERROR
            );
            return;
        }
        $this->name = $configLookup[$name];
    }

    /**
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($string, $config, $context)
    {
        $allowed = $config->get('Attr.' . $this->name);
        if (empty($allowed)) {
            return false;
        }

        $string = $this->parseCDATA($string);
        $parts = explode(' ', $string);

        // lookup to prevent duplicates
        $ret_lookup = array();
        foreach ($parts as $part) {
            $part = strtolower(trim($part));
            if (!isset($allowed[$part])) {
                continue;
            }
            $ret_lookup[$part] = true;
        }

        if (empty($ret_lookup)) {
            return false;
        }
        $string = implode(' ', array_keys($ret_lookup));
        return $string;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates contents based on NMTOKENS attribute type.
 */
class HTMLPurifier_AttrDef_HTML_Nmtokens extends HTMLPurifier_AttrDef
{

    /**
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($string, $config, $context)
    {
        $string = trim($string);

        // early abort: '' and '0' (strings that convert to false) are invalid
        if (!$string) {
            return false;
        }

        $tokens = $this->split($string, $config, $context);
        $tokens = $this->filter($tokens, $config, $context);
        if (empty($tokens)) {
            return false;
        }
        return implode(' ', $tokens);
    }

    /**
     * Splits a space separated list of tokens into its constituent parts.
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    protected function split($string, $config, $context)
    {
        // OPTIMIZABLE!
        // do the preg_match, capture all subpatterns for reformulation

        // we don't support U+00A1 and up codepoints or
        // escaping because I don't know how to do that with regexps
        // and plus it would complicate optimization efforts (you never
        // see that anyway).
        $pattern = '/(?:(?<=\s)|\A)' . // look behind for space or string start
            '((?:--|-?[A-Za-z_])[A-Za-z_\-0-9]*)' .
            '(?:(?=\s)|\z)/'; // look ahead for space or string end
        preg_match_all($pattern, $string, $matches);
        return $matches[1];
    }

    /**
     * Template method for removing certain tokens based on arbitrary criteria.
     * @note If we wanted to be really functional, we'd do an array_filter
     *       with a callback. But... we're not.
     * @param array $tokens
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    protected function filter($tokens, $config, $context)
    {
        return $tokens;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates a MultiLength as defined by the HTML spec.
 *
 * A multilength is either a integer (pixel count), a percentage, or
 * a relative number.
 */
class HTMLPurifier_AttrDef_HTML_MultiLength extends HTMLPurifier_AttrDef_HTML_Length
{

    /**
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($string, $config, $context)
    {
        $string = trim($string);
        if ($string === '') {
            return false;
        }

        $parent_result = parent::validate($string, $config, $context);
        if ($parent_result !== false) {
            return $parent_result;
        }

        $length = strlen($string);
        $last_char = $string[$length - 1];

        if ($last_char !== '*') {
            return false;
        }

        $int = substr($string, 0, $length - 1);

        if ($int == '') {
            return '*';
        }
        if (!is_numeric($int)) {
            return false;
        }

        $int = (int)$int;
        if ($int < 0) {
            return false;
        }
        if ($int == 0) {
            return '0';
        }
        if ($int == 1) {
            return '*';
        }
        return ((string)$int) . '*';
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates the HTML type length (not to be confused with CSS's length).
 *
 * This accepts integer pixels or percentages as lengths for certain
 * HTML attributes.
 */

class HTMLPurifier_AttrDef_HTML_Length extends HTMLPurifier_AttrDef_HTML_Pixels
{

    /**
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($string, $config, $context)
    {
        $string = trim($string);
        if ($string === '') {
            return false;
        }

        $parent_result = parent::validate($string, $config, $context);
        if ($parent_result !== false) {
            return $parent_result;
        }

        $length = strlen($string);
        $last_char = $string[$length - 1];

        if ($last_char !== '%') {
            return false;
        }

        $points = substr($string, 0, $length - 1);

        if (!is_numeric($points)) {
            return false;
        }

        $points = (int)$points;

        if ($points < 0) {
            return '0%';
        }
        if ($points > 100) {
            return '100%';
        }
        return ((string)$points) . '%';
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Dummy AttrDef that mimics another AttrDef, BUT it generates clones
 * with make.
 */
class HTMLPurifier_AttrDef_Clone extends HTMLPurifier_AttrDef
{
    /**
     * What we're cloning.
     * @type HTMLPurifier_AttrDef
     */
    protected $clone;

    /**
     * @param HTMLPurifier_AttrDef $clone
     */
    public function __construct($clone)
    {
        $this->clone = $clone;
    }

    /**
     * @param string $v
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($v, $config, $context)
    {
        return $this->clone->validate($v, $config, $context);
    }

    /**
     * @param string $string
     * @return HTMLPurifier_AttrDef
     */
    public function make($string)
    {
        return clone $this->clone;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Decorator that, depending on a token, switches between two definitions.
 */
class HTMLPurifier_AttrDef_Switch
{

    /**
     * @type string
     */
    protected $tag;

    /**
     * @type HTMLPurifier_AttrDef
     */
    protected $withTag;

    /**
     * @type HTMLPurifier_AttrDef
     */
    protected $withoutTag;

    /**
     * @param string $tag Tag name to switch upon
     * @param HTMLPurifier_AttrDef $with_tag Call if token matches tag
     * @param HTMLPurifier_AttrDef $without_tag Call if token doesn't match, or there is no token
     */
    public function __construct($tag, $with_tag, $without_tag)
    {
        $this->tag = $tag;
        $this->withTag = $with_tag;
        $this->withoutTag = $without_tag;
    }

    /**
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($string, $config, $context)
    {
        $token = $context->get('CurrentToken', true);
        if (!$token || $token->name !== $this->tag) {
            return $this->withoutTag->validate($string, $config, $context);
        } else {
            return $this->withTag->validate($string, $config, $context);
        }
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates a URI as defined by RFC 3986.
 * @note Scheme-specific mechanics deferred to HTMLPurifier_URIScheme
 */
class HTMLPurifier_AttrDef_URI extends HTMLPurifier_AttrDef
{

    /**
     * @type HTMLPurifier_URIParser
     */
    protected $parser;

    /**
     * @type bool
     */
    protected $embedsResource;

    /**
     * @param bool $embeds_resource Does the URI here result in an extra HTTP request?
     */
    public function __construct($embeds_resource = false)
    {
        $this->parser = new HTMLPurifier_URIParser();
        $this->embedsResource = (bool)$embeds_resource;
    }

    /**
     * @param string $string
     * @return HTMLPurifier_AttrDef_URI
     */
    public function make($string)
    {
        $embeds = ($string === 'embedded');
        return new HTMLPurifier_AttrDef_URI($embeds);
    }

    /**
     * @param string $uri
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($uri, $config, $context)
    {
        if ($config->get('URI.Disable')) {
            return false;
        }

        $uri = $this->parseCDATA($uri);

        // parse the URI
        $uri = $this->parser->parse($uri);
        if ($uri === false) {
            return false;
        }

        // add embedded flag to context for validators
        $context->register('EmbeddedURI', $this->embedsResource);

        $ok = false;
        do {

            // generic validation
            $result = $uri->validate($config, $context);
            if (!$result) {
                break;
            }

            // chained filtering
            $uri_def = $config->getDefinition('URI');
            $result = $uri_def->filter($uri, $config, $context);
            if (!$result) {
                break;
            }

            // scheme-specific validation
            $scheme_obj = $uri->getSchemeObj($config, $context);
            if (!$scheme_obj) {
                break;
            }
            if ($this->embedsResource && !$scheme_obj->browsable) {
                break;
            }
            $result = $scheme_obj->validate($uri, $config, $context);
            if (!$result) {
                break;
            }

            // Post chained filtering
            $result = $uri_def->postFilter($uri, $config, $context);
            if (!$result) {
                break;
            }

            // survived gauntlet
            $ok = true;

        } while (false);

        $context->destroy('EmbeddedURI');
        if (!$ok) {
            return false;
        }
        // back to string
        return $uri->toString();
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates the HTML attribute style, otherwise known as CSS.
 * @note We don't implement the whole CSS specification, so it might be
 *       difficult to reuse this component in the context of validating
 *       actual stylesheet declarations.
 * @note If we were really serious about validating the CSS, we would
 *       tokenize the styles and then parse the tokens. Obviously, we
 *       are not doing that. Doing that could seriously harm performance,
 *       but would make these components a lot more viable for a CSS
 *       filtering solution.
 */
class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef
{

    /**
     * @param string $css
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($css, $config, $context)
    {
        $css = $this->parseCDATA($css);

        $definition = $config->getCSSDefinition();
        $allow_duplicates = $config->get("CSS.AllowDuplicates");


        // According to the CSS2.1 spec, the places where a
        // non-delimiting semicolon can appear are in strings
        // escape sequences.   So here is some dumb hack to
        // handle quotes.
        $len = strlen($css);
        $accum = "";
        $declarations = array();
        $quoted = false;
        for ($i = 0; $i < $len; $i++) {
            $c = strcspn($css, ";'\"", $i);
            $accum .= substr($css, $i, $c);
            $i += $c;
            if ($i == $len) break;
            $d = $css[$i];
            if ($quoted) {
                $accum .= $d;
                if ($d == $quoted) {
                    $quoted = false;
                }
            } else {
                if ($d == ";") {
                    $declarations[] = $accum;
                    $accum = "";
                } else {
                    $accum .= $d;
                    $quoted = $d;
                }
            }
        }
        if ($accum != "") $declarations[] = $accum;

        $propvalues = array();
        $new_declarations = '';

        /**
         * Name of the current CSS property being validated.
         */
        $property = false;
        $context->register('CurrentCSSProperty', $property);

        foreach ($declarations as $declaration) {
            if (!$declaration) {
                continue;
            }
            if (!strpos($declaration, ':')) {
                continue;
            }
            list($property, $value) = explode(':', $declaration, 2);
            $property = trim($property);
            $value = trim($value);
            $ok = false;
            do {
                if (isset($definition->info[$property])) {
                    $ok = true;
                    break;
                }
                if (ctype_lower($property)) {
                    break;
                }
                $property = strtolower($property);
                if (isset($definition->info[$property])) {
                    $ok = true;
                    break;
                }
            } while (0);
            if (!$ok) {
                continue;
            }
            // inefficient call, since the validator will do this again
            if (strtolower(trim($value)) !== 'inherit') {
                // inherit works for everything (but only on the base property)
                $result = $definition->info[$property]->validate(
                    $value,
                    $config,
                    $context
                );
            } else {
                $result = 'inherit';
            }
            if ($result === false) {
                continue;
            }
            if ($allow_duplicates) {
                $new_declarations .= "$property:$result;";
            } else {
                $propvalues[$property] = $result;
            }
        }

        $context->destroy('CurrentCSSProperty');

        // procedure does not write the new CSS simultaneously, so it's
        // slightly inefficient, but it's the only way of getting rid of
        // duplicates. Perhaps config to optimize it, but not now.

        foreach ($propvalues as $prop => $value) {
            $new_declarations .= "$prop:$value;";
        }

        return $new_declarations ? $new_declarations : false;

    }

}

// vim: et sw=4 sts=4
<?php

abstract class HTMLPurifier_AttrDef_URI_Email extends HTMLPurifier_AttrDef
{

    /**
     * Unpacks a mailbox into its display-name and address
     * @param string $string
     * @return mixed
     */
    public function unpack($string)
    {
        // needs to be implemented
    }

}

// sub-implementations

// vim: et sw=4 sts=4
<?php

/**
 * Validates an IPv6 address.
 * @author Feyd @ forums.devnetwork.net (public domain)
 * @note This function requires brackets to have been removed from address
 *       in URI.
 */
class HTMLPurifier_AttrDef_URI_IPv6 extends HTMLPurifier_AttrDef_URI_IPv4
{

    /**
     * @param string $aIP
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($aIP, $config, $context)
    {
        if (!$this->ip4) {
            $this->_loadRegex();
        }

        $original = $aIP;

        $hex = '[0-9a-fA-F]';
        $blk = '(?:' . $hex . '{1,4})';
        $pre = '(?:/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))'; // /0 - /128

        //      prefix check
        if (strpos($aIP, '/') !== false) {
            if (preg_match('#' . $pre . '$#s', $aIP, $find)) {
                $aIP = substr($aIP, 0, 0 - strlen($find[0]));
                unset($find);
            } else {
                return false;
            }
        }

        //      IPv4-compatiblity check
        if (preg_match('#(?<=:' . ')' . $this->ip4 . '$#s', $aIP, $find)) {
            $aIP = substr($aIP, 0, 0 - strlen($find[0]));
            $ip = explode('.', $find[0]);
            $ip = array_map('dechex', $ip);
            $aIP .= $ip[0] . $ip[1] . ':' . $ip[2] . $ip[3];
            unset($find, $ip);
        }

        //      compression check
        $aIP = explode('::', $aIP);
        $c = count($aIP);
        if ($c > 2) {
            return false;
        } elseif ($c == 2) {
            list($first, $second) = $aIP;
            $first = explode(':', $first);
            $second = explode(':', $second);

            if (count($first) + count($second) > 8) {
                return false;
            }

            while (count($first) < 8) {
                array_push($first, '0');
            }

            array_splice($first, 8 - count($second), 8, $second);
            $aIP = $first;
            unset($first, $second);
        } else {
            $aIP = explode(':', $aIP[0]);
        }
        $c = count($aIP);

        if ($c != 8) {
            return false;
        }

        //      All the pieces should be 16-bit hex strings. Are they?
        foreach ($aIP as $piece) {
            if (!preg_match('#^[0-9a-fA-F]{4}$#s', sprintf('%04s', $piece))) {
                return false;
            }
        }
        return $original;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates a host according to the IPv4, IPv6 and DNS (future) specifications.
 */
class HTMLPurifier_AttrDef_URI_Host extends HTMLPurifier_AttrDef
{

    /**
     * IPv4 sub-validator.
     * @type HTMLPurifier_AttrDef_URI_IPv4
     */
    protected $ipv4;

    /**
     * IPv6 sub-validator.
     * @type HTMLPurifier_AttrDef_URI_IPv6
     */
    protected $ipv6;

    public function __construct()
    {
        $this->ipv4 = new HTMLPurifier_AttrDef_URI_IPv4();
        $this->ipv6 = new HTMLPurifier_AttrDef_URI_IPv6();
    }

    /**
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($string, $config, $context)
    {
        $length = strlen($string);
        // empty hostname is OK; it's usually semantically equivalent:
        // the default host as defined by a URI scheme is used:
        //
        //      If the URI scheme defines a default for host, then that
        //      default applies when the host subcomponent is undefined
        //      or when the registered name is empty (zero length).
        if ($string === '') {
            return '';
        }
        if ($length > 1 && $string[0] === '[' && $string[$length - 1] === ']') {
            //IPv6
            $ip = substr($string, 1, $length - 2);
            $valid = $this->ipv6->validate($ip, $config, $context);
            if ($valid === false) {
                return false;
            }
            return '[' . $valid . ']';
        }

        // need to do checks on unusual encodings too
        $ipv4 = $this->ipv4->validate($string, $config, $context);
        if ($ipv4 !== false) {
            return $ipv4;
        }

        // A regular domain name.

        // This doesn't match I18N domain names, but we don't have proper IRI support,
        // so force users to insert Punycode.

        // There is not a good sense in which underscores should be
        // allowed, since it's technically not! (And if you go as
        // far to allow everything as specified by the DNS spec...
        // well, that's literally everything, modulo some space limits
        // for the components and the overall name (which, by the way,
        // we are NOT checking!).  So we (arbitrarily) decide this:
        // let's allow underscores wherever we would have allowed
        // hyphens, if they are enabled.  This is a pretty good match
        // for browser behavior, for example, a large number of browsers
        // cannot handle foo_.example.com, but foo_bar.example.com is
        // fairly well supported.
        $underscore = $config->get('Core.AllowHostnameUnderscore') ? '_' : '';

        // Based off of RFC 1738, but amended so that
        // as per RFC 3696, the top label need only not be all numeric.
        // The productions describing this are:
        $a   = '[a-z]';     // alpha
        $an  = '[a-z0-9]';  // alphanum
        $and = "[a-z0-9-$underscore]"; // alphanum | "-"
        // domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
        $domainlabel = "$an(?:$and*$an)?";
        // AMENDED as per RFC 3696
        // toplabel    = alphanum | alphanum *( alphanum | "-" ) alphanum
        //      side condition: not all numeric
        $toplabel = "$an(?:$and*$an)?";
        // hostname    = *( domainlabel "." ) toplabel [ "." ]
        if (preg_match("/^(?:$domainlabel\.)*($toplabel)\.?$/i", $string, $matches)) {
            if (!ctype_digit($matches[1])) {
                return $string;
            }
        }

        // PHP 5.3 and later support this functionality natively
        if (function_exists('idn_to_ascii')) {
            $string = idn_to_ascii($string, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);

        // If we have Net_IDNA2 support, we can support IRIs by
        // punycoding them. (This is the most portable thing to do,
        // since otherwise we have to assume browsers support
        } elseif ($config->get('Core.EnableIDNA')) {
            $idna = new Net_IDNA2(array('encoding' => 'utf8', 'overlong' => false, 'strict' => true));
            // we need to encode each period separately
            $parts = explode('.', $string);
            try {
                $new_parts = array();
                foreach ($parts as $part) {
                    $encodable = false;
                    for ($i = 0, $c = strlen($part); $i < $c; $i++) {
                        if (ord($part[$i]) > 0x7a) {
                            $encodable = true;
                            break;
                        }
                    }
                    if (!$encodable) {
                        $new_parts[] = $part;
                    } else {
                        $new_parts[] = $idna->encode($part);
                    }
                }
                $string = implode('.', $new_parts);
            } catch (Exception $e) {
                // XXX error reporting
            }
        }
        // Try again
        if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) {
            return $string;
        }
        return false;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates an IPv4 address
 * @author Feyd @ forums.devnetwork.net (public domain)
 */
class HTMLPurifier_AttrDef_URI_IPv4 extends HTMLPurifier_AttrDef
{

    /**
     * IPv4 regex, protected so that IPv6 can reuse it.
     * @type string
     */
    protected $ip4;

    /**
     * @param string $aIP
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($aIP, $config, $context)
    {
        if (!$this->ip4) {
            $this->_loadRegex();
        }

        if (preg_match('#^' . $this->ip4 . '$#s', $aIP)) {
            return $aIP;
        }
        return false;
    }

    /**
     * Lazy load function to prevent regex from being stuffed in
     * cache.
     */
    protected function _loadRegex()
    {
        $oct = '(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])'; // 0-255
        $this->ip4 = "(?:{$oct}\\.{$oct}\\.{$oct}\\.{$oct})";
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Primitive email validation class based on the regexp found at
 * http://www.regular-expressions.info/email.html
 */
class HTMLPurifier_AttrDef_URI_Email_SimpleCheck extends HTMLPurifier_AttrDef_URI_Email
{

    /**
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($string, $config, $context)
    {
        // no support for named mailboxes i.e. "Bob <bob@example.com>"
        // that needs more percent encoding to be done
        if ($string == '') {
            return false;
        }
        $string = trim($string);
        $result = preg_match('/^[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i', $string);
        return $result ? $string : false;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validates arbitrary text according to the HTML spec.
 */
class HTMLPurifier_AttrDef_Text extends HTMLPurifier_AttrDef
{

    /**
     * @param string $string
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string
     */
    public function validate($string, $config, $context)
    {
        return $this->parseCDATA($string);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Forgivingly lexes HTML (SGML-style) markup into tokens.
 *
 * A lexer parses a string of SGML-style markup and converts them into
 * corresponding tokens.  It doesn't check for well-formedness, although its
 * internal mechanism may make this automatic (such as the case of
 * HTMLPurifier_Lexer_DOMLex).  There are several implementations to choose
 * from.
 *
 * A lexer is HTML-oriented: it might work with XML, but it's not
 * recommended, as we adhere to a subset of the specification for optimization
 * reasons. This might change in the future. Also, most tokenizers are not
 * expected to handle DTDs or PIs.
 *
 * This class should not be directly instantiated, but you may use create() to
 * retrieve a default copy of the lexer.  Being a supertype, this class
 * does not actually define any implementation, but offers commonly used
 * convenience functions for subclasses.
 *
 * @note The unit tests will instantiate this class for testing purposes, as
 *       many of the utility functions require a class to be instantiated.
 *       This means that, even though this class is not runnable, it will
 *       not be declared abstract.
 *
 * @par
 *
 * @note
 * We use tokens rather than create a DOM representation because DOM would:
 *
 * @par
 *  -# Require more processing and memory to create,
 *  -# Is not streamable, and
 *  -# Has the entire document structure (html and body not needed).
 *
 * @par
 * However, DOM is helpful in that it makes it easy to move around nodes
 * without a lot of lookaheads to see when a tag is closed. This is a
 * limitation of the token system and some workarounds would be nice.
 */
class HTMLPurifier_Lexer
{

    /**
     * Whether or not this lexer implements line-number/column-number tracking.
     * If it does, set to true.
     */
    public $tracksLineNumbers = false;

    // -- STATIC ----------------------------------------------------------

    /**
     * Retrieves or sets the default Lexer as a Prototype Factory.
     *
     * By default HTMLPurifier_Lexer_DOMLex will be returned. There are
     * a few exceptions involving special features that only DirectLex
     * implements.
     *
     * @note The behavior of this class has changed, rather than accepting
     *       a prototype object, it now accepts a configuration object.
     *       To specify your own prototype, set %Core.LexerImpl to it.
     *       This change in behavior de-singletonizes the lexer object.
     *
     * @param HTMLPurifier_Config $config
     * @return HTMLPurifier_Lexer
     * @throws HTMLPurifier_Exception
     */
    public static function create($config)
    {
        if (!($config instanceof HTMLPurifier_Config)) {
            $lexer = $config;
            trigger_error(
                "Passing a prototype to
                HTMLPurifier_Lexer::create() is deprecated, please instead
                use %Core.LexerImpl",
                E_USER_WARNING
            );
        } else {
            $lexer = $config->get('Core.LexerImpl');
        }

        $needs_tracking =
            $config->get('Core.MaintainLineNumbers') ||
            $config->get('Core.CollectErrors');

        $inst = null;
        if (is_object($lexer)) {
            $inst = $lexer;
        } else {
            if (is_null($lexer)) {
                do {
                    // auto-detection algorithm
                    if ($needs_tracking) {
                        $lexer = 'DirectLex';
                        break;
                    }

                    if (class_exists('DOMDocument', false) &&
                        method_exists('DOMDocument', 'loadHTML') &&
                        !extension_loaded('domxml')
                    ) {
                        // check for DOM support, because while it's part of the
                        // core, it can be disabled compile time. Also, the PECL
                        // domxml extension overrides the default DOM, and is evil
                        // and nasty and we shan't bother to support it
                        $lexer = 'DOMLex';
                    } else {
                        $lexer = 'DirectLex';
                    }
                } while (0);
            } // do..while so we can break

            // instantiate recognized string names
            switch ($lexer) {
                case 'DOMLex':
                    $inst = new HTMLPurifier_Lexer_DOMLex();
                    break;
                case 'DirectLex':
                    $inst = new HTMLPurifier_Lexer_DirectLex();
                    break;
                case 'PH5P':
                    $inst = new HTMLPurifier_Lexer_PH5P();
                    break;
                default:
                    throw new HTMLPurifier_Exception(
                        "Cannot instantiate unrecognized Lexer type " .
                        htmlspecialchars($lexer)
                    );
            }
        }

        if (!$inst) {
            throw new HTMLPurifier_Exception('No lexer was instantiated');
        }

        // once PHP DOM implements native line numbers, or we
        // hack out something using XSLT, remove this stipulation
        if ($needs_tracking && !$inst->tracksLineNumbers) {
            throw new HTMLPurifier_Exception(
                'Cannot use lexer that does not support line numbers with ' .
                'Core.MaintainLineNumbers or Core.CollectErrors (use DirectLex instead)'
            );
        }

        return $inst;

    }

    // -- CONVENIENCE MEMBERS ---------------------------------------------

    public function __construct()
    {
        $this->_entity_parser = new HTMLPurifier_EntityParser();
    }

    /**
     * Most common entity to raw value conversion table for special entities.
     * @type array
     */
    protected $_special_entity2str =
        array(
            '&quot;' => '"',
            '&amp;' => '&',
            '&lt;' => '<',
            '&gt;' => '>',
            '&#39;' => "'",
            '&#039;' => "'",
            '&#x27;' => "'"
        );

    public function parseText($string, $config) {
        return $this->parseData($string, false, $config);
    }

    public function parseAttr($string, $config) {
        return $this->parseData($string, true, $config);
    }

    /**
     * Parses special entities into the proper characters.
     *
     * This string will translate escaped versions of the special characters
     * into the correct ones.
     *
     * @param string $string String character data to be parsed.
     * @return string Parsed character data.
     */
    public function parseData($string, $is_attr, $config)
    {
        // following functions require at least one character
        if ($string === '') {
            return '';
        }

        // subtracts amps that cannot possibly be escaped
        $num_amp = substr_count($string, '&') - substr_count($string, '& ') -
            ($string[strlen($string) - 1] === '&' ? 1 : 0);

        if (!$num_amp) {
            return $string;
        } // abort if no entities
        $num_esc_amp = substr_count($string, '&amp;');
        $string = strtr($string, $this->_special_entity2str);

        // code duplication for sake of optimization, see above
        $num_amp_2 = substr_count($string, '&') - substr_count($string, '& ') -
            ($string[strlen($string) - 1] === '&' ? 1 : 0);

        if ($num_amp_2 <= $num_esc_amp) {
            return $string;
        }

        // hmm... now we have some uncommon entities. Use the callback.
        if ($config->get('Core.LegacyEntityDecoder')) {
            $string = $this->_entity_parser->substituteSpecialEntities($string);
        } else {
            if ($is_attr) {
                $string = $this->_entity_parser->substituteAttrEntities($string);
            } else {
                $string = $this->_entity_parser->substituteTextEntities($string);
            }
        }
        return $string;
    }

    /**
     * Lexes an HTML string into tokens.
     * @param $string String HTML.
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return HTMLPurifier_Token[] array representation of HTML.
     */
    public function tokenizeHTML($string, $config, $context)
    {
        trigger_error('Call to abstract class', E_USER_ERROR);
    }

    /**
     * Translates CDATA sections into regular sections (through escaping).
     * @param string $string HTML string to process.
     * @return string HTML with CDATA sections escaped.
     */
    protected static function escapeCDATA($string)
    {
        return preg_replace_callback(
            '/<!\[CDATA\[(.+?)\]\]>/s',
            array('HTMLPurifier_Lexer', 'CDATACallback'),
            $string
        );
    }

    /**
     * Special CDATA case that is especially convoluted for <script>
     * @param string $string HTML string to process.
     * @return string HTML with CDATA sections escaped.
     */
    protected static function escapeCommentedCDATA($string)
    {
        return preg_replace_callback(
            '#<!--//--><!\[CDATA\[//><!--(.+?)//--><!\]\]>#s',
            array('HTMLPurifier_Lexer', 'CDATACallback'),
            $string
        );
    }

    /**
     * Special Internet Explorer conditional comments should be removed.
     * @param string $string HTML string to process.
     * @return string HTML with conditional comments removed.
     */
    protected static function removeIEConditional($string)
    {
        return preg_replace(
            '#<!--\[if [^>]+\]>.*?<!\[endif\]-->#si', // probably should generalize for all strings
            '',
            $string
        );
    }

    /**
     * Callback function for escapeCDATA() that does the work.
     *
     * @warning Though this is public in order to let the callback happen,
     *          calling it directly is not recommended.
     * @param array $matches PCRE matches array, with index 0 the entire match
     *                  and 1 the inside of the CDATA section.
     * @return string Escaped internals of the CDATA section.
     */
    protected static function CDATACallback($matches)
    {
        // not exactly sure why the character set is needed, but whatever
        return htmlspecialchars($matches[1], ENT_COMPAT, 'UTF-8');
    }

    /**
     * Takes a piece of HTML and normalizes it by converting entities, fixing
     * encoding, extracting bits, and other good stuff.
     * @param string $html HTML.
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return string
     * @todo Consider making protected
     */
    public function normalize($html, $config, $context)
    {
        // normalize newlines to \n
        if ($config->get('Core.NormalizeNewlines')) {
            $html = str_replace("\r\n", "\n", $html);
            $html = str_replace("\r", "\n", $html);
        }

        if ($config->get('HTML.Trusted')) {
            // escape convoluted CDATA
            $html = $this->escapeCommentedCDATA($html);
        }

        // escape CDATA
        $html = $this->escapeCDATA($html);

        $html = $this->removeIEConditional($html);

        // extract body from document if applicable
        if ($config->get('Core.ConvertDocumentToFragment')) {
            $e = false;
            if ($config->get('Core.CollectErrors')) {
                $e =& $context->get('ErrorCollector');
            }
            $new_html = $this->extractBody($html);
            if ($e && $new_html != $html) {
                $e->send(E_WARNING, 'Lexer: Extracted body');
            }
            $html = $new_html;
        }

        // expand entities that aren't the big five
        if ($config->get('Core.LegacyEntityDecoder')) {
            $html = $this->_entity_parser->substituteNonSpecialEntities($html);
        }

        // clean into wellformed UTF-8 string for an SGML context: this has
        // to be done after entity expansion because the entities sometimes
        // represent non-SGML characters (horror, horror!)
        $html = HTMLPurifier_Encoder::cleanUTF8($html);

        // if processing instructions are to removed, remove them now
        if ($config->get('Core.RemoveProcessingInstructions')) {
            $html = preg_replace('#<\?.+?\?>#s', '', $html);
        }

        $hidden_elements = $config->get('Core.HiddenElements');
        if ($config->get('Core.AggressivelyRemoveScript') &&
            !($config->get('HTML.Trusted') || !$config->get('Core.RemoveScriptContents')
            || empty($hidden_elements["script"]))) {
            $html = preg_replace('#<script[^>]*>.*?</script>#i', '', $html);
        }

        return $html;
    }

    /**
     * Takes a string of HTML (fragment or document) and returns the content
     * @todo Consider making protected
     */
    public function extractBody($html)
    {
        $matches = array();
        $result = preg_match('|(.*?)<body[^>]*>(.*)</body>|is', $html, $matches);
        if ($result) {
            // Make sure it's not in a comment
            $comment_start = strrpos($matches[1], '<!--');
            $comment_end   = strrpos($matches[1], '-->');
            if ($comment_start === false ||
                ($comment_end !== false && $comment_end > $comment_start)) {
                return $matches[2];
            }
        }
        return $html;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * HTML Purifier's internal representation of a URI.
 * @note
 *      Internal data-structures are completely escaped. If the data needs
 *      to be used in a non-URI context (which is very unlikely), be sure
 *      to decode it first. The URI may not necessarily be well-formed until
 *      validate() is called.
 */
class HTMLPurifier_URI
{
    /**
     * @type string
     */
    public $scheme;

    /**
     * @type string
     */
    public $userinfo;

    /**
     * @type string
     */
    public $host;

    /**
     * @type int
     */
    public $port;

    /**
     * @type string
     */
    public $path;

    /**
     * @type string
     */
    public $query;

    /**
     * @type string
     */
    public $fragment;

    /**
     * @param string $scheme
     * @param string $userinfo
     * @param string $host
     * @param int $port
     * @param string $path
     * @param string $query
     * @param string $fragment
     * @note Automatically normalizes scheme and port
     */
    public function __construct($scheme, $userinfo, $host, $port, $path, $query, $fragment)
    {
        $this->scheme = is_null($scheme) || ctype_lower($scheme) ? $scheme : strtolower($scheme);
        $this->userinfo = $userinfo;
        $this->host = $host;
        $this->port = is_null($port) ? $port : (int)$port;
        $this->path = $path;
        $this->query = $query;
        $this->fragment = $fragment;
    }

    /**
     * Retrieves a scheme object corresponding to the URI's scheme/default
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return HTMLPurifier_URIScheme Scheme object appropriate for validating this URI
     */
    public function getSchemeObj($config, $context)
    {
        $registry = HTMLPurifier_URISchemeRegistry::instance();
        if ($this->scheme !== null) {
            $scheme_obj = $registry->getScheme($this->scheme, $config, $context);
            if (!$scheme_obj) {
                return false;
            } // invalid scheme, clean it out
        } else {
            // no scheme: retrieve the default one
            $def = $config->getDefinition('URI');
            $scheme_obj = $def->getDefaultScheme($config, $context);
            if (!$scheme_obj) {
                if ($def->defaultScheme !== null) {
                    // something funky happened to the default scheme object
                    trigger_error(
                        'Default scheme object "' . $def->defaultScheme . '" was not readable',
                        E_USER_WARNING
                    );
                } // suppress error if it's null
                return false;
            }
        }
        return $scheme_obj;
    }

    /**
     * Generic validation method applicable for all schemes. May modify
     * this URI in order to get it into a compliant form.
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool True if validation/filtering succeeds, false if failure
     */
    public function validate($config, $context)
    {
        // ABNF definitions from RFC 3986
        $chars_sub_delims = '!$&\'()*+,;=';
        $chars_gen_delims = ':/?#[]@';
        $chars_pchar = $chars_sub_delims . ':@';

        // validate host
        if (!is_null($this->host)) {
            $host_def = new HTMLPurifier_AttrDef_URI_Host();
            $this->host = $host_def->validate($this->host, $config, $context);
            if ($this->host === false) {
                $this->host = null;
            }
        }

        // validate scheme
        // NOTE: It's not appropriate to check whether or not this
        // scheme is in our registry, since a URIFilter may convert a
        // URI that we don't allow into one we do.  So instead, we just
        // check if the scheme can be dropped because there is no host
        // and it is our default scheme.
        if (!is_null($this->scheme) && is_null($this->host) || $this->host === '') {
            // support for relative paths is pretty abysmal when the
            // scheme is present, so axe it when possible
            $def = $config->getDefinition('URI');
            if ($def->defaultScheme === $this->scheme) {
                $this->scheme = null;
            }
        }

        // validate username
        if (!is_null($this->userinfo)) {
            $encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . ':');
            $this->userinfo = $encoder->encode($this->userinfo);
        }

        // validate port
        if (!is_null($this->port)) {
            if ($this->port < 1 || $this->port > 65535) {
                $this->port = null;
            }
        }

        // validate path
        $segments_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/');
        if (!is_null($this->host)) { // this catches $this->host === ''
            // path-abempty (hier and relative)
            // http://www.example.com/my/path
            // //www.example.com/my/path (looks odd, but works, and
            //                            recognized by most browsers)
            // (this set is valid or invalid on a scheme by scheme
            // basis, so we'll deal with it later)
            // file:///my/path
            // ///my/path
            $this->path = $segments_encoder->encode($this->path);
        } elseif ($this->path !== '') {
            if ($this->path[0] === '/') {
                // path-absolute (hier and relative)
                // http:/my/path
                // /my/path
                if (strlen($this->path) >= 2 && $this->path[1] === '/') {
                    // This could happen if both the host gets stripped
                    // out
                    // http://my/path
                    // //my/path
                    $this->path = '';
                } else {
                    $this->path = $segments_encoder->encode($this->path);
                }
            } elseif (!is_null($this->scheme)) {
                // path-rootless (hier)
                // http:my/path
                // Short circuit evaluation means we don't need to check nz
                $this->path = $segments_encoder->encode($this->path);
            } else {
                // path-noscheme (relative)
                // my/path
                // (once again, not checking nz)
                $segment_nc_encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . '@');
                $c = strpos($this->path, '/');
                if ($c !== false) {
                    $this->path =
                        $segment_nc_encoder->encode(substr($this->path, 0, $c)) .
                        $segments_encoder->encode(substr($this->path, $c));
                } else {
                    $this->path = $segment_nc_encoder->encode($this->path);
                }
            }
        } else {
            // path-empty (hier and relative)
            $this->path = ''; // just to be safe
        }

        // qf = query and fragment
        $qf_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/?');

        if (!is_null($this->query)) {
            $this->query = $qf_encoder->encode($this->query);
        }

        if (!is_null($this->fragment)) {
            $this->fragment = $qf_encoder->encode($this->fragment);
        }
        return true;
    }

    /**
     * Convert URI back to string
     * @return string URI appropriate for output
     */
    public function toString()
    {
        // reconstruct authority
        $authority = null;
        // there is a rendering difference between a null authority
        // (http:foo-bar) and an empty string authority
        // (http:///foo-bar).
        if (!is_null($this->host)) {
            $authority = '';
            if (!is_null($this->userinfo)) {
                $authority .= $this->userinfo . '@';
            }
            $authority .= $this->host;
            if (!is_null($this->port)) {
                $authority .= ':' . $this->port;
            }
        }

        // Reconstruct the result
        // One might wonder about parsing quirks from browsers after
        // this reconstruction.  Unfortunately, parsing behavior depends
        // on what *scheme* was employed (file:///foo is handled *very*
        // differently than http:///foo), so unfortunately we have to
        // defer to the schemes to do the right thing.
        $result = '';
        if (!is_null($this->scheme)) {
            $result .= $this->scheme . ':';
        }
        if (!is_null($authority)) {
            $result .= '//' . $authority;
        }
        $result .= $this->path;
        if (!is_null($this->query)) {
            $result .= '?' . $this->query;
        }
        if (!is_null($this->fragment)) {
            $result .= '#' . $this->fragment;
        }

        return $result;
    }

    /**
     * Returns true if this URL might be considered a 'local' URL given
     * the current context.  This is true when the host is null, or
     * when it matches the host supplied to the configuration.
     *
     * Note that this does not do any scheme checking, so it is mostly
     * only appropriate for metadata that doesn't care about protocol
     * security.  isBenign is probably what you actually want.
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool
     */
    public function isLocal($config, $context)
    {
        if ($this->host === null) {
            return true;
        }
        $uri_def = $config->getDefinition('URI');
        if ($uri_def->host === $this->host) {
            return true;
        }
        return false;
    }

    /**
     * Returns true if this URL should be considered a 'benign' URL,
     * that is:
     *
     *      - It is a local URL (isLocal), and
     *      - It has a equal or better level of security
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool
     */
    public function isBenign($config, $context)
    {
        if (!$this->isLocal($config, $context)) {
            return false;
        }

        $scheme_obj = $this->getSchemeObj($config, $context);
        if (!$scheme_obj) {
            return false;
        } // conservative approach

        $current_scheme_obj = $config->getDefinition('URI')->getDefaultScheme($config, $context);
        if ($current_scheme_obj->secure) {
            if (!$scheme_obj->secure) {
                return false;
            }
        }
        return true;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Represents a language and defines localizable string formatting and
 * other functions, as well as the localized messages for HTML Purifier.
 */
class HTMLPurifier_Language
{

    /**
     * ISO 639 language code of language. Prefers shortest possible version.
     * @type string
     */
    public $code = 'en';

    /**
     * Fallback language code.
     * @type bool|string
     */
    public $fallback = false;

    /**
     * Array of localizable messages.
     * @type array
     */
    public $messages = array();

    /**
     * Array of localizable error codes.
     * @type array
     */
    public $errorNames = array();

    /**
     * True if no message file was found for this language, so English
     * is being used instead. Check this if you'd like to notify the
     * user that they've used a non-supported language.
     * @type bool
     */
    public $error = false;

    /**
     * Has the language object been loaded yet?
     * @type bool
     * @todo Make it private, fix usage in HTMLPurifier_LanguageTest
     */
    public $_loaded = false;

    /**
     * @type HTMLPurifier_Config
     */
    protected $config;

    /**
     * @type HTMLPurifier_Context
     */
    protected $context;

    /**
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     */
    public function __construct($config, $context)
    {
        $this->config  = $config;
        $this->context = $context;
    }

    /**
     * Loads language object with necessary info from factory cache
     * @note This is a lazy loader
     */
    public function load()
    {
        if ($this->_loaded) {
            return;
        }
        $factory = HTMLPurifier_LanguageFactory::instance();
        $factory->loadLanguage($this->code);
        foreach ($factory->keys as $key) {
            $this->$key = $factory->cache[$this->code][$key];
        }
        $this->_loaded = true;
    }

    /**
     * Retrieves a localised message.
     * @param string $key string identifier of message
     * @return string localised message
     */
    public function getMessage($key)
    {
        if (!$this->_loaded) {
            $this->load();
        }
        if (!isset($this->messages[$key])) {
            return "[$key]";
        }
        return $this->messages[$key];
    }

    /**
     * Retrieves a localised error name.
     * @param int $int error number, corresponding to PHP's error reporting
     * @return string localised message
     */
    public function getErrorName($int)
    {
        if (!$this->_loaded) {
            $this->load();
        }
        if (!isset($this->errorNames[$int])) {
            return "[Error: $int]";
        }
        return $this->errorNames[$int];
    }

    /**
     * Converts an array list into a string readable representation
     * @param array $array
     * @return string
     */
    public function listify($array)
    {
        $sep      = $this->getMessage('Item separator');
        $sep_last = $this->getMessage('Item separator last');
        $ret = '';
        for ($i = 0, $c = count($array); $i < $c; $i++) {
            if ($i == 0) {
            } elseif ($i + 1 < $c) {
                $ret .= $sep;
            } else {
                $ret .= $sep_last;
            }
            $ret .= $array[$i];
        }
        return $ret;
    }

    /**
     * Formats a localised message with passed parameters
     * @param string $key string identifier of message
     * @param array $args Parameters to substitute in
     * @return string localised message
     * @todo Implement conditionals? Right now, some messages make
     *     reference to line numbers, but those aren't always available
     */
    public function formatMessage($key, $args = array())
    {
        if (!$this->_loaded) {
            $this->load();
        }
        if (!isset($this->messages[$key])) {
            return "[$key]";
        }
        $raw = $this->messages[$key];
        $subst = array();
        $generator = false;
        foreach ($args as $i => $value) {
            if (is_object($value)) {
                if ($value instanceof HTMLPurifier_Token) {
                    // factor this out some time
                    if (!$generator) {
                        $generator = $this->context->get('Generator');
                    }
                    if (isset($value->name)) {
                        $subst['$'.$i.'.Name'] = $value->name;
                    }
                    if (isset($value->data)) {
                        $subst['$'.$i.'.Data'] = $value->data;
                    }
                    $subst['$'.$i.'.Compact'] =
                    $subst['$'.$i.'.Serialized'] = $generator->generateFromToken($value);
                    // a more complex algorithm for compact representation
                    // could be introduced for all types of tokens. This
                    // may need to be factored out into a dedicated class
                    if (!empty($value->attr)) {
                        $stripped_token = clone $value;
                        $stripped_token->attr = array();
                        $subst['$'.$i.'.Compact'] = $generator->generateFromToken($stripped_token);
                    }
                    $subst['$'.$i.'.Line'] = $value->line ? $value->line : 'unknown';
                }
                continue;
            } elseif (is_array($value)) {
                $keys = array_keys($value);
                if (array_keys($keys) === $keys) {
                    // list
                    $subst['$'.$i] = $this->listify($value);
                } else {
                    // associative array
                    // no $i implementation yet, sorry
                    $subst['$'.$i.'.Keys'] = $this->listify($keys);
                    $subst['$'.$i.'.Values'] = $this->listify(array_values($value));
                }
                continue;
            }
            $subst['$' . $i] = $value;
        }
        return strtr($raw, $subst);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Component of HTMLPurifier_AttrContext that accumulates IDs to prevent dupes
 * @note In Slashdot-speak, dupe means duplicate.
 * @note The default constructor does not accept $config or $context objects:
 *       use must use the static build() factory method to perform initialization.
 */
class HTMLPurifier_IDAccumulator
{

    /**
     * Lookup table of IDs we've accumulated.
     * @public
     */
    public $ids = array();

    /**
     * Builds an IDAccumulator, also initializing the default blacklist
     * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config
     * @param HTMLPurifier_Context $context Instance of HTMLPurifier_Context
     * @return HTMLPurifier_IDAccumulator Fully initialized HTMLPurifier_IDAccumulator
     */
    public static function build($config, $context)
    {
        $id_accumulator = new HTMLPurifier_IDAccumulator();
        $id_accumulator->load($config->get('Attr.IDBlacklist'));
        return $id_accumulator;
    }

    /**
     * Add an ID to the lookup table.
     * @param string $id ID to be added.
     * @return bool status, true if success, false if there's a dupe
     */
    public function add($id)
    {
        if (isset($this->ids[$id])) {
            return false;
        }
        return $this->ids[$id] = true;
    }

    /**
     * Load a list of IDs into the lookup table
     * @param $array_of_ids Array of IDs to load
     * @note This function doesn't care about duplicates
     */
    public function load($array_of_ids)
    {
        foreach ($array_of_ids as $id) {
            $this->ids[$id] = true;
        }
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * @todo Unit test
 */
class HTMLPurifier_ContentSets
{

    /**
     * List of content set strings (pipe separators) indexed by name.
     * @type array
     */
    public $info = array();

    /**
     * List of content set lookups (element => true) indexed by name.
     * @type array
     * @note This is in HTMLPurifier_HTMLDefinition->info_content_sets
     */
    public $lookup = array();

    /**
     * Synchronized list of defined content sets (keys of info).
     * @type array
     */
    protected $keys = array();
    /**
     * Synchronized list of defined content values (values of info).
     * @type array
     */
    protected $values = array();

    /**
     * Merges in module's content sets, expands identifiers in the content
     * sets and populates the keys, values and lookup member variables.
     * @param HTMLPurifier_HTMLModule[] $modules List of HTMLPurifier_HTMLModule
     */
    public function __construct($modules)
    {
        if (!is_array($modules)) {
            $modules = array($modules);
        }
        // populate content_sets based on module hints
        // sorry, no way of overloading
        foreach ($modules as $module) {
            foreach ($module->content_sets as $key => $value) {
                $temp = $this->convertToLookup($value);
                if (isset($this->lookup[$key])) {
                    // add it into the existing content set
                    $this->lookup[$key] = array_merge($this->lookup[$key], $temp);
                } else {
                    $this->lookup[$key] = $temp;
                }
            }
        }
        $old_lookup = false;
        while ($old_lookup !== $this->lookup) {
            $old_lookup = $this->lookup;
            foreach ($this->lookup as $i => $set) {
                $add = array();
                foreach ($set as $element => $x) {
                    if (isset($this->lookup[$element])) {
                        $add += $this->lookup[$element];
                        unset($this->lookup[$i][$element]);
                    }
                }
                $this->lookup[$i] += $add;
            }
        }

        foreach ($this->lookup as $key => $lookup) {
            $this->info[$key] = implode(' | ', array_keys($lookup));
        }
        $this->keys   = array_keys($this->info);
        $this->values = array_values($this->info);
    }

    /**
     * Accepts a definition; generates and assigns a ChildDef for it
     * @param HTMLPurifier_ElementDef $def HTMLPurifier_ElementDef reference
     * @param HTMLPurifier_HTMLModule $module Module that defined the ElementDef
     */
    public function generateChildDef(&$def, $module)
    {
        if (!empty($def->child)) { // already done!
            return;
        }
        $content_model = $def->content_model;
        if (is_string($content_model)) {
            // Assume that $this->keys is alphanumeric
            $def->content_model = preg_replace_callback(
                '/\b(' . implode('|', $this->keys) . ')\b/',
                array($this, 'generateChildDefCallback'),
                $content_model
            );
            //$def->content_model = str_replace(
            //    $this->keys, $this->values, $content_model);
        }
        $def->child = $this->getChildDef($def, $module);
    }

    public function generateChildDefCallback($matches)
    {
        return $this->info[$matches[0]];
    }

    /**
     * Instantiates a ChildDef based on content_model and content_model_type
     * member variables in HTMLPurifier_ElementDef
     * @note This will also defer to modules for custom HTMLPurifier_ChildDef
     *       subclasses that need content set expansion
     * @param HTMLPurifier_ElementDef $def HTMLPurifier_ElementDef to have ChildDef extracted
     * @param HTMLPurifier_HTMLModule $module Module that defined the ElementDef
     * @return HTMLPurifier_ChildDef corresponding to ElementDef
     */
    public function getChildDef($def, $module)
    {
        $value = $def->content_model;
        if (is_object($value)) {
            trigger_error(
                'Literal object child definitions should be stored in '.
                'ElementDef->child not ElementDef->content_model',
                E_USER_NOTICE
            );
            return $value;
        }
        switch ($def->content_model_type) {
            case 'required':
                return new HTMLPurifier_ChildDef_Required($value);
            case 'optional':
                return new HTMLPurifier_ChildDef_Optional($value);
            case 'empty':
                return new HTMLPurifier_ChildDef_Empty();
            case 'custom':
                return new HTMLPurifier_ChildDef_Custom($value);
        }
        // defer to its module
        $return = false;
        if ($module->defines_child_def) { // save a func call
            $return = $module->getChildDef($def);
        }
        if ($return !== false) {
            return $return;
        }
        // error-out
        trigger_error(
            'Could not determine which ChildDef class to instantiate',
            E_USER_ERROR
        );
        return false;
    }

    /**
     * Converts a string list of elements separated by pipes into
     * a lookup array.
     * @param string $string List of elements
     * @return array Lookup array of elements
     */
    protected function convertToLookup($string)
    {
        $array = explode('|', str_replace(' ', '', $string));
        $ret = array();
        foreach ($array as $k) {
            $ret[$k] = true;
        }
        return $ret;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * This is in almost every respect equivalent to an array except
 * that it keeps track of which keys were accessed.
 *
 * @warning For the sake of backwards compatibility with early versions
 *     of PHP 5, you must not use the $hash[$key] syntax; if you do
 *     our version of offsetGet is never called.
 */
class HTMLPurifier_StringHash extends ArrayObject
{
    /**
     * @type array
     */
    protected $accessed = array();

    /**
     * Retrieves a value, and logs the access.
     * @param mixed $index
     * @return mixed
     */
    public function offsetGet($index)
    {
        $this->accessed[$index] = true;
        return parent::offsetGet($index);
    }

    /**
     * Returns a lookup array of all array indexes that have been accessed.
     * @return array in form array($index => true).
     */
    public function getAccessed()
    {
        return $this->accessed;
    }

    /**
     * Resets the access array.
     */
    public function resetAccessed()
    {
        $this->accessed = array();
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Represents an XHTML 1.1 module, with information on elements, tags
 * and attributes.
 * @note Even though this is technically XHTML 1.1, it is also used for
 *       regular HTML parsing. We are using modulization as a convenient
 *       way to represent the internals of HTMLDefinition, and our
 *       implementation is by no means conforming and does not directly
 *       use the normative DTDs or XML schemas.
 * @note The public variables in a module should almost directly
 *       correspond to the variables in HTMLPurifier_HTMLDefinition.
 *       However, the prefix info carries no special meaning in these
 *       objects (include it anyway if that's the correspondence though).
 * @todo Consider making some member functions protected
 */

class HTMLPurifier_HTMLModule
{

    // -- Overloadable ----------------------------------------------------

    /**
     * Short unique string identifier of the module.
     * @type string
     */
    public $name;

    /**
     * Informally, a list of elements this module changes.
     * Not used in any significant way.
     * @type array
     */
    public $elements = array();

    /**
     * Associative array of element names to element definitions.
     * Some definitions may be incomplete, to be merged in later
     * with the full definition.
     * @type array
     */
    public $info = array();

    /**
     * Associative array of content set names to content set additions.
     * This is commonly used to, say, add an A element to the Inline
     * content set. This corresponds to an internal variable $content_sets
     * and NOT info_content_sets member variable of HTMLDefinition.
     * @type array
     */
    public $content_sets = array();

    /**
     * Associative array of attribute collection names to attribute
     * collection additions. More rarely used for adding attributes to
     * the global collections. Example is the StyleAttribute module adding
     * the style attribute to the Core. Corresponds to HTMLDefinition's
     * attr_collections->info, since the object's data is only info,
     * with extra behavior associated with it.
     * @type array
     */
    public $attr_collections = array();

    /**
     * Associative array of deprecated tag name to HTMLPurifier_TagTransform.
     * @type array
     */
    public $info_tag_transform = array();

    /**
     * List of HTMLPurifier_AttrTransform to be performed before validation.
     * @type array
     */
    public $info_attr_transform_pre = array();

    /**
     * List of HTMLPurifier_AttrTransform to be performed after validation.
     * @type array
     */
    public $info_attr_transform_post = array();

    /**
     * List of HTMLPurifier_Injector to be performed during well-formedness fixing.
     * An injector will only be invoked if all of it's pre-requisites are met;
     * if an injector fails setup, there will be no error; it will simply be
     * silently disabled.
     * @type array
     */
    public $info_injector = array();

    /**
     * Boolean flag that indicates whether or not getChildDef is implemented.
     * For optimization reasons: may save a call to a function. Be sure
     * to set it if you do implement getChildDef(), otherwise it will have
     * no effect!
     * @type bool
     */
    public $defines_child_def = false;

    /**
     * Boolean flag whether or not this module is safe. If it is not safe, all
     * of its members are unsafe. Modules are safe by default (this might be
     * slightly dangerous, but it doesn't make much sense to force HTML Purifier,
     * which is based off of safe HTML, to explicitly say, "This is safe," even
     * though there are modules which are "unsafe")
     *
     * @type bool
     * @note Previously, safety could be applied at an element level granularity.
     *       We've removed this ability, so in order to add "unsafe" elements
     *       or attributes, a dedicated module with this property set to false
     *       must be used.
     */
    public $safe = true;

    /**
     * Retrieves a proper HTMLPurifier_ChildDef subclass based on
     * content_model and content_model_type member variables of
     * the HTMLPurifier_ElementDef class. There is a similar function
     * in HTMLPurifier_HTMLDefinition.
     * @param HTMLPurifier_ElementDef $def
     * @return HTMLPurifier_ChildDef subclass
     */
    public function getChildDef($def)
    {
        return false;
    }

    // -- Convenience -----------------------------------------------------

    /**
     * Convenience function that sets up a new element
     * @param string $element Name of element to add
     * @param string|bool $type What content set should element be registered to?
     *              Set as false to skip this step.
     * @param string $contents Allowed children in form of:
     *              "$content_model_type: $content_model"
     * @param array $attr_includes What attribute collections to register to
     *              element?
     * @param array $attr What unique attributes does the element define?
     * @see HTMLPurifier_ElementDef:: for in-depth descriptions of these parameters.
     * @return HTMLPurifier_ElementDef Created element definition object, so you
     *         can set advanced parameters
     */
    public function addElement($element, $type, $contents, $attr_includes = array(), $attr = array())
    {
        $this->elements[] = $element;
        // parse content_model
        list($content_model_type, $content_model) = $this->parseContents($contents);
        // merge in attribute inclusions
        $this->mergeInAttrIncludes($attr, $attr_includes);
        // add element to content sets
        if ($type) {
            $this->addElementToContentSet($element, $type);
        }
        // create element
        $this->info[$element] = HTMLPurifier_ElementDef::create(
            $content_model,
            $content_model_type,
            $attr
        );
        // literal object $contents means direct child manipulation
        if (!is_string($contents)) {
            $this->info[$element]->child = $contents;
        }
        return $this->info[$element];
    }

    /**
     * Convenience function that creates a totally blank, non-standalone
     * element.
     * @param string $element Name of element to create
     * @return HTMLPurifier_ElementDef Created element
     */
    public function addBlankElement($element)
    {
        if (!isset($this->info[$element])) {
            $this->elements[] = $element;
            $this->info[$element] = new HTMLPurifier_ElementDef();
            $this->info[$element]->standalone = false;
        } else {
            trigger_error("Definition for $element already exists in module, cannot redefine");
        }
        return $this->info[$element];
    }

    /**
     * Convenience function that registers an element to a content set
     * @param string $element Element to register
     * @param string $type Name content set (warning: case sensitive, usually upper-case
     *        first letter)
     */
    public function addElementToContentSet($element, $type)
    {
        if (!isset($this->content_sets[$type])) {
            $this->content_sets[$type] = '';
        } else {
            $this->content_sets[$type] .= ' | ';
        }
        $this->content_sets[$type] .= $element;
    }

    /**
     * Convenience function that transforms single-string contents
     * into separate content model and content model type
     * @param string $contents Allowed children in form of:
     *                  "$content_model_type: $content_model"
     * @return array
     * @note If contents is an object, an array of two nulls will be
     *       returned, and the callee needs to take the original $contents
     *       and use it directly.
     */
    public function parseContents($contents)
    {
        if (!is_string($contents)) {
            return array(null, null);
        } // defer
        switch ($contents) {
            // check for shorthand content model forms
            case 'Empty':
                return array('empty', '');
            case 'Inline':
                return array('optional', 'Inline | #PCDATA');
            case 'Flow':
                return array('optional', 'Flow | #PCDATA');
        }
        list($content_model_type, $content_model) = explode(':', $contents);
        $content_model_type = strtolower(trim($content_model_type));
        $content_model = trim($content_model);
        return array($content_model_type, $content_model);
    }

    /**
     * Convenience function that merges a list of attribute includes into
     * an attribute array.
     * @param array $attr Reference to attr array to modify
     * @param array $attr_includes Array of includes / string include to merge in
     */
    public function mergeInAttrIncludes(&$attr, $attr_includes)
    {
        if (!is_array($attr_includes)) {
            if (empty($attr_includes)) {
                $attr_includes = array();
            } else {
                $attr_includes = array($attr_includes);
            }
        }
        $attr[0] = $attr_includes;
    }

    /**
     * Convenience function that generates a lookup table with boolean
     * true as value.
     * @param string $list List of values to turn into a lookup
     * @note You can also pass an arbitrary number of arguments in
     *       place of the regular argument
     * @return array array equivalent of list
     */
    public function makeLookup($list)
    {
        if (is_string($list)) {
            $list = func_get_args();
        }
        $ret = array();
        foreach ($list as $value) {
            if (is_null($value)) {
                continue;
            }
            $ret[$value] = true;
        }
        return $ret;
    }

    /**
     * Lazy load construction of the module after determining whether
     * or not it's needed, and also when a finalized configuration object
     * is available.
     * @param HTMLPurifier_Config $config
     */
    public function setup($config)
    {
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Object that provides entity lookup table from entity name to character
 */
class HTMLPurifier_EntityLookup
{
    /**
     * Assoc array of entity name to character represented.
     * @type array
     */
    public $table;

    /**
     * Sets up the entity lookup table from the serialized file contents.
     * @param bool $file
     * @note The serialized contents are versioned, but were generated
     *       using the maintenance script generate_entity_file.php
     * @warning This is not in constructor to help enforce the Singleton
     */
    public function setup($file = false)
    {
        if (!$file) {
            $file = HTMLPURIFIER_PREFIX . '/HTMLPurifier/EntityLookup/entities.ser';
        }
        $this->table = unserialize(file_get_contents($file));
    }

    /**
     * Retrieves sole instance of the object.
     * @param bool|HTMLPurifier_EntityLookup $prototype Optional prototype of custom lookup table to overload with.
     * @return HTMLPurifier_EntityLookup
     */
    public static function instance($prototype = false)
    {
        // no references, since PHP doesn't copy unless modified
        static $instance = null;
        if ($prototype) {
            $instance = $prototype;
        } elseif (!$instance) {
            $instance = new HTMLPurifier_EntityLookup();
            $instance->setup();
        }
        return $instance;
    }
}

// vim: et sw=4 sts=4
<?php

// if want to implement error collecting here, we'll need to use some sort
// of global data (probably trigger_error) because it's impossible to pass
// $config or $context to the callback functions.

/**
 * Handles referencing and derefencing character entities
 */
class HTMLPurifier_EntityParser
{

    /**
     * Reference to entity lookup table.
     * @type HTMLPurifier_EntityLookup
     */
    protected $_entity_lookup;

    /**
     * Callback regex string for entities in text.
     * @type string
     */
    protected $_textEntitiesRegex;

    /**
     * Callback regex string for entities in attributes.
     * @type string
     */
    protected $_attrEntitiesRegex;

    /**
     * Tests if the beginning of a string is a semi-optional regex
     */
    protected $_semiOptionalPrefixRegex;

    public function __construct() {
        // From
        // http://stackoverflow.com/questions/15532252/why-is-reg-being-rendered-as-without-the-bounding-semicolon
        $semi_optional = "quot|QUOT|lt|LT|gt|GT|amp|AMP|AElig|Aacute|Acirc|Agrave|Aring|Atilde|Auml|COPY|Ccedil|ETH|Eacute|Ecirc|Egrave|Euml|Iacute|Icirc|Igrave|Iuml|Ntilde|Oacute|Ocirc|Ograve|Oslash|Otilde|Ouml|REG|THORN|Uacute|Ucirc|Ugrave|Uuml|Yacute|aacute|acirc|acute|aelig|agrave|aring|atilde|auml|brvbar|ccedil|cedil|cent|copy|curren|deg|divide|eacute|ecirc|egrave|eth|euml|frac12|frac14|frac34|iacute|icirc|iexcl|igrave|iquest|iuml|laquo|macr|micro|middot|nbsp|not|ntilde|oacute|ocirc|ograve|ordf|ordm|oslash|otilde|ouml|para|plusmn|pound|raquo|reg|sect|shy|sup1|sup2|sup3|szlig|thorn|times|uacute|ucirc|ugrave|uml|uuml|yacute|yen|yuml";

        // NB: three empty captures to put the fourth match in the right
        // place
        $this->_semiOptionalPrefixRegex = "/&()()()($semi_optional)/";

        $this->_textEntitiesRegex =
            '/&(?:'.
            // hex
            '[#]x([a-fA-F0-9]+);?|'.
            // dec
            '[#]0*(\d+);?|'.
            // string (mandatory semicolon)
            // NB: order matters: match semicolon preferentially
            '([A-Za-z_:][A-Za-z0-9.\-_:]*);|'.
            // string (optional semicolon)
            "($semi_optional)".
            ')/';

        $this->_attrEntitiesRegex =
            '/&(?:'.
            // hex
            '[#]x([a-fA-F0-9]+);?|'.
            // dec
            '[#]0*(\d+);?|'.
            // string (mandatory semicolon)
            // NB: order matters: match semicolon preferentially
            '([A-Za-z_:][A-Za-z0-9.\-_:]*);|'.
            // string (optional semicolon)
            // don't match if trailing is equals or alphanumeric (URL
            // like)
            "($semi_optional)(?![=;A-Za-z0-9])".
            ')/';

    }

    /**
     * Substitute entities with the parsed equivalents.  Use this on
     * textual data in an HTML document (as opposed to attributes.)
     *
     * @param string $string String to have entities parsed.
     * @return string Parsed string.
     */
    public function substituteTextEntities($string)
    {
        return preg_replace_callback(
            $this->_textEntitiesRegex,
            array($this, 'entityCallback'),
            $string
        );
    }

    /**
     * Substitute entities with the parsed equivalents.  Use this on
     * attribute contents in documents.
     *
     * @param string $string String to have entities parsed.
     * @return string Parsed string.
     */
    public function substituteAttrEntities($string)
    {
        return preg_replace_callback(
            $this->_attrEntitiesRegex,
            array($this, 'entityCallback'),
            $string
        );
    }

    /**
     * Callback function for substituteNonSpecialEntities() that does the work.
     *
     * @param array $matches  PCRE matches array, with 0 the entire match, and
     *                  either index 1, 2 or 3 set with a hex value, dec value,
     *                  or string (respectively).
     * @return string Replacement string.
     */

    protected function entityCallback($matches)
    {
        $entity = $matches[0];
        $hex_part = @$matches[1];
        $dec_part = @$matches[2];
        $named_part = empty($matches[3]) ? @$matches[4] : $matches[3];
        if ($hex_part !== NULL && $hex_part !== "") {
            return HTMLPurifier_Encoder::unichr(hexdec($hex_part));
        } elseif ($dec_part !== NULL && $dec_part !== "") {
            return HTMLPurifier_Encoder::unichr((int) $dec_part);
        } else {
            if (!$this->_entity_lookup) {
                $this->_entity_lookup = HTMLPurifier_EntityLookup::instance();
            }
            if (isset($this->_entity_lookup->table[$named_part])) {
                return $this->_entity_lookup->table[$named_part];
            } else {
                // exact match didn't match anything, so test if
                // any of the semicolon optional match the prefix.
                // Test that this is an EXACT match is important to
                // prevent infinite loop
                if (!empty($matches[3])) {
                    return preg_replace_callback(
                        $this->_semiOptionalPrefixRegex,
                        array($this, 'entityCallback'),
                        $entity
                    );
                }
                return $entity;
            }
        }
    }

    // LEGACY CODE BELOW

    /**
     * Callback regex string for parsing entities.
     * @type string
     */
    protected $_substituteEntitiesRegex =
        '/&(?:[#]x([a-fA-F0-9]+)|[#]0*(\d+)|([A-Za-z_:][A-Za-z0-9.\-_:]*));?/';
        //     1. hex             2. dec      3. string (XML style)

    /**
     * Decimal to parsed string conversion table for special entities.
     * @type array
     */
    protected $_special_dec2str =
            array(
                    34 => '"',
                    38 => '&',
                    39 => "'",
                    60 => '<',
                    62 => '>'
            );

    /**
     * Stripped entity names to decimal conversion table for special entities.
     * @type array
     */
    protected $_special_ent2dec =
            array(
                    'quot' => 34,
                    'amp'  => 38,
                    'lt'   => 60,
                    'gt'   => 62
            );

    /**
     * Substitutes non-special entities with their parsed equivalents. Since
     * running this whenever you have parsed character is t3h 5uck, we run
     * it before everything else.
     *
     * @param string $string String to have non-special entities parsed.
     * @return string Parsed string.
     */
    public function substituteNonSpecialEntities($string)
    {
        // it will try to detect missing semicolons, but don't rely on it
        return preg_replace_callback(
            $this->_substituteEntitiesRegex,
            array($this, 'nonSpecialEntityCallback'),
            $string
        );
    }

    /**
     * Callback function for substituteNonSpecialEntities() that does the work.
     *
     * @param array $matches  PCRE matches array, with 0 the entire match, and
     *                  either index 1, 2 or 3 set with a hex value, dec value,
     *                  or string (respectively).
     * @return string Replacement string.
     */

    protected function nonSpecialEntityCallback($matches)
    {
        // replaces all but big five
        $entity = $matches[0];
        $is_num = (@$matches[0][1] === '#');
        if ($is_num) {
            $is_hex = (@$entity[2] === 'x');
            $code = $is_hex ? hexdec($matches[1]) : (int) $matches[2];
            // abort for special characters
            if (isset($this->_special_dec2str[$code])) {
                return $entity;
            }
            return HTMLPurifier_Encoder::unichr($code);
        } else {
            if (isset($this->_special_ent2dec[$matches[3]])) {
                return $entity;
            }
            if (!$this->_entity_lookup) {
                $this->_entity_lookup = HTMLPurifier_EntityLookup::instance();
            }
            if (isset($this->_entity_lookup->table[$matches[3]])) {
                return $this->_entity_lookup->table[$matches[3]];
            } else {
                return $entity;
            }
        }
    }

    /**
     * Substitutes only special entities with their parsed equivalents.
     *
     * @notice We try to avoid calling this function because otherwise, it
     * would have to be called a lot (for every parsed section).
     *
     * @param string $string String to have non-special entities parsed.
     * @return string Parsed string.
     */
    public function substituteSpecialEntities($string)
    {
        return preg_replace_callback(
            $this->_substituteEntitiesRegex,
            array($this, 'specialEntityCallback'),
            $string
        );
    }

    /**
     * Callback function for substituteSpecialEntities() that does the work.
     *
     * This callback has same syntax as nonSpecialEntityCallback().
     *
     * @param array $matches  PCRE-style matches array, with 0 the entire match, and
     *                  either index 1, 2 or 3 set with a hex value, dec value,
     *                  or string (respectively).
     * @return string Replacement string.
     */
    protected function specialEntityCallback($matches)
    {
        $entity = $matches[0];
        $is_num = (@$matches[0][1] === '#');
        if ($is_num) {
            $is_hex = (@$entity[2] === 'x');
            $int = $is_hex ? hexdec($matches[1]) : (int) $matches[2];
            return isset($this->_special_dec2str[$int]) ?
                $this->_special_dec2str[$int] :
                $entity;
        } else {
            return isset($this->_special_ent2dec[$matches[3]]) ?
                $this->_special_dec2str[$this->_special_ent2dec[$matches[3]]] :
                $entity;
        }
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Exception type for HTMLPurifier_VarParser
 */
class HTMLPurifier_VarParserException extends HTMLPurifier_Exception
{

}

// vim: et sw=4 sts=4
<?php

/**
 * Class that handles operations involving percent-encoding in URIs.
 *
 * @warning
 *      Be careful when reusing instances of PercentEncoder. The object
 *      you use for normalize() SHOULD NOT be used for encode(), or
 *      vice-versa.
 */
class HTMLPurifier_PercentEncoder
{

    /**
     * Reserved characters to preserve when using encode().
     * @type array
     */
    protected $preserve = array();

    /**
     * String of characters that should be preserved while using encode().
     * @param bool $preserve
     */
    public function __construct($preserve = false)
    {
        // unreserved letters, ought to const-ify
        for ($i = 48; $i <= 57; $i++) { // digits
            $this->preserve[$i] = true;
        }
        for ($i = 65; $i <= 90; $i++) { // upper-case
            $this->preserve[$i] = true;
        }
        for ($i = 97; $i <= 122; $i++) { // lower-case
            $this->preserve[$i] = true;
        }
        $this->preserve[45] = true; // Dash         -
        $this->preserve[46] = true; // Period       .
        $this->preserve[95] = true; // Underscore   _
        $this->preserve[126]= true; // Tilde        ~

        // extra letters not to escape
        if ($preserve !== false) {
            for ($i = 0, $c = strlen($preserve); $i < $c; $i++) {
                $this->preserve[ord($preserve[$i])] = true;
            }
        }
    }

    /**
     * Our replacement for urlencode, it encodes all non-reserved characters,
     * as well as any extra characters that were instructed to be preserved.
     * @note
     *      Assumes that the string has already been normalized, making any
     *      and all percent escape sequences valid. Percents will not be
     *      re-escaped, regardless of their status in $preserve
     * @param string $string String to be encoded
     * @return string Encoded string.
     */
    public function encode($string)
    {
        $ret = '';
        for ($i = 0, $c = strlen($string); $i < $c; $i++) {
            if ($string[$i] !== '%' && !isset($this->preserve[$int = ord($string[$i])])) {
                $ret .= '%' . sprintf('%02X', $int);
            } else {
                $ret .= $string[$i];
            }
        }
        return $ret;
    }

    /**
     * Fix up percent-encoding by decoding unreserved characters and normalizing.
     * @warning This function is affected by $preserve, even though the
     *          usual desired behavior is for this not to preserve those
     *          characters. Be careful when reusing instances of PercentEncoder!
     * @param string $string String to normalize
     * @return string
     */
    public function normalize($string)
    {
        if ($string == '') {
            return '';
        }
        $parts = explode('%', $string);
        $ret = array_shift($parts);
        foreach ($parts as $part) {
            $length = strlen($part);
            if ($length < 2) {
                $ret .= '%25' . $part;
                continue;
            }
            $encoding = substr($part, 0, 2);
            $text     = substr($part, 2);
            if (!ctype_xdigit($encoding)) {
                $ret .= '%25' . $part;
                continue;
            }
            $int = hexdec($encoding);
            if (isset($this->preserve[$int])) {
                $ret .= chr($int) . $text;
                continue;
            }
            $encoding = strtoupper($encoding);
            $ret .= '%' . $encoding . $text;
        }
        return $ret;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Represents a measurable length, with a string numeric magnitude
 * and a unit. This object is immutable.
 */
class HTMLPurifier_Length
{

    /**
     * String numeric magnitude.
     * @type string
     */
    protected $n;

    /**
     * String unit. False is permitted if $n = 0.
     * @type string|bool
     */
    protected $unit;

    /**
     * Whether or not this length is valid. Null if not calculated yet.
     * @type bool
     */
    protected $isValid;

    /**
     * Array Lookup array of units recognized by CSS 3
     * @type array
     */
    protected static $allowedUnits = array(
        'em' => true, 'ex' => true, 'px' => true, 'in' => true,
        'cm' => true, 'mm' => true, 'pt' => true, 'pc' => true,
        'ch' => true, 'rem' => true, 'vw' => true, 'vh' => true,
        'vmin' => true, 'vmax' => true
    );

    /**
     * @param string $n Magnitude
     * @param bool|string $u Unit
     */
    public function __construct($n = '0', $u = false)
    {
        $this->n = (string) $n;
        $this->unit = $u !== false ? (string) $u : false;
    }

    /**
     * @param string $s Unit string, like '2em' or '3.4in'
     * @return HTMLPurifier_Length
     * @warning Does not perform validation.
     */
    public static function make($s)
    {
        if ($s instanceof HTMLPurifier_Length) {
            return $s;
        }
        $n_length = strspn($s, '1234567890.+-');
        $n = substr($s, 0, $n_length);
        $unit = substr($s, $n_length);
        if ($unit === '') {
            $unit = false;
        }
        return new HTMLPurifier_Length($n, $unit);
    }

    /**
     * Validates the number and unit.
     * @return bool
     */
    protected function validate()
    {
        // Special case:
        if ($this->n === '+0' || $this->n === '-0') {
            $this->n = '0';
        }
        if ($this->n === '0' && $this->unit === false) {
            return true;
        }
        if (!ctype_lower($this->unit)) {
            $this->unit = strtolower($this->unit);
        }
        if (!isset(HTMLPurifier_Length::$allowedUnits[$this->unit])) {
            return false;
        }
        // Hack:
        $def = new HTMLPurifier_AttrDef_CSS_Number();
        $result = $def->validate($this->n, false, false);
        if ($result === false) {
            return false;
        }
        $this->n = $result;
        return true;
    }

    /**
     * Returns string representation of number.
     * @return string
     */
    public function toString()
    {
        if (!$this->isValid()) {
            return false;
        }
        return $this->n . $this->unit;
    }

    /**
     * Retrieves string numeric magnitude.
     * @return string
     */
    public function getN()
    {
        return $this->n;
    }

    /**
     * Retrieves string unit.
     * @return string
     */
    public function getUnit()
    {
        return $this->unit;
    }

    /**
     * Returns true if this length unit is valid.
     * @return bool
     */
    public function isValid()
    {
        if ($this->isValid === null) {
            $this->isValid = $this->validate();
        }
        return $this->isValid;
    }

    /**
     * Compares two lengths, and returns 1 if greater, -1 if less and 0 if equal.
     * @param HTMLPurifier_Length $l
     * @return int
     * @warning If both values are too large or small, this calculation will
     *          not work properly
     */
    public function compareTo($l)
    {
        if ($l === false) {
            return false;
        }
        if ($l->unit !== $this->unit) {
            $converter = new HTMLPurifier_UnitConverter();
            $l = $converter->convert($l, $this->unit);
            if ($l === false) {
                return false;
            }
        }
        return $this->n - $l->n;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Factory for token generation.
 *
 * @note Doing some benchmarking indicates that the new operator is much
 *       slower than the clone operator (even discounting the cost of the
 *       constructor).  This class is for that optimization.
 *       Other then that, there's not much point as we don't
 *       maintain parallel HTMLPurifier_Token hierarchies (the main reason why
 *       you'd want to use an abstract factory).
 * @todo Port DirectLex to use this
 */
class HTMLPurifier_TokenFactory
{
    // p stands for prototype

    /**
     * @type HTMLPurifier_Token_Start
     */
    private $p_start;

    /**
     * @type HTMLPurifier_Token_End
     */
    private $p_end;

    /**
     * @type HTMLPurifier_Token_Empty
     */
    private $p_empty;

    /**
     * @type HTMLPurifier_Token_Text
     */
    private $p_text;

    /**
     * @type HTMLPurifier_Token_Comment
     */
    private $p_comment;

    /**
     * Generates blank prototypes for cloning.
     */
    public function __construct()
    {
        $this->p_start = new HTMLPurifier_Token_Start('', array());
        $this->p_end = new HTMLPurifier_Token_End('');
        $this->p_empty = new HTMLPurifier_Token_Empty('', array());
        $this->p_text = new HTMLPurifier_Token_Text('');
        $this->p_comment = new HTMLPurifier_Token_Comment('');
    }

    /**
     * Creates a HTMLPurifier_Token_Start.
     * @param string $name Tag name
     * @param array $attr Associative array of attributes
     * @return HTMLPurifier_Token_Start Generated HTMLPurifier_Token_Start
     */
    public function createStart($name, $attr = array())
    {
        $p = clone $this->p_start;
        $p->__construct($name, $attr);
        return $p;
    }

    /**
     * Creates a HTMLPurifier_Token_End.
     * @param string $name Tag name
     * @return HTMLPurifier_Token_End Generated HTMLPurifier_Token_End
     */
    public function createEnd($name)
    {
        $p = clone $this->p_end;
        $p->__construct($name);
        return $p;
    }

    /**
     * Creates a HTMLPurifier_Token_Empty.
     * @param string $name Tag name
     * @param array $attr Associative array of attributes
     * @return HTMLPurifier_Token_Empty Generated HTMLPurifier_Token_Empty
     */
    public function createEmpty($name, $attr = array())
    {
        $p = clone $this->p_empty;
        $p->__construct($name, $attr);
        return $p;
    }

    /**
     * Creates a HTMLPurifier_Token_Text.
     * @param string $data Data of text token
     * @return HTMLPurifier_Token_Text Generated HTMLPurifier_Token_Text
     */
    public function createText($data)
    {
        $p = clone $this->p_text;
        $p->__construct($data);
        return $p;
    }

    /**
     * Creates a HTMLPurifier_Token_Comment.
     * @param string $data Data of comment token
     * @return HTMLPurifier_Token_Comment Generated HTMLPurifier_Token_Comment
     */
    public function createComment($data)
    {
        $p = clone $this->p_comment;
        $p->__construct($data);
        return $p;
    }
}

// vim: et sw=4 sts=4
<?php

class HTMLPurifier_Filter_YouTube extends HTMLPurifier_Filter
{

    /**
     * @type string
     */
    public $name = 'YouTube';

    /**
     * @param string $html
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return string
     */
    public function preFilter($html, $config, $context)
    {
        $pre_regex = '#<object[^>]+>.+?' .
            '(?:http:)?//www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+).+?</object>#s';
        $pre_replace = '<span class="youtube-embed">\1</span>';
        return preg_replace($pre_regex, $pre_replace, $html);
    }

    /**
     * @param string $html
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return string
     */
    public function postFilter($html, $config, $context)
    {
        $post_regex = '#<span class="youtube-embed">((?:v|cp)/[A-Za-z0-9\-_=]+)</span>#';
        return preg_replace_callback($post_regex, array($this, 'postFilterCallback'), $html);
    }

    /**
     * @param $url
     * @return string
     */
    protected function armorUrl($url)
    {
        return str_replace('--', '-&#45;', $url);
    }

    /**
     * @param array $matches
     * @return string
     */
    protected function postFilterCallback($matches)
    {
        $url = $this->armorUrl($matches[1]);
        return '<object width="425" height="350" type="application/x-shockwave-flash" ' .
        'data="//www.youtube.com/' . $url . '">' .
        '<param name="movie" value="//www.youtube.com/' . $url . '"></param>' .
        '<!--[if IE]>' .
        '<embed src="//www.youtube.com/' . $url . '"' .
        'type="application/x-shockwave-flash"' .
        'wmode="transparent" width="425" height="350" />' .
        '<![endif]-->' .
        '</object>';
    }
}

// vim: et sw=4 sts=4
<?php

// why is this a top level function? Because PHP 5.2.0 doesn't seem to
// understand how to interpret this filter if it's a static method.
// It's all really silly, but if we go this route it might be reasonable
// to coalesce all of these methods into one.
function htmlpurifier_filter_extractstyleblocks_muteerrorhandler()
{
}

/**
 * This filter extracts <style> blocks from input HTML, cleans them up
 * using CSSTidy, and then places them in $purifier->context->get('StyleBlocks')
 * so they can be used elsewhere in the document.
 *
 * @note
 *      See tests/HTMLPurifier/Filter/ExtractStyleBlocksTest.php for
 *      sample usage.
 *
 * @note
 *      This filter can also be used on stylesheets not included in the
 *      document--something purists would probably prefer. Just directly
 *      call HTMLPurifier_Filter_ExtractStyleBlocks->cleanCSS()
 */
class HTMLPurifier_Filter_ExtractStyleBlocks extends HTMLPurifier_Filter
{
    /**
     * @type string
     */
    public $name = 'ExtractStyleBlocks';

    /**
     * @type array
     */
    private $_styleMatches = array();

    /**
     * @type csstidy
     */
    private $_tidy;

    /**
     * @type HTMLPurifier_AttrDef_HTML_ID
     */
    private $_id_attrdef;

    /**
     * @type HTMLPurifier_AttrDef_CSS_Ident
     */
    private $_class_attrdef;

    /**
     * @type HTMLPurifier_AttrDef_Enum
     */
    private $_enum_attrdef;

    public function __construct()
    {
        $this->_tidy = new csstidy();
        $this->_tidy->set_cfg('lowercase_s', false);
        $this->_id_attrdef = new HTMLPurifier_AttrDef_HTML_ID(true);
        $this->_class_attrdef = new HTMLPurifier_AttrDef_CSS_Ident();
        $this->_enum_attrdef = new HTMLPurifier_AttrDef_Enum(
            array(
                'first-child',
                'link',
                'visited',
                'active',
                'hover',
                'focus'
            )
        );
    }

    /**
     * Save the contents of CSS blocks to style matches
     * @param array $matches preg_replace style $matches array
     */
    protected function styleCallback($matches)
    {
        $this->_styleMatches[] = $matches[1];
    }

    /**
     * Removes inline <style> tags from HTML, saves them for later use
     * @param string $html
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return string
     * @todo Extend to indicate non-text/css style blocks
     */
    public function preFilter($html, $config, $context)
    {
        $tidy = $config->get('Filter.ExtractStyleBlocks.TidyImpl');
        if ($tidy !== null) {
            $this->_tidy = $tidy;
        }
        // NB: this must be NON-greedy because if we have
        // <style>foo</style>  <style>bar</style>
        // we must not grab foo</style>  <style>bar
        $html = preg_replace_callback('#<style(?:\s.*)?>(.*)<\/style>#isU', array($this, 'styleCallback'), $html);
        $style_blocks = $this->_styleMatches;
        $this->_styleMatches = array(); // reset
        $context->register('StyleBlocks', $style_blocks); // $context must not be reused
        if ($this->_tidy) {
            foreach ($style_blocks as &$style) {
                $style = $this->cleanCSS($style, $config, $context);
            }
        }
        return $html;
    }

    /**
     * Takes CSS (the stuff found in <style>) and cleans it.
     * @warning Requires CSSTidy <http://csstidy.sourceforge.net/>
     * @param string $css CSS styling to clean
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @throws HTMLPurifier_Exception
     * @return string Cleaned CSS
     */
    public function cleanCSS($css, $config, $context)
    {
        // prepare scope
        $scope = $config->get('Filter.ExtractStyleBlocks.Scope');
        if ($scope !== null) {
            $scopes = array_map('trim', explode(',', $scope));
        } else {
            $scopes = array();
        }
        // remove comments from CSS
        $css = trim($css);
        if (strncmp('<!--', $css, 4) === 0) {
            $css = substr($css, 4);
        }
        if (strlen($css) > 3 && substr($css, -3) == '-->') {
            $css = substr($css, 0, -3);
        }
        $css = trim($css);
        set_error_handler('htmlpurifier_filter_extractstyleblocks_muteerrorhandler');
        $this->_tidy->parse($css);
        restore_error_handler();
        $css_definition = $config->getDefinition('CSS');
        $html_definition = $config->getDefinition('HTML');
        $new_css = array();
        foreach ($this->_tidy->css as $k => $decls) {
            // $decls are all CSS declarations inside an @ selector
            $new_decls = array();
            foreach ($decls as $selector => $style) {
                $selector = trim($selector);
                if ($selector === '') {
                    continue;
                } // should not happen
                // Parse the selector
                // Here is the relevant part of the CSS grammar:
                //
                // ruleset
                //   : selector [ ',' S* selector ]* '{' ...
                // selector
                //   : simple_selector [ combinator selector | S+ [ combinator? selector ]? ]?
                // combinator
                //   : '+' S*
                //   : '>' S*
                // simple_selector
                //   : element_name [ HASH | class | attrib | pseudo ]*
                //   | [ HASH | class | attrib | pseudo ]+
                // element_name
                //   : IDENT | '*'
                //   ;
                // class
                //   : '.' IDENT
                //   ;
                // attrib
                //   : '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S*
                //     [ IDENT | STRING ] S* ]? ']'
                //   ;
                // pseudo
                //   : ':' [ IDENT | FUNCTION S* [IDENT S*]? ')' ]
                //   ;
                //
                // For reference, here are the relevant tokens:
                //
                // HASH         #{name}
                // IDENT        {ident}
                // INCLUDES     ==
                // DASHMATCH    |=
                // STRING       {string}
                // FUNCTION     {ident}\(
                //
                // And the lexical scanner tokens
                //
                // name         {nmchar}+
                // nmchar       [_a-z0-9-]|{nonascii}|{escape}
                // nonascii     [\240-\377]
                // escape       {unicode}|\\[^\r\n\f0-9a-f]
                // unicode      \\{h}}{1,6}(\r\n|[ \t\r\n\f])?
                // ident        -?{nmstart}{nmchar*}
                // nmstart      [_a-z]|{nonascii}|{escape}
                // string       {string1}|{string2}
                // string1      \"([^\n\r\f\\"]|\\{nl}|{escape})*\"
                // string2      \'([^\n\r\f\\"]|\\{nl}|{escape})*\'
                //
                // We'll implement a subset (in order to reduce attack
                // surface); in particular:
                //
                //      - No Unicode support
                //      - No escapes support
                //      - No string support (by proxy no attrib support)
                //      - element_name is matched against allowed
                //        elements (some people might find this
                //        annoying...)
                //      - Pseudo-elements one of :first-child, :link,
                //        :visited, :active, :hover, :focus

                // handle ruleset
                $selectors = array_map('trim', explode(',', $selector));
                $new_selectors = array();
                foreach ($selectors as $sel) {
                    // split on +, > and spaces
                    $basic_selectors = preg_split('/\s*([+> ])\s*/', $sel, -1, PREG_SPLIT_DELIM_CAPTURE);
                    // even indices are chunks, odd indices are
                    // delimiters
                    $nsel = null;
                    $delim = null; // guaranteed to be non-null after
                    // two loop iterations
                    for ($i = 0, $c = count($basic_selectors); $i < $c; $i++) {
                        $x = $basic_selectors[$i];
                        if ($i % 2) {
                            // delimiter
                            if ($x === ' ') {
                                $delim = ' ';
                            } else {
                                $delim = ' ' . $x . ' ';
                            }
                        } else {
                            // simple selector
                            $components = preg_split('/([#.:])/', $x, -1, PREG_SPLIT_DELIM_CAPTURE);
                            $sdelim = null;
                            $nx = null;
                            for ($j = 0, $cc = count($components); $j < $cc; $j++) {
                                $y = $components[$j];
                                if ($j === 0) {
                                    if ($y === '*' || isset($html_definition->info[$y = strtolower($y)])) {
                                        $nx = $y;
                                    } else {
                                        // $nx stays null; this matters
                                        // if we don't manage to find
                                        // any valid selector content,
                                        // in which case we ignore the
                                        // outer $delim
                                    }
                                } elseif ($j % 2) {
                                    // set delimiter
                                    $sdelim = $y;
                                } else {
                                    $attrdef = null;
                                    if ($sdelim === '#') {
                                        $attrdef = $this->_id_attrdef;
                                    } elseif ($sdelim === '.') {
                                        $attrdef = $this->_class_attrdef;
                                    } elseif ($sdelim === ':') {
                                        $attrdef = $this->_enum_attrdef;
                                    } else {
                                        throw new HTMLPurifier_Exception('broken invariant sdelim and preg_split');
                                    }
                                    $r = $attrdef->validate($y, $config, $context);
                                    if ($r !== false) {
                                        if ($r !== true) {
                                            $y = $r;
                                        }
                                        if ($nx === null) {
                                            $nx = '';
                                        }
                                        $nx .= $sdelim . $y;
                                    }
                                }
                            }
                            if ($nx !== null) {
                                if ($nsel === null) {
                                    $nsel = $nx;
                                } else {
                                    $nsel .= $delim . $nx;
                                }
                            } else {
                                // delimiters to the left of invalid
                                // basic selector ignored
                            }
                        }
                    }
                    if ($nsel !== null) {
                        if (!empty($scopes)) {
                            foreach ($scopes as $s) {
                                $new_selectors[] = "$s $nsel";
                            }
                        } else {
                            $new_selectors[] = $nsel;
                        }
                    }
                }
                if (empty($new_selectors)) {
                    continue;
                }
                $selector = implode(', ', $new_selectors);
                foreach ($style as $name => $value) {
                    if (!isset($css_definition->info[$name])) {
                        unset($style[$name]);
                        continue;
                    }
                    $def = $css_definition->info[$name];
                    $ret = $def->validate($value, $config, $context);
                    if ($ret === false) {
                        unset($style[$name]);
                    } else {
                        $style[$name] = $ret;
                    }
                }
                $new_decls[$selector] = $style;
            }
            $new_css[$k] = $new_decls;
        }
        // remove stuff that shouldn't be used, could be reenabled
        // after security risks are analyzed
        $this->_tidy->css = $new_css;
        $this->_tidy->import = array();
        $this->_tidy->charset = null;
        $this->_tidy->namespace = null;
        $css = $this->_tidy->print->plain();
        // we are going to escape any special characters <>& to ensure
        // that no funny business occurs (i.e. </style> in a font-family prop).
        if ($config->get('Filter.ExtractStyleBlocks.Escaping')) {
            $css = str_replace(
                array('<', '>', '&'),
                array('\3C ', '\3E ', '\26 '),
                $css
            );
        }
        return $css;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Concrete element node class.
 */
class HTMLPurifier_Node_Element extends HTMLPurifier_Node
{
    /**
     * The lower-case name of the tag, like 'a', 'b' or 'blockquote'.
     *
     * @note Strictly speaking, XML tags are case sensitive, so we shouldn't
     * be lower-casing them, but these tokens cater to HTML tags, which are
     * insensitive.
     * @type string
     */
    public $name;

    /**
     * Associative array of the node's attributes.
     * @type array
     */
    public $attr = array();

    /**
     * List of child elements.
     * @type array
     */
    public $children = array();

    /**
     * Does this use the <a></a> form or the </a> form, i.e.
     * is it a pair of start/end tokens or an empty token.
     * @bool
     */
    public $empty = false;

    public $endCol = null, $endLine = null, $endArmor = array();

    public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array()) {
        $this->name = $name;
        $this->attr = $attr;
        $this->line = $line;
        $this->col = $col;
        $this->armor = $armor;
    }

    public function toTokenPair() {
        // XXX inefficiency here, normalization is not necessary
        if ($this->empty) {
            return array(new HTMLPurifier_Token_Empty($this->name, $this->attr, $this->line, $this->col, $this->armor), null);
        } else {
            $start = new HTMLPurifier_Token_Start($this->name, $this->attr, $this->line, $this->col, $this->armor);
            $end = new HTMLPurifier_Token_End($this->name, array(), $this->endLine, $this->endCol, $this->endArmor);
            //$end->start = $start;
            return array($start, $end);
        }
    }
}

<?php

/**
 * Concrete comment node class.
 */
class HTMLPurifier_Node_Comment extends HTMLPurifier_Node
{
    /**
     * Character data within comment.
     * @type string
     */
    public $data;

    /**
     * @type bool
     */
    public $is_whitespace = true;

    /**
     * Transparent constructor.
     *
     * @param string $data String comment data.
     * @param int $line
     * @param int $col
     */
    public function __construct($data, $line = null, $col = null)
    {
        $this->data = $data;
        $this->line = $line;
        $this->col = $col;
    }

    public function toTokenPair() {
        return array(new HTMLPurifier_Token_Comment($this->data, $this->line, $this->col), null);
    }
}
<?php

/**
 * Concrete text token class.
 *
 * Text tokens comprise of regular parsed character data (PCDATA) and raw
 * character data (from the CDATA sections). Internally, their
 * data is parsed with all entities expanded. Surprisingly, the text token
 * does have a "tag name" called #PCDATA, which is how the DTD represents it
 * in permissible child nodes.
 */
class HTMLPurifier_Node_Text extends HTMLPurifier_Node
{

    /**
     * PCDATA tag name compatible with DTD, see
     * HTMLPurifier_ChildDef_Custom for details.
     * @type string
     */
    public $name = '#PCDATA';

    /**
     * @type string
     */
    public $data;
    /**< Parsed character data of text. */

    /**
     * @type bool
     */
    public $is_whitespace;

    /**< Bool indicating if node is whitespace. */

    /**
     * Constructor, accepts data and determines if it is whitespace.
     * @param string $data String parsed character data.
     * @param int $line
     * @param int $col
     */
    public function __construct($data, $is_whitespace, $line = null, $col = null)
    {
        $this->data = $data;
        $this->is_whitespace = $is_whitespace;
        $this->line = $line;
        $this->col = $col;
    }

    public function toTokenPair() {
        return array(new HTMLPurifier_Token_Text($this->data, $this->line, $this->col), null);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Parses a URI into the components and fragment identifier as specified
 * by RFC 3986.
 */
class HTMLPurifier_URIParser
{

    /**
     * Instance of HTMLPurifier_PercentEncoder to do normalization with.
     */
    protected $percentEncoder;

    public function __construct()
    {
        $this->percentEncoder = new HTMLPurifier_PercentEncoder();
    }

    /**
     * Parses a URI.
     * @param $uri string URI to parse
     * @return HTMLPurifier_URI representation of URI. This representation has
     *         not been validated yet and may not conform to RFC.
     */
    public function parse($uri)
    {
        $uri = $this->percentEncoder->normalize($uri);

        // Regexp is as per Appendix B.
        // Note that ["<>] are an addition to the RFC's recommended
        // characters, because they represent external delimeters.
        $r_URI = '!'.
            '(([a-zA-Z0-9\.\+\-]+):)?'. // 2. Scheme
            '(//([^/?#"<>]*))?'. // 4. Authority
            '([^?#"<>]*)'.       // 5. Path
            '(\?([^#"<>]*))?'.   // 7. Query
            '(#([^"<>]*))?'.     // 8. Fragment
            '!';

        $matches = array();
        $result = preg_match($r_URI, $uri, $matches);

        if (!$result) return false; // *really* invalid URI

        // seperate out parts
        $scheme     = !empty($matches[1]) ? $matches[2] : null;
        $authority  = !empty($matches[3]) ? $matches[4] : null;
        $path       = $matches[5]; // always present, can be empty
        $query      = !empty($matches[6]) ? $matches[7] : null;
        $fragment   = !empty($matches[8]) ? $matches[9] : null;

        // further parse authority
        if ($authority !== null) {
            $r_authority = "/^((.+?)@)?(\[[^\]]+\]|[^:]*)(:(\d*))?/";
            $matches = array();
            preg_match($r_authority, $authority, $matches);
            $userinfo   = !empty($matches[1]) ? $matches[2] : null;
            $host       = !empty($matches[3]) ? $matches[3] : '';
            $port       = !empty($matches[4]) ? (int) $matches[5] : null;
        } else {
            $port = $host = $userinfo = null;
        }

        return new HTMLPurifier_URI(
            $scheme, $userinfo, $host, $port, $path, $query, $fragment);
    }

}

// vim: et sw=4 sts=4
<?php

/**
 * Generates HTML from tokens.
 * @todo Refactor interface so that configuration/context is determined
 *       upon instantiation, no need for messy generateFromTokens() calls
 * @todo Make some of the more internal functions protected, and have
 *       unit tests work around that
 */
class HTMLPurifier_Generator
{

    /**
     * Whether or not generator should produce XML output.
     * @type bool
     */
    private $_xhtml = true;

    /**
     * :HACK: Whether or not generator should comment the insides of <script> tags.
     * @type bool
     */
    private $_scriptFix = false;

    /**
     * Cache of HTMLDefinition during HTML output to determine whether or
     * not attributes should be minimized.
     * @type HTMLPurifier_HTMLDefinition
     */
    private $_def;

    /**
     * Cache of %Output.SortAttr.
     * @type bool
     */
    private $_sortAttr;

    /**
     * Cache of %Output.FlashCompat.
     * @type bool
     */
    private $_flashCompat;

    /**
     * Cache of %Output.FixInnerHTML.
     * @type bool
     */
    private $_innerHTMLFix;

    /**
     * Stack for keeping track of object information when outputting IE
     * compatibility code.
     * @type array
     */
    private $_flashStack = array();

    /**
     * Configuration for the generator
     * @type HTMLPurifier_Config
     */
    protected $config;

    /**
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     */
    public function __construct($config, $context)
    {
        $this->config = $config;
        $this->_scriptFix = $config->get('Output.CommentScriptContents');
        $this->_innerHTMLFix = $config->get('Output.FixInnerHTML');
        $this->_sortAttr = $config->get('Output.SortAttr');
        $this->_flashCompat = $config->get('Output.FlashCompat');
        $this->_def = $config->getHTMLDefinition();
        $this->_xhtml = $this->_def->doctype->xml;
    }

    /**
     * Generates HTML from an array of tokens.
     * @param HTMLPurifier_Token[] $tokens Array of HTMLPurifier_Token
     * @return string Generated HTML
     */
    public function generateFromTokens($tokens)
    {
        if (!$tokens) {
            return '';
        }

        // Basic algorithm
        $html = '';
        for ($i = 0, $size = count($tokens); $i < $size; $i++) {
            if ($this->_scriptFix && $tokens[$i]->name === 'script'
                && $i + 2 < $size && $tokens[$i+2] instanceof HTMLPurifier_Token_End) {
                // script special case
                // the contents of the script block must be ONE token
                // for this to work.
                $html .= $this->generateFromToken($tokens[$i++]);
                $html .= $this->generateScriptFromToken($tokens[$i++]);
            }
            $html .= $this->generateFromToken($tokens[$i]);
        }

        // Tidy cleanup
        if (extension_loaded('tidy') && $this->config->get('Output.TidyFormat')) {
            $tidy = new Tidy;
            $tidy->parseString(
                $html,
                array(
                   'indent'=> true,
                   'output-xhtml' => $this->_xhtml,
                   'show-body-only' => true,
                   'indent-spaces' => 2,
                   'wrap' => 68,
                ),
                'utf8'
            );
            $tidy->cleanRepair();
            $html = (string) $tidy; // explicit cast necessary
        }

        // Normalize newlines to system defined value
        if ($this->config->get('Core.NormalizeNewlines')) {
            $nl = $this->config->get('Output.Newline');
            if ($nl === null) {
                $nl = PHP_EOL;
            }
            if ($nl !== "\n") {
                $html = str_replace("\n", $nl, $html);
            }
        }
        return $html;
    }

    /**
     * Generates HTML from a single token.
     * @param HTMLPurifier_Token $token HTMLPurifier_Token object.
     * @return string Generated HTML
     */
    public function generateFromToken($token)
    {
        if (!$token instanceof HTMLPurifier_Token) {
            trigger_error('Cannot generate HTML from non-HTMLPurifier_Token object', E_USER_WARNING);
            return '';

        } elseif ($token instanceof HTMLPurifier_Token_Start) {
            $attr = $this->generateAttributes($token->attr, $token->name);
            if ($this->_flashCompat) {
                if ($token->name == "object") {
                    $flash = new stdClass();
                    $flash->attr = $token->attr;
                    $flash->param = array();
                    $this->_flashStack[] = $flash;
                }
            }
            return '<' . $token->name . ($attr ? ' ' : '') . $attr . '>';

        } elseif ($token instanceof HTMLPurifier_Token_End) {
            $_extra = '';
            if ($this->_flashCompat) {
                if ($token->name == "object" && !empty($this->_flashStack)) {
                    // doesn't do anything for now
                }
            }
            return $_extra . '</' . $token->name . '>';

        } elseif ($token instanceof HTMLPurifier_Token_Empty) {
            if ($this->_flashCompat && $token->name == "param" && !empty($this->_flashStack)) {
                $this->_flashStack[count($this->_flashStack)-1]->param[$token->attr['name']] = $token->attr['value'];
            }
            $attr = $this->generateAttributes($token->attr, $token->name);
             return '<' . $token->name . ($attr ? ' ' : '') . $attr .
                ( $this->_xhtml ? ' /': '' ) // <br /> v. <br>
                . '>';

        } elseif ($token instanceof HTMLPurifier_Token_Text) {
            return $this->escape($token->data, ENT_NOQUOTES);

        } elseif ($token instanceof HTMLPurifier_Token_Comment) {
            return '<!--' . $token->data . '-->';
        } else {
            return '';

        }
    }

    /**
     * Special case processor for the contents of script tags
     * @param HTMLPurifier_Token $token HTMLPurifier_Token object.
     * @return string
     * @warning This runs into problems if there's already a literal
     *          --> somewhere inside the script contents.
     */
    public function generateScriptFromToken($token)
    {
        if (!$token instanceof HTMLPurifier_Token_Text) {
            return $this->generateFromToken($token);
        }
        // Thanks <http://lachy.id.au/log/2005/05/script-comments>
        $data = preg_replace('#//\s*$#', '', $token->data);
        return '<!--//--><![CDATA[//><!--' . "\n" . trim($data) . "\n" . '//--><!]]>';
    }

    /**
     * Generates attribute declarations from attribute array.
     * @note This does not include the leading or trailing space.
     * @param array $assoc_array_of_attributes Attribute array
     * @param string $element Name of element attributes are for, used to check
     *        attribute minimization.
     * @return string Generated HTML fragment for insertion.
     */
    public function generateAttributes($assoc_array_of_attributes, $element = '')
    {
        $html = '';
        if ($this->_sortAttr) {
            ksort($assoc_array_of_attributes);
        }
        foreach ($assoc_array_of_attributes as $key => $value) {
            if (!$this->_xhtml) {
                // Remove namespaced attributes
                if (strpos($key, ':') !== false) {
                    continue;
                }
                // Check if we should minimize the attribute: val="val" -> val
                if ($element && !empty($this->_def->info[$element]->attr[$key]->minimized)) {
                    $html .= $key . ' ';
                    continue;
                }
            }
            // Workaround for Internet Explorer innerHTML bug.
            // Essentially, Internet Explorer, when calculating
            // innerHTML, omits quotes if there are no instances of
            // angled brackets, quotes or spaces.  However, when parsing
            // HTML (for example, when you assign to innerHTML), it
            // treats backticks as quotes.  Thus,
            //      <img alt="``" />
            // becomes
            //      <img alt=`` />
            // becomes
            //      <img alt='' />
            // Fortunately, all we need to do is trigger an appropriate
            // quoting style, which we do by adding an extra space.
            // This also is consistent with the W3C spec, which states
            // that user agents may ignore leading or trailing
            // whitespace (in fact, most don't, at least for attributes
            // like alt, but an extra space at the end is barely
            // noticeable).  Still, we have a configuration knob for
            // this, since this transformation is not necesary if you
            // don't process user input with innerHTML or you don't plan
            // on supporting Internet Explorer.
            if ($this->_innerHTMLFix) {
                if (strpos($value, '`') !== false) {
                    // check if correct quoting style would not already be
                    // triggered
                    if (strcspn($value, '"\' <>') === strlen($value)) {
                        // protect!
                        $value .= ' ';
                    }
                }
            }
            $html .= $key.'="'.$this->escape($value).'" ';
        }
        return rtrim($html);
    }

    /**
     * Escapes raw text data.
     * @todo This really ought to be protected, but until we have a facility
     *       for properly generating HTML here w/o using tokens, it stays
     *       public.
     * @param string $string String data to escape for HTML.
     * @param int $quote Quoting style, like htmlspecialchars. ENT_NOQUOTES is
     *               permissible for non-attribute output.
     * @return string escaped data.
     */
    public function escape($string, $quote = null)
    {
        // Workaround for APC bug on Mac Leopard reported by sidepodcast
        // http://htmlpurifier.org/phorum/read.php?3,4823,4846
        if ($quote === null) {
            $quote = ENT_COMPAT;
        }
        return htmlspecialchars($string, $quote, 'UTF-8');
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Class for converting between different unit-lengths as specified by
 * CSS.
 */
class HTMLPurifier_UnitConverter
{

    const ENGLISH = 1;
    const METRIC = 2;
    const DIGITAL = 3;

    /**
     * Units information array. Units are grouped into measuring systems
     * (English, Metric), and are assigned an integer representing
     * the conversion factor between that unit and the smallest unit in
     * the system. Numeric indexes are actually magical constants that
     * encode conversion data from one system to the next, with a O(n^2)
     * constraint on memory (this is generally not a problem, since
     * the number of measuring systems is small.)
     */
    protected static $units = array(
        self::ENGLISH => array(
            'px' => 3, // This is as per CSS 2.1 and Firefox. Your mileage may vary
            'pt' => 4,
            'pc' => 48,
            'in' => 288,
            self::METRIC => array('pt', '0.352777778', 'mm'),
        ),
        self::METRIC => array(
            'mm' => 1,
            'cm' => 10,
            self::ENGLISH => array('mm', '2.83464567', 'pt'),
        ),
    );

    /**
     * Minimum bcmath precision for output.
     * @type int
     */
    protected $outputPrecision;

    /**
     * Bcmath precision for internal calculations.
     * @type int
     */
    protected $internalPrecision;

    /**
     * Whether or not BCMath is available.
     * @type bool
     */
    private $bcmath;

    public function __construct($output_precision = 4, $internal_precision = 10, $force_no_bcmath = false)
    {
        $this->outputPrecision = $output_precision;
        $this->internalPrecision = $internal_precision;
        $this->bcmath = !$force_no_bcmath && function_exists('bcmul');
    }

    /**
     * Converts a length object of one unit into another unit.
     * @param HTMLPurifier_Length $length
     *      Instance of HTMLPurifier_Length to convert. You must validate()
     *      it before passing it here!
     * @param string $to_unit
     *      Unit to convert to.
     * @return HTMLPurifier_Length|bool
     * @note
     *      About precision: This conversion function pays very special
     *      attention to the incoming precision of values and attempts
     *      to maintain a number of significant figure. Results are
     *      fairly accurate up to nine digits. Some caveats:
     *          - If a number is zero-padded as a result of this significant
     *            figure tracking, the zeroes will be eliminated.
     *          - If a number contains less than four sigfigs ($outputPrecision)
     *            and this causes some decimals to be excluded, those
     *            decimals will be added on.
     */
    public function convert($length, $to_unit)
    {
        if (!$length->isValid()) {
            return false;
        }

        $n = $length->getN();
        $unit = $length->getUnit();

        if ($n === '0' || $unit === false) {
            return new HTMLPurifier_Length('0', false);
        }

        $state = $dest_state = false;
        foreach (self::$units as $k => $x) {
            if (isset($x[$unit])) {
                $state = $k;
            }
            if (isset($x[$to_unit])) {
                $dest_state = $k;
            }
        }
        if (!$state || !$dest_state) {
            return false;
        }

        // Some calculations about the initial precision of the number;
        // this will be useful when we need to do final rounding.
        $sigfigs = $this->getSigFigs($n);
        if ($sigfigs < $this->outputPrecision) {
            $sigfigs = $this->outputPrecision;
        }

        // BCMath's internal precision deals only with decimals. Use
        // our default if the initial number has no decimals, or increase
        // it by how ever many decimals, thus, the number of guard digits
        // will always be greater than or equal to internalPrecision.
        $log = (int)floor(log(abs($n), 10));
        $cp = ($log < 0) ? $this->internalPrecision - $log : $this->internalPrecision; // internal precision

        for ($i = 0; $i < 2; $i++) {

            // Determine what unit IN THIS SYSTEM we need to convert to
            if ($dest_state === $state) {
                // Simple conversion
                $dest_unit = $to_unit;
            } else {
                // Convert to the smallest unit, pending a system shift
                $dest_unit = self::$units[$state][$dest_state][0];
            }

            // Do the conversion if necessary
            if ($dest_unit !== $unit) {
                $factor = $this->div(self::$units[$state][$unit], self::$units[$state][$dest_unit], $cp);
                $n = $this->mul($n, $factor, $cp);
                $unit = $dest_unit;
            }

            // Output was zero, so bail out early. Shouldn't ever happen.
            if ($n === '') {
                $n = '0';
                $unit = $to_unit;
                break;
            }

            // It was a simple conversion, so bail out
            if ($dest_state === $state) {
                break;
            }

            if ($i !== 0) {
                // Conversion failed! Apparently, the system we forwarded
                // to didn't have this unit. This should never happen!
                return false;
            }

            // Pre-condition: $i == 0

            // Perform conversion to next system of units
            $n = $this->mul($n, self::$units[$state][$dest_state][1], $cp);
            $unit = self::$units[$state][$dest_state][2];
            $state = $dest_state;

            // One more loop around to convert the unit in the new system.

        }

        // Post-condition: $unit == $to_unit
        if ($unit !== $to_unit) {
            return false;
        }

        // Useful for debugging:
        //echo "<pre>n";
        //echo "$n\nsigfigs = $sigfigs\nnew_log = $new_log\nlog = $log\nrp = $rp\n</pre>\n";

        $n = $this->round($n, $sigfigs);
        if (strpos($n, '.') !== false) {
            $n = rtrim($n, '0');
        }
        $n = rtrim($n, '.');

        return new HTMLPurifier_Length($n, $unit);
    }

    /**
     * Returns the number of significant figures in a string number.
     * @param string $n Decimal number
     * @return int number of sigfigs
     */
    public function getSigFigs($n)
    {
        $n = ltrim($n, '0+-');
        $dp = strpos($n, '.'); // decimal position
        if ($dp === false) {
            $sigfigs = strlen(rtrim($n, '0'));
        } else {
            $sigfigs = strlen(ltrim($n, '0.')); // eliminate extra decimal character
            if ($dp !== 0) {
                $sigfigs--;
            }
        }
        return $sigfigs;
    }

    /**
     * Adds two numbers, using arbitrary precision when available.
     * @param string $s1
     * @param string $s2
     * @param int $scale
     * @return string
     */
    private function add($s1, $s2, $scale)
    {
        if ($this->bcmath) {
            return bcadd($s1, $s2, $scale);
        } else {
            return $this->scale((float)$s1 + (float)$s2, $scale);
        }
    }

    /**
     * Multiples two numbers, using arbitrary precision when available.
     * @param string $s1
     * @param string $s2
     * @param int $scale
     * @return string
     */
    private function mul($s1, $s2, $scale)
    {
        if ($this->bcmath) {
            return bcmul($s1, $s2, $scale);
        } else {
            return $this->scale((float)$s1 * (float)$s2, $scale);
        }
    }

    /**
     * Divides two numbers, using arbitrary precision when available.
     * @param string $s1
     * @param string $s2
     * @param int $scale
     * @return string
     */
    private function div($s1, $s2, $scale)
    {
        if ($this->bcmath) {
            return bcdiv($s1, $s2, $scale);
        } else {
            return $this->scale((float)$s1 / (float)$s2, $scale);
        }
    }

    /**
     * Rounds a number according to the number of sigfigs it should have,
     * using arbitrary precision when available.
     * @param float $n
     * @param int $sigfigs
     * @return string
     */
    private function round($n, $sigfigs)
    {
        $new_log = (int)floor(log(abs($n), 10)); // Number of digits left of decimal - 1
        $rp = $sigfigs - $new_log - 1; // Number of decimal places needed
        $neg = $n < 0 ? '-' : ''; // Negative sign
        if ($this->bcmath) {
            if ($rp >= 0) {
                $n = bcadd($n, $neg . '0.' . str_repeat('0', $rp) . '5', $rp + 1);
                $n = bcdiv($n, '1', $rp);
            } else {
                // This algorithm partially depends on the standardized
                // form of numbers that comes out of bcmath.
                $n = bcadd($n, $neg . '5' . str_repeat('0', $new_log - $sigfigs), 0);
                $n = substr($n, 0, $sigfigs + strlen($neg)) . str_repeat('0', $new_log - $sigfigs + 1);
            }
            return $n;
        } else {
            return $this->scale(round($n, $sigfigs - $new_log - 1), $rp + 1);
        }
    }

    /**
     * Scales a float to $scale digits right of decimal point, like BCMath.
     * @param float $r
     * @param int $scale
     * @return string
     */
    private function scale($r, $scale)
    {
        if ($scale < 0) {
            // The f sprintf type doesn't support negative numbers, so we
            // need to cludge things manually. First get the string.
            $r = sprintf('%.0f', (float)$r);
            // Due to floating point precision loss, $r will more than likely
            // look something like 4652999999999.9234. We grab one more digit
            // than we need to precise from $r and then use that to round
            // appropriately.
            $precise = (string)round(substr($r, 0, strlen($r) + $scale), -1);
            // Now we return it, truncating the zero that was rounded off.
            return substr($precise, 0, -1) . str_repeat('0', -$scale + 1);
        }
        return sprintf('%.' . $scale . 'f', (float)$r);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Global exception class for HTML Purifier; any exceptions we throw
 * are from here.
 */
class HTMLPurifier_Exception extends Exception
{

}

// vim: et sw=4 sts=4
<?php

/**
 * Abstract class representing Definition cache managers that implements
 * useful common methods and is a factory.
 * @todo Create a separate maintenance file advanced users can use to
 *       cache their custom HTMLDefinition, which can be loaded
 *       via a configuration directive
 * @todo Implement memcached
 */
abstract class HTMLPurifier_DefinitionCache
{
    /**
     * @type string
     */
    public $type;

    /**
     * @param string $type Type of definition objects this instance of the
     *      cache will handle.
     */
    public function __construct($type)
    {
        $this->type = $type;
    }

    /**
     * Generates a unique identifier for a particular configuration
     * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config
     * @return string
     */
    public function generateKey($config)
    {
        return $config->version . ',' . // possibly replace with function calls
               $config->getBatchSerial($this->type) . ',' .
               $config->get($this->type . '.DefinitionRev');
    }

    /**
     * Tests whether or not a key is old with respect to the configuration's
     * version and revision number.
     * @param string $key Key to test
     * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config to test against
     * @return bool
     */
    public function isOld($key, $config)
    {
        if (substr_count($key, ',') < 2) {
            return true;
        }
        list($version, $hash, $revision) = explode(',', $key, 3);
        $compare = version_compare($version, $config->version);
        // version mismatch, is always old
        if ($compare != 0) {
            return true;
        }
        // versions match, ids match, check revision number
        if ($hash == $config->getBatchSerial($this->type) &&
            $revision < $config->get($this->type . '.DefinitionRev')) {
            return true;
        }
        return false;
    }

    /**
     * Checks if a definition's type jives with the cache's type
     * @note Throws an error on failure
     * @param HTMLPurifier_Definition $def Definition object to check
     * @return bool true if good, false if not
     */
    public function checkDefType($def)
    {
        if ($def->type !== $this->type) {
            trigger_error("Cannot use definition of type {$def->type} in cache for {$this->type}");
            return false;
        }
        return true;
    }

    /**
     * Adds a definition object to the cache
     * @param HTMLPurifier_Definition $def
     * @param HTMLPurifier_Config $config
     */
    abstract public function add($def, $config);

    /**
     * Unconditionally saves a definition object to the cache
     * @param HTMLPurifier_Definition $def
     * @param HTMLPurifier_Config $config
     */
    abstract public function set($def, $config);

    /**
     * Replace an object in the cache
     * @param HTMLPurifier_Definition $def
     * @param HTMLPurifier_Config $config
     */
    abstract public function replace($def, $config);

    /**
     * Retrieves a definition object from the cache
     * @param HTMLPurifier_Config $config
     */
    abstract public function get($config);

    /**
     * Removes a definition object to the cache
     * @param HTMLPurifier_Config $config
     */
    abstract public function remove($config);

    /**
     * Clears all objects from cache
     * @param HTMLPurifier_Config $config
     */
    abstract public function flush($config);

    /**
     * Clears all expired (older version or revision) objects from cache
     * @note Be careful implementing this method as flush. Flush must
     *       not interfere with other Definition types, and cleanup()
     *       should not be repeatedly called by userland code.
     * @param HTMLPurifier_Config $config
     */
    abstract public function cleanup($config);
}

// vim: et sw=4 sts=4
<?php

// OUT OF DATE, NEEDS UPDATING!
// USE XMLWRITER!

class HTMLPurifier_Printer
{

    /**
     * For HTML generation convenience funcs.
     * @type HTMLPurifier_Generator
     */
    protected $generator;

    /**
     * For easy access.
     * @type HTMLPurifier_Config
     */
    protected $config;

    /**
     * Initialize $generator.
     */
    public function __construct()
    {
    }

    /**
     * Give generator necessary configuration if possible
     * @param HTMLPurifier_Config $config
     */
    public function prepareGenerator($config)
    {
        $all = $config->getAll();
        $context = new HTMLPurifier_Context();
        $this->generator = new HTMLPurifier_Generator($config, $context);
    }

    /**
     * Main function that renders object or aspect of that object
     * @note Parameters vary depending on printer
     */
    // function render() {}

    /**
     * Returns a start tag
     * @param string $tag Tag name
     * @param array $attr Attribute array
     * @return string
     */
    protected function start($tag, $attr = array())
    {
        return $this->generator->generateFromToken(
            new HTMLPurifier_Token_Start($tag, $attr ? $attr : array())
        );
    }

    /**
     * Returns an end tag
     * @param string $tag Tag name
     * @return string
     */
    protected function end($tag)
    {
        return $this->generator->generateFromToken(
            new HTMLPurifier_Token_End($tag)
        );
    }

    /**
     * Prints a complete element with content inside
     * @param string $tag Tag name
     * @param string $contents Element contents
     * @param array $attr Tag attributes
     * @param bool $escape whether or not to escape contents
     * @return string
     */
    protected function element($tag, $contents, $attr = array(), $escape = true)
    {
        return $this->start($tag, $attr) .
            ($escape ? $this->escape($contents) : $contents) .
            $this->end($tag);
    }

    /**
     * @param string $tag
     * @param array $attr
     * @return string
     */
    protected function elementEmpty($tag, $attr = array())
    {
        return $this->generator->generateFromToken(
            new HTMLPurifier_Token_Empty($tag, $attr)
        );
    }

    /**
     * @param string $text
     * @return string
     */
    protected function text($text)
    {
        return $this->generator->generateFromToken(
            new HTMLPurifier_Token_Text($text)
        );
    }

    /**
     * Prints a simple key/value row in a table.
     * @param string $name Key
     * @param mixed $value Value
     * @return string
     */
    protected function row($name, $value)
    {
        if (is_bool($value)) {
            $value = $value ? 'On' : 'Off';
        }
        return
            $this->start('tr') . "\n" .
            $this->element('th', $name) . "\n" .
            $this->element('td', $value) . "\n" .
            $this->end('tr');
    }

    /**
     * Escapes a string for HTML output.
     * @param string $string String to escape
     * @return string
     */
    protected function escape($string)
    {
        $string = HTMLPurifier_Encoder::cleanUTF8($string);
        $string = htmlspecialchars($string, ENT_COMPAT, 'UTF-8');
        return $string;
    }

    /**
     * Takes a list of strings and turns them into a single list
     * @param string[] $array List of strings
     * @param bool $polite Bool whether or not to add an end before the last
     * @return string
     */
    protected function listify($array, $polite = false)
    {
        if (empty($array)) {
            return 'None';
        }
        $ret = '';
        $i = count($array);
        foreach ($array as $value) {
            $i--;
            $ret .= $value;
            if ($i > 0 && !($polite && $i == 1)) {
                $ret .= ', ';
            }
            if ($polite && $i == 1) {
                $ret .= 'and ';
            }
        }
        return $ret;
    }

    /**
     * Retrieves the class of an object without prefixes, as well as metadata
     * @param object $obj Object to determine class of
     * @param string $sec_prefix Further prefix to remove
     * @return string
     */
    protected function getClass($obj, $sec_prefix = '')
    {
        static $five = null;
        if ($five === null) {
            $five = version_compare(PHP_VERSION, '5', '>=');
        }
        $prefix = 'HTMLPurifier_' . $sec_prefix;
        if (!$five) {
            $prefix = strtolower($prefix);
        }
        $class = str_replace($prefix, '', get_class($obj));
        $lclass = strtolower($class);
        $class .= '(';
        switch ($lclass) {
            case 'enum':
                $values = array();
                foreach ($obj->valid_values as $value => $bool) {
                    $values[] = $value;
                }
                $class .= implode(', ', $values);
                break;
            case 'css_composite':
                $values = array();
                foreach ($obj->defs as $def) {
                    $values[] = $this->getClass($def, $sec_prefix);
                }
                $class .= implode(', ', $values);
                break;
            case 'css_multiple':
                $class .= $this->getClass($obj->single, $sec_prefix) . ', ';
                $class .= $obj->max;
                break;
            case 'css_denyelementdecorator':
                $class .= $this->getClass($obj->def, $sec_prefix) . ', ';
                $class .= $obj->element;
                break;
            case 'css_importantdecorator':
                $class .= $this->getClass($obj->def, $sec_prefix);
                if ($obj->allow) {
                    $class .= ', !important';
                }
                break;
        }
        $class .= ')';
        return $class;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Registry for retrieving specific URI scheme validator objects.
 */
class HTMLPurifier_URISchemeRegistry
{

    /**
     * Retrieve sole instance of the registry.
     * @param HTMLPurifier_URISchemeRegistry $prototype Optional prototype to overload sole instance with,
     *                   or bool true to reset to default registry.
     * @return HTMLPurifier_URISchemeRegistry
     * @note Pass a registry object $prototype with a compatible interface and
     *       the function will copy it and return it all further times.
     */
    public static function instance($prototype = null)
    {
        static $instance = null;
        if ($prototype !== null) {
            $instance = $prototype;
        } elseif ($instance === null || $prototype == true) {
            $instance = new HTMLPurifier_URISchemeRegistry();
        }
        return $instance;
    }

    /**
     * Cache of retrieved schemes.
     * @type HTMLPurifier_URIScheme[]
     */
    protected $schemes = array();

    /**
     * Retrieves a scheme validator object
     * @param string $scheme String scheme name like http or mailto
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return HTMLPurifier_URIScheme
     */
    public function getScheme($scheme, $config, $context)
    {
        if (!$config) {
            $config = HTMLPurifier_Config::createDefault();
        }

        // important, otherwise attacker could include arbitrary file
        $allowed_schemes = $config->get('URI.AllowedSchemes');
        if (!$config->get('URI.OverrideAllowedSchemes') &&
            !isset($allowed_schemes[$scheme])
        ) {
            return;
        }

        if (isset($this->schemes[$scheme])) {
            return $this->schemes[$scheme];
        }
        if (!isset($allowed_schemes[$scheme])) {
            return;
        }

        $class = 'HTMLPurifier_URIScheme_' . $scheme;
        if (!class_exists($class)) {
            return;
        }
        $this->schemes[$scheme] = new $class();
        return $this->schemes[$scheme];
    }

    /**
     * Registers a custom scheme to the cache, bypassing reflection.
     * @param string $scheme Scheme name
     * @param HTMLPurifier_URIScheme $scheme_obj
     */
    public function register($scheme, $scheme_obj)
    {
        $this->schemes[$scheme] = $scheme_obj;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Base class for all validating attribute definitions.
 *
 * This family of classes forms the core for not only HTML attribute validation,
 * but also any sort of string that needs to be validated or cleaned (which
 * means CSS properties and composite definitions are defined here too).
 * Besides defining (through code) what precisely makes the string valid,
 * subclasses are also responsible for cleaning the code if possible.
 */

abstract class HTMLPurifier_AttrDef
{

    /**
     * Tells us whether or not an HTML attribute is minimized.
     * Has no meaning in other contexts.
     * @type bool
     */
    public $minimized = false;

    /**
     * Tells us whether or not an HTML attribute is required.
     * Has no meaning in other contexts
     * @type bool
     */
    public $required = false;

    /**
     * Validates and cleans passed string according to a definition.
     *
     * @param string $string String to be validated and cleaned.
     * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object.
     * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object.
     */
    abstract public function validate($string, $config, $context);

    /**
     * Convenience method that parses a string as if it were CDATA.
     *
     * This method process a string in the manner specified at
     * <http://www.w3.org/TR/html4/types.html#h-6.2> by removing
     * leading and trailing whitespace, ignoring line feeds, and replacing
     * carriage returns and tabs with spaces.  While most useful for HTML
     * attributes specified as CDATA, it can also be applied to most CSS
     * values.
     *
     * @note This method is not entirely standards compliant, as trim() removes
     *       more types of whitespace than specified in the spec. In practice,
     *       this is rarely a problem, as those extra characters usually have
     *       already been removed by HTMLPurifier_Encoder.
     *
     * @warning This processing is inconsistent with XML's whitespace handling
     *          as specified by section 3.3.3 and referenced XHTML 1.0 section
     *          4.7.  However, note that we are NOT necessarily
     *          parsing XML, thus, this behavior may still be correct. We
     *          assume that newlines have been normalized.
     */
    public function parseCDATA($string)
    {
        $string = trim($string);
        $string = str_replace(array("\n", "\t", "\r"), ' ', $string);
        return $string;
    }

    /**
     * Factory method for creating this class from a string.
     * @param string $string String construction info
     * @return HTMLPurifier_AttrDef Created AttrDef object corresponding to $string
     */
    public function make($string)
    {
        // default implementation, return a flyweight of this object.
        // If $string has an effect on the returned object (i.e. you
        // need to overload this method), it is best
        // to clone or instantiate new copies. (Instantiation is safer.)
        return $this;
    }

    /**
     * Removes spaces from rgb(0, 0, 0) so that shorthand CSS properties work
     * properly. THIS IS A HACK!
     * @param string $string a CSS colour definition
     * @return string
     */
    protected function mungeRgb($string)
    {
        $p = '\s*(\d+(\.\d+)?([%]?))\s*';

        if (preg_match('/(rgba|hsla)\(/', $string)) {
            return preg_replace('/(rgba|hsla)\('.$p.','.$p.','.$p.','.$p.'\)/', '\1(\2,\5,\8,\11)', $string);
        }

        return preg_replace('/(rgb|hsl)\('.$p.','.$p.','.$p.'\)/', '\1(\2,\5,\8)', $string);
    }

    /**
     * Parses a possibly escaped CSS string and returns the "pure"
     * version of it.
     */
    protected function expandCSSEscape($string)
    {
        // flexibly parse it
        $ret = '';
        for ($i = 0, $c = strlen($string); $i < $c; $i++) {
            if ($string[$i] === '\\') {
                $i++;
                if ($i >= $c) {
                    $ret .= '\\';
                    break;
                }
                if (ctype_xdigit($string[$i])) {
                    $code = $string[$i];
                    for ($a = 1, $i++; $i < $c && $a < 6; $i++, $a++) {
                        if (!ctype_xdigit($string[$i])) {
                            break;
                        }
                        $code .= $string[$i];
                    }
                    // We have to be extremely careful when adding
                    // new characters, to make sure we're not breaking
                    // the encoding.
                    $char = HTMLPurifier_Encoder::unichr(hexdec($code));
                    if (HTMLPurifier_Encoder::cleanUTF8($char) === '') {
                        continue;
                    }
                    $ret .= $char;
                    if ($i < $c && trim($string[$i]) !== '') {
                        $i--;
                    }
                    continue;
                }
                if ($string[$i] === "\n") {
                    continue;
                }
            }
            $ret .= $string[$i];
        }
        return $ret;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Injects tokens into the document while parsing for well-formedness.
 * This enables "formatter-like" functionality such as auto-paragraphing,
 * smiley-ification and linkification to take place.
 *
 * A note on how handlers create changes; this is done by assigning a new
 * value to the $token reference. These values can take a variety of forms and
 * are best described HTMLPurifier_Strategy_MakeWellFormed->processToken()
 * documentation.
 *
 * @todo Allow injectors to request a re-run on their output. This
 *       would help if an operation is recursive.
 */
abstract class HTMLPurifier_Injector
{

    /**
     * Advisory name of injector, this is for friendly error messages.
     * @type string
     */
    public $name;

    /**
     * @type HTMLPurifier_HTMLDefinition
     */
    protected $htmlDefinition;

    /**
     * Reference to CurrentNesting variable in Context. This is an array
     * list of tokens that we are currently "inside"
     * @type array
     */
    protected $currentNesting;

    /**
     * Reference to current token.
     * @type HTMLPurifier_Token
     */
    protected $currentToken;

    /**
     * Reference to InputZipper variable in Context.
     * @type HTMLPurifier_Zipper
     */
    protected $inputZipper;

    /**
     * Array of elements and attributes this injector creates and therefore
     * need to be allowed by the definition. Takes form of
     * array('element' => array('attr', 'attr2'), 'element2')
     * @type array
     */
    public $needed = array();

    /**
     * Number of elements to rewind backwards (relative).
     * @type bool|int
     */
    protected $rewindOffset = false;

    /**
     * Rewind to a spot to re-perform processing. This is useful if you
     * deleted a node, and now need to see if this change affected any
     * earlier nodes. Rewinding does not affect other injectors, and can
     * result in infinite loops if not used carefully.
     * @param bool|int $offset
     * @warning HTML Purifier will prevent you from fast-forwarding with this
     *          function.
     */
    public function rewindOffset($offset)
    {
        $this->rewindOffset = $offset;
    }

    /**
     * Retrieves rewind offset, and then unsets it.
     * @return bool|int
     */
    public function getRewindOffset()
    {
        $r = $this->rewindOffset;
        $this->rewindOffset = false;
        return $r;
    }

    /**
     * Prepares the injector by giving it the config and context objects:
     * this allows references to important variables to be made within
     * the injector. This function also checks if the HTML environment
     * will work with the Injector (see checkNeeded()).
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool|string Boolean false if success, string of missing needed element/attribute if failure
     */
    public function prepare($config, $context)
    {
        $this->htmlDefinition = $config->getHTMLDefinition();
        // Even though this might fail, some unit tests ignore this and
        // still test checkNeeded, so be careful. Maybe get rid of that
        // dependency.
        $result = $this->checkNeeded($config);
        if ($result !== false) {
            return $result;
        }
        $this->currentNesting =& $context->get('CurrentNesting');
        $this->currentToken   =& $context->get('CurrentToken');
        $this->inputZipper    =& $context->get('InputZipper');
        return false;
    }

    /**
     * This function checks if the HTML environment
     * will work with the Injector: if p tags are not allowed, the
     * Auto-Paragraphing injector should not be enabled.
     * @param HTMLPurifier_Config $config
     * @return bool|string Boolean false if success, string of missing needed element/attribute if failure
     */
    public function checkNeeded($config)
    {
        $def = $config->getHTMLDefinition();
        foreach ($this->needed as $element => $attributes) {
            if (is_int($element)) {
                $element = $attributes;
            }
            if (!isset($def->info[$element])) {
                return $element;
            }
            if (!is_array($attributes)) {
                continue;
            }
            foreach ($attributes as $name) {
                if (!isset($def->info[$element]->attr[$name])) {
                    return "$element.$name";
                }
            }
        }
        return false;
    }

    /**
     * Tests if the context node allows a certain element
     * @param string $name Name of element to test for
     * @return bool True if element is allowed, false if it is not
     */
    public function allowsElement($name)
    {
        if (!empty($this->currentNesting)) {
            $parent_token = array_pop($this->currentNesting);
            $this->currentNesting[] = $parent_token;
            $parent = $this->htmlDefinition->info[$parent_token->name];
        } else {
            $parent = $this->htmlDefinition->info_parent_def;
        }
        if (!isset($parent->child->elements[$name]) || isset($parent->excludes[$name])) {
            return false;
        }
        // check for exclusion
        if (!empty($this->currentNesting)) {
            for ($i = count($this->currentNesting) - 2; $i >= 0; $i--) {
                $node = $this->currentNesting[$i];
                $def  = $this->htmlDefinition->info[$node->name];
                if (isset($def->excludes[$name])) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Iterator function, which starts with the next token and continues until
     * you reach the end of the input tokens.
     * @warning Please prevent previous references from interfering with this
     *          functions by setting $i = null beforehand!
     * @param int $i Current integer index variable for inputTokens
     * @param HTMLPurifier_Token $current Current token variable.
     *          Do NOT use $token, as that variable is also a reference
     * @return bool
     */
    protected function forward(&$i, &$current)
    {
        if ($i === null) {
            $i = count($this->inputZipper->back) - 1;
        } else {
            $i--;
        }
        if ($i < 0) {
            return false;
        }
        $current = $this->inputZipper->back[$i];
        return true;
    }

    /**
     * Similar to _forward, but accepts a third parameter $nesting (which
     * should be initialized at 0) and stops when we hit the end tag
     * for the node $this->inputIndex starts in.
     * @param int $i Current integer index variable for inputTokens
     * @param HTMLPurifier_Token $current Current token variable.
     *          Do NOT use $token, as that variable is also a reference
     * @param int $nesting
     * @return bool
     */
    protected function forwardUntilEndToken(&$i, &$current, &$nesting)
    {
        $result = $this->forward($i, $current);
        if (!$result) {
            return false;
        }
        if ($nesting === null) {
            $nesting = 0;
        }
        if ($current instanceof HTMLPurifier_Token_Start) {
            $nesting++;
        } elseif ($current instanceof HTMLPurifier_Token_End) {
            if ($nesting <= 0) {
                return false;
            }
            $nesting--;
        }
        return true;
    }

    /**
     * Iterator function, starts with the previous token and continues until
     * you reach the beginning of input tokens.
     * @warning Please prevent previous references from interfering with this
     *          functions by setting $i = null beforehand!
     * @param int $i Current integer index variable for inputTokens
     * @param HTMLPurifier_Token $current Current token variable.
     *          Do NOT use $token, as that variable is also a reference
     * @return bool
     */
    protected function backward(&$i, &$current)
    {
        if ($i === null) {
            $i = count($this->inputZipper->front) - 1;
        } else {
            $i--;
        }
        if ($i < 0) {
            return false;
        }
        $current = $this->inputZipper->front[$i];
        return true;
    }

    /**
     * Handler that is called when a text token is processed
     */
    public function handleText(&$token)
    {
    }

    /**
     * Handler that is called when a start or empty token is processed
     */
    public function handleElement(&$token)
    {
    }

    /**
     * Handler that is called when an end token is processed
     */
    public function handleEnd(&$token)
    {
        $this->notifyEnd($token);
    }

    /**
     * Notifier that is called when an end token is processed
     * @param HTMLPurifier_Token $token Current token variable.
     * @note This differs from handlers in that the token is read-only
     * @deprecated
     */
    public function notifyEnd($token)
    {
    }
}

// vim: et sw=4 sts=4
<?php

class HTMLPurifier_Injector_RemoveEmpty extends HTMLPurifier_Injector
{
    /**
     * @type HTMLPurifier_Context
     */
    private $context;

    /**
     * @type HTMLPurifier_Config
     */
    private $config;

    /**
     * @type HTMLPurifier_AttrValidator
     */
    private $attrValidator;

    /**
     * @type bool
     */
    private $removeNbsp;

    /**
     * @type bool
     */
    private $removeNbspExceptions;

    /**
     * Cached contents of %AutoFormat.RemoveEmpty.Predicate
     * @type array
     */
    private $exclude;

    /**
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return void
     */
    public function prepare($config, $context)
    {
        parent::prepare($config, $context);
        $this->config = $config;
        $this->context = $context;
        $this->removeNbsp = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp');
        $this->removeNbspExceptions = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions');
        $this->exclude = $config->get('AutoFormat.RemoveEmpty.Predicate');
        foreach ($this->exclude as $key => $attrs) {
            if (!is_array($attrs)) {
                // HACK, see HTMLPurifier/Printer/ConfigForm.php
                $this->exclude[$key] = explode(';', $attrs);
            }
        }
        $this->attrValidator = new HTMLPurifier_AttrValidator();
    }

    /**
     * @param HTMLPurifier_Token $token
     */
    public function handleElement(&$token)
    {
        if (!$token instanceof HTMLPurifier_Token_Start) {
            return;
        }
        $next = false;
        $deleted = 1; // the current tag
        for ($i = count($this->inputZipper->back) - 1; $i >= 0; $i--, $deleted++) {
            $next = $this->inputZipper->back[$i];
            if ($next instanceof HTMLPurifier_Token_Text) {
                if ($next->is_whitespace) {
                    continue;
                }
                if ($this->removeNbsp && !isset($this->removeNbspExceptions[$token->name])) {
                    $plain = str_replace("\xC2\xA0", "", $next->data);
                    $isWsOrNbsp = $plain === '' || ctype_space($plain);
                    if ($isWsOrNbsp) {
                        continue;
                    }
                }
            }
            break;
        }
        if (!$next || ($next instanceof HTMLPurifier_Token_End && $next->name == $token->name)) {
            $this->attrValidator->validateToken($token, $this->config, $this->context);
            $token->armor['ValidateAttributes'] = true;
            if (isset($this->exclude[$token->name])) {
                $r = true;
                foreach ($this->exclude[$token->name] as $elem) {
                    if (!isset($token->attr[$elem])) $r = false;
                }
                if ($r) return;
            }
            if (isset($token->attr['id']) || isset($token->attr['name'])) {
                return;
            }
            $token = $deleted + 1;
            for ($b = 0, $c = count($this->inputZipper->front); $b < $c; $b++) {
                $prev = $this->inputZipper->front[$b];
                if ($prev instanceof HTMLPurifier_Token_Text && $prev->is_whitespace) {
                    continue;
                }
                break;
            }
            // This is safe because we removed the token that triggered this.
            $this->rewindOffset($b+$deleted);
            return;
        }
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Adds important param elements to inside of object in order to make
 * things safe.
 */
class HTMLPurifier_Injector_SafeObject extends HTMLPurifier_Injector
{
    /**
     * @type string
     */
    public $name = 'SafeObject';

    /**
     * @type array
     */
    public $needed = array('object', 'param');

    /**
     * @type array
     */
    protected $objectStack = array();

    /**
     * @type array
     */
    protected $paramStack = array();

    /**
     * Keep this synchronized with AttrTransform/SafeParam.php.
     * @type array
     */
    protected $addParam = array(
        'allowScriptAccess' => 'never',
        'allowNetworking' => 'internal',
    );

    /**
     * These are all lower-case keys.
     * @type array
     */
    protected $allowedParam = array(
        'wmode' => true,
        'movie' => true,
        'flashvars' => true,
        'src' => true,
        'allowfullscreen' => true, // if omitted, assume to be 'false'
    );

    /**
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return void
     */
    public function prepare($config, $context)
    {
        parent::prepare($config, $context);
    }

    /**
     * @param HTMLPurifier_Token $token
     */
    public function handleElement(&$token)
    {
        if ($token->name == 'object') {
            $this->objectStack[] = $token;
            $this->paramStack[] = array();
            $new = array($token);
            foreach ($this->addParam as $name => $value) {
                $new[] = new HTMLPurifier_Token_Empty('param', array('name' => $name, 'value' => $value));
            }
            $token = $new;
        } elseif ($token->name == 'param') {
            $nest = count($this->currentNesting) - 1;
            if ($nest >= 0 && $this->currentNesting[$nest]->name === 'object') {
                $i = count($this->objectStack) - 1;
                if (!isset($token->attr['name'])) {
                    $token = false;
                    return;
                }
                $n = $token->attr['name'];
                // We need this fix because YouTube doesn't supply a data
                // attribute, which we need if a type is specified. This is
                // *very* Flash specific.
                if (!isset($this->objectStack[$i]->attr['data']) &&
                    ($token->attr['name'] == 'movie' || $token->attr['name'] == 'src')
                ) {
                    $this->objectStack[$i]->attr['data'] = $token->attr['value'];
                }
                // Check if the parameter is the correct value but has not
                // already been added
                if (!isset($this->paramStack[$i][$n]) &&
                    isset($this->addParam[$n]) &&
                    $token->attr['name'] === $this->addParam[$n]) {
                    // keep token, and add to param stack
                    $this->paramStack[$i][$n] = true;
                } elseif (isset($this->allowedParam[strtolower($n)])) {
                    // keep token, don't do anything to it
                    // (could possibly check for duplicates here)
                    // Note: In principle, parameters should be case sensitive.
                    // But it seems they are not really; so accept any case.
                } else {
                    $token = false;
                }
            } else {
                // not directly inside an object, DENY!
                $token = false;
            }
        }
    }

    public function handleEnd(&$token)
    {
        // This is the WRONG way of handling the object and param stacks;
        // we should be inserting them directly on the relevant object tokens
        // so that the global stack handling handles it.
        if ($token->name == 'object') {
            array_pop($this->objectStack);
            array_pop($this->paramStack);
        }
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Injector that removes spans with no attributes
 */
class HTMLPurifier_Injector_RemoveSpansWithoutAttributes extends HTMLPurifier_Injector
{
    /**
     * @type string
     */
    public $name = 'RemoveSpansWithoutAttributes';

    /**
     * @type array
     */
    public $needed = array('span');

    /**
     * @type HTMLPurifier_AttrValidator
     */
    private $attrValidator;

    /**
     * Used by AttrValidator.
     * @type HTMLPurifier_Config
     */
    private $config;

    /**
     * @type HTMLPurifier_Context
     */
    private $context;

    public function prepare($config, $context)
    {
        $this->attrValidator = new HTMLPurifier_AttrValidator();
        $this->config = $config;
        $this->context = $context;
        return parent::prepare($config, $context);
    }

    /**
     * @param HTMLPurifier_Token $token
     */
    public function handleElement(&$token)
    {
        if ($token->name !== 'span' || !$token instanceof HTMLPurifier_Token_Start) {
            return;
        }

        // We need to validate the attributes now since this doesn't normally
        // happen until after MakeWellFormed. If all the attributes are removed
        // the span needs to be removed too.
        $this->attrValidator->validateToken($token, $this->config, $this->context);
        $token->armor['ValidateAttributes'] = true;

        if (!empty($token->attr)) {
            return;
        }

        $nesting = 0;
        while ($this->forwardUntilEndToken($i, $current, $nesting)) {
        }

        if ($current instanceof HTMLPurifier_Token_End && $current->name === 'span') {
            // Mark closing span tag for deletion
            $current->markForDeletion = true;
            // Delete open span tag
            $token = false;
        }
    }

    /**
     * @param HTMLPurifier_Token $token
     */
    public function handleEnd(&$token)
    {
        if ($token->markForDeletion) {
            $token = false;
        }
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Injector that converts configuration directive syntax %Namespace.Directive
 * to links
 */
class HTMLPurifier_Injector_PurifierLinkify extends HTMLPurifier_Injector
{
    /**
     * @type string
     */
    public $name = 'PurifierLinkify';

    /**
     * @type string
     */
    public $docURL;

    /**
     * @type array
     */
    public $needed = array('a' => array('href'));

    /**
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return string
     */
    public function prepare($config, $context)
    {
        $this->docURL = $config->get('AutoFormat.PurifierLinkify.DocURL');
        return parent::prepare($config, $context);
    }

    /**
     * @param HTMLPurifier_Token $token
     */
    public function handleText(&$token)
    {
        if (!$this->allowsElement('a')) {
            return;
        }
        if (strpos($token->data, '%') === false) {
            return;
        }

        $bits = preg_split('#%([a-z0-9]+\.[a-z0-9]+)#Si', $token->data, -1, PREG_SPLIT_DELIM_CAPTURE);
        $token = array();

        // $i = index
        // $c = count
        // $l = is link
        for ($i = 0, $c = count($bits), $l = false; $i < $c; $i++, $l = !$l) {
            if (!$l) {
                if ($bits[$i] === '') {
                    continue;
                }
                $token[] = new HTMLPurifier_Token_Text($bits[$i]);
            } else {
                $token[] = new HTMLPurifier_Token_Start(
                    'a',
                    array('href' => str_replace('%s', $bits[$i], $this->docURL))
                );
                $token[] = new HTMLPurifier_Token_Text('%' . $bits[$i]);
                $token[] = new HTMLPurifier_Token_End('a');
            }
        }
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Injector that auto paragraphs text in the root node based on
 * double-spacing.
 * @todo Ensure all states are unit tested, including variations as well.
 * @todo Make a graph of the flow control for this Injector.
 */
class HTMLPurifier_Injector_AutoParagraph extends HTMLPurifier_Injector
{
    /**
     * @type string
     */
    public $name = 'AutoParagraph';

    /**
     * @type array
     */
    public $needed = array('p');

    /**
     * @return HTMLPurifier_Token_Start
     */
    private function _pStart()
    {
        $par = new HTMLPurifier_Token_Start('p');
        $par->armor['MakeWellFormed_TagClosedError'] = true;
        return $par;
    }

    /**
     * @param HTMLPurifier_Token_Text $token
     */
    public function handleText(&$token)
    {
        $text = $token->data;
        // Does the current parent allow <p> tags?
        if ($this->allowsElement('p')) {
            if (empty($this->currentNesting) || strpos($text, "\n\n") !== false) {
                // Note that we have differing behavior when dealing with text
                // in the anonymous root node, or a node inside the document.
                // If the text as a double-newline, the treatment is the same;
                // if it doesn't, see the next if-block if you're in the document.

                $i = $nesting = null;
                if (!$this->forwardUntilEndToken($i, $current, $nesting) && $token->is_whitespace) {
                    // State 1.1: ...    ^ (whitespace, then document end)
                    //               ----
                    // This is a degenerate case
                } else {
                    if (!$token->is_whitespace || $this->_isInline($current)) {
                        // State 1.2: PAR1
                        //            ----

                        // State 1.3: PAR1\n\nPAR2
                        //            ------------

                        // State 1.4: <div>PAR1\n\nPAR2 (see State 2)
                        //                 ------------
                        $token = array($this->_pStart());
                        $this->_splitText($text, $token);
                    } else {
                        // State 1.5: \n<hr />
                        //            --
                    }
                }
            } else {
                // State 2:   <div>PAR1... (similar to 1.4)
                //                 ----

                // We're in an element that allows paragraph tags, but we're not
                // sure if we're going to need them.
                if ($this->_pLookAhead()) {
                    // State 2.1: <div>PAR1<b>PAR1\n\nPAR2
                    //                 ----
                    // Note: This will always be the first child, since any
                    // previous inline element would have triggered this very
                    // same routine, and found the double newline. One possible
                    // exception would be a comment.
                    $token = array($this->_pStart(), $token);
                } else {
                    // State 2.2.1: <div>PAR1<div>
                    //                   ----

                    // State 2.2.2: <div>PAR1<b>PAR1</b></div>
                    //                   ----
                }
            }
            // Is the current parent a <p> tag?
        } elseif (!empty($this->currentNesting) &&
            $this->currentNesting[count($this->currentNesting) - 1]->name == 'p') {
            // State 3.1: ...<p>PAR1
            //                  ----

            // State 3.2: ...<p>PAR1\n\nPAR2
            //                  ------------
            $token = array();
            $this->_splitText($text, $token);
            // Abort!
        } else {
            // State 4.1: ...<b>PAR1
            //                  ----

            // State 4.2: ...<b>PAR1\n\nPAR2
            //                  ------------
        }
    }

    /**
     * @param HTMLPurifier_Token $token
     */
    public function handleElement(&$token)
    {
        // We don't have to check if we're already in a <p> tag for block
        // tokens, because the tag would have been autoclosed by MakeWellFormed.
        if ($this->allowsElement('p')) {
            if (!empty($this->currentNesting)) {
                if ($this->_isInline($token)) {
                    // State 1: <div>...<b>
                    //                  ---
                    // Check if this token is adjacent to the parent token
                    // (seek backwards until token isn't whitespace)
                    $i = null;
                    $this->backward($i, $prev);

                    if (!$prev instanceof HTMLPurifier_Token_Start) {
                        // Token wasn't adjacent
                        if ($prev instanceof HTMLPurifier_Token_Text &&
                            substr($prev->data, -2) === "\n\n"
                        ) {
                            // State 1.1.4: <div><p>PAR1</p>\n\n<b>
                            //                                  ---
                            // Quite frankly, this should be handled by splitText
                            $token = array($this->_pStart(), $token);
                        } else {
                            // State 1.1.1: <div><p>PAR1</p><b>
                            //                              ---
                            // State 1.1.2: <div><br /><b>
                            //                         ---
                            // State 1.1.3: <div>PAR<b>
                            //                      ---
                        }
                    } else {
                        // State 1.2.1: <div><b>
                        //                   ---
                        // Lookahead to see if <p> is needed.
                        if ($this->_pLookAhead()) {
                            // State 1.3.1: <div><b>PAR1\n\nPAR2
                            //                   ---
                            $token = array($this->_pStart(), $token);
                        } else {
                            // State 1.3.2: <div><b>PAR1</b></div>
                            //                   ---

                            // State 1.3.3: <div><b>PAR1</b><div></div>\n\n</div>
                            //                   ---
                        }
                    }
                } else {
                    // State 2.3: ...<div>
                    //               -----
                }
            } else {
                if ($this->_isInline($token)) {
                    // State 3.1: <b>
                    //            ---
                    // This is where the {p} tag is inserted, not reflected in
                    // inputTokens yet, however.
                    $token = array($this->_pStart(), $token);
                } else {
                    // State 3.2: <div>
                    //            -----
                }

                $i = null;
                if ($this->backward($i, $prev)) {
                    if (!$prev instanceof HTMLPurifier_Token_Text) {
                        // State 3.1.1: ...</p>{p}<b>
                        //                        ---
                        // State 3.2.1: ...</p><div>
                        //                     -----
                        if (!is_array($token)) {
                            $token = array($token);
                        }
                        array_unshift($token, new HTMLPurifier_Token_Text("\n\n"));
                    } else {
                        // State 3.1.2: ...</p>\n\n{p}<b>
                        //                            ---
                        // State 3.2.2: ...</p>\n\n<div>
                        //                         -----
                        // Note: PAR<ELEM> cannot occur because PAR would have been
                        // wrapped in <p> tags.
                    }
                }
            }
        } else {
            // State 2.2: <ul><li>
            //                ----
            // State 2.4: <p><b>
            //               ---
        }
    }

    /**
     * Splits up a text in paragraph tokens and appends them
     * to the result stream that will replace the original
     * @param string $data String text data that will be processed
     *    into paragraphs
     * @param HTMLPurifier_Token[] $result Reference to array of tokens that the
     *    tags will be appended onto
     */
    private function _splitText($data, &$result)
    {
        $raw_paragraphs = explode("\n\n", $data);
        $paragraphs = array(); // without empty paragraphs
        $needs_start = false;
        $needs_end = false;

        $c = count($raw_paragraphs);
        if ($c == 1) {
            // There were no double-newlines, abort quickly. In theory this
            // should never happen.
            $result[] = new HTMLPurifier_Token_Text($data);
            return;
        }
        for ($i = 0; $i < $c; $i++) {
            $par = $raw_paragraphs[$i];
            if (trim($par) !== '') {
                $paragraphs[] = $par;
            } else {
                if ($i == 0) {
                    // Double newline at the front
                    if (empty($result)) {
                        // The empty result indicates that the AutoParagraph
                        // injector did not add any start paragraph tokens.
                        // This means that we have been in a paragraph for
                        // a while, and the newline means we should start a new one.
                        $result[] = new HTMLPurifier_Token_End('p');
                        $result[] = new HTMLPurifier_Token_Text("\n\n");
                        // However, the start token should only be added if
                        // there is more processing to be done (i.e. there are
                        // real paragraphs in here). If there are none, the
                        // next start paragraph tag will be handled by the
                        // next call to the injector
                        $needs_start = true;
                    } else {
                        // We just started a new paragraph!
                        // Reinstate a double-newline for presentation's sake, since
                        // it was in the source code.
                        array_unshift($result, new HTMLPurifier_Token_Text("\n\n"));
                    }
                } elseif ($i + 1 == $c) {
                    // Double newline at the end
                    // There should be a trailing </p> when we're finally done.
                    $needs_end = true;
                }
            }
        }

        // Check if this was just a giant blob of whitespace. Move this earlier,
        // perhaps?
        if (empty($paragraphs)) {
            return;
        }

        // Add the start tag indicated by \n\n at the beginning of $data
        if ($needs_start) {
            $result[] = $this->_pStart();
        }

        // Append the paragraphs onto the result
        foreach ($paragraphs as $par) {
            $result[] = new HTMLPurifier_Token_Text($par);
            $result[] = new HTMLPurifier_Token_End('p');
            $result[] = new HTMLPurifier_Token_Text("\n\n");
            $result[] = $this->_pStart();
        }

        // Remove trailing start token; Injector will handle this later if
        // it was indeed needed. This prevents from needing to do a lookahead,
        // at the cost of a lookbehind later.
        array_pop($result);

        // If there is no need for an end tag, remove all of it and let
        // MakeWellFormed close it later.
        if (!$needs_end) {
            array_pop($result); // removes \n\n
            array_pop($result); // removes </p>
        }
    }

    /**
     * Returns true if passed token is inline (and, ergo, allowed in
     * paragraph tags)
     * @param HTMLPurifier_Token $token
     * @return bool
     */
    private function _isInline($token)
    {
        return isset($this->htmlDefinition->info['p']->child->elements[$token->name]);
    }

    /**
     * Looks ahead in the token list and determines whether or not we need
     * to insert a <p> tag.
     * @return bool
     */
    private function _pLookAhead()
    {
        if ($this->currentToken instanceof HTMLPurifier_Token_Start) {
            $nesting = 1;
        } else {
            $nesting = 0;
        }
        $ok = false;
        $i = null;
        while ($this->forwardUntilEndToken($i, $current, $nesting)) {
            $result = $this->_checkNeedsP($current);
            if ($result !== null) {
                $ok = $result;
                break;
            }
        }
        return $ok;
    }

    /**
     * Determines if a particular token requires an earlier inline token
     * to get a paragraph. This should be used with _forwardUntilEndToken
     * @param HTMLPurifier_Token $current
     * @return bool
     */
    private function _checkNeedsP($current)
    {
        if ($current instanceof HTMLPurifier_Token_Start) {
            if (!$this->_isInline($current)) {
                // <div>PAR1<div>
                //      ----
                // Terminate early, since we hit a block element
                return false;
            }
        } elseif ($current instanceof HTMLPurifier_Token_Text) {
            if (strpos($current->data, "\n\n") !== false) {
                // <div>PAR1<b>PAR1\n\nPAR2
                //      ----
                return true;
            } else {
                // <div>PAR1<b>PAR1...
                //      ----
            }
        }
        return null;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Injector that converts http, https and ftp text URLs to actual links.
 */
class HTMLPurifier_Injector_Linkify extends HTMLPurifier_Injector
{
    /**
     * @type string
     */
    public $name = 'Linkify';

    /**
     * @type array
     */
    public $needed = array('a' => array('href'));

    /**
     * @param HTMLPurifier_Token $token
     */
    public function handleText(&$token)
    {
        if (!$this->allowsElement('a')) {
            return;
        }

        if (strpos($token->data, '://') === false) {
            // our really quick heuristic failed, abort
            // this may not work so well if we want to match things like
            // "google.com", but then again, most people don't
            return;
        }

        // there is/are URL(s). Let's split the string.
        // We use this regex:
        // https://gist.github.com/gruber/249502
        // but with @cscott's backtracking fix and also
        // the Unicode characters un-Unicodified.
        $bits = preg_split(
            '/\\b((?:[a-z][\\w\\-]+:(?:\\/{1,3}|[a-z0-9%])|www\\d{0,3}[.]|[a-z0-9.\\-]+[.][a-z]{2,4}\\/)(?:[^\\s()<>]|\\((?:[^\\s()<>]|(?:\\([^\\s()<>]+\\)))*\\))+(?:\\((?:[^\\s()<>]|(?:\\([^\\s()<>]+\\)))*\\)|[^\\s`!()\\[\\]{};:\'".,<>?\x{00ab}\x{00bb}\x{201c}\x{201d}\x{2018}\x{2019}]))/iu',
            $token->data, -1, PREG_SPLIT_DELIM_CAPTURE);


        $token = array();

        // $i = index
        // $c = count
        // $l = is link
        for ($i = 0, $c = count($bits), $l = false; $i < $c; $i++, $l = !$l) {
            if (!$l) {
                if ($bits[$i] === '') {
                    continue;
                }
                $token[] = new HTMLPurifier_Token_Text($bits[$i]);
            } else {
                $token[] = new HTMLPurifier_Token_Start('a', array('href' => $bits[$i]));
                $token[] = new HTMLPurifier_Token_Text($bits[$i]);
                $token[] = new HTMLPurifier_Token_End('a');
            }
        }
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Injector that displays the URL of an anchor instead of linking to it, in addition to showing the text of the link.
 */
class HTMLPurifier_Injector_DisplayLinkURI extends HTMLPurifier_Injector
{
    /**
     * @type string
     */
    public $name = 'DisplayLinkURI';

    /**
     * @type array
     */
    public $needed = array('a');

    /**
     * @param $token
     */
    public function handleElement(&$token)
    {
    }

    /**
     * @param HTMLPurifier_Token $token
     */
    public function handleEnd(&$token)
    {
        if (isset($token->start->attr['href'])) {
            $url = $token->start->attr['href'];
            unset($token->start->attr['href']);
            $token = array($token, new HTMLPurifier_Token_Text(" ($url)"));
        } else {
            // nothing to display
        }
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Composite strategy that runs multiple strategies on tokens.
 */
abstract class HTMLPurifier_Strategy_Composite extends HTMLPurifier_Strategy
{

    /**
     * List of strategies to run tokens through.
     * @type HTMLPurifier_Strategy[]
     */
    protected $strategies = array();

    /**
     * @param HTMLPurifier_Token[] $tokens
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return HTMLPurifier_Token[]
     */
    public function execute($tokens, $config, $context)
    {
        foreach ($this->strategies as $strategy) {
            $tokens = $strategy->execute($tokens, $config, $context);
        }
        return $tokens;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Takes tokens makes them well-formed (balance end tags, etc.)
 *
 * Specification of the armor attributes this strategy uses:
 *
 *      - MakeWellFormed_TagClosedError: This armor field is used to
 *        suppress tag closed errors for certain tokens [TagClosedSuppress],
 *        in particular, if a tag was generated automatically by HTML
 *        Purifier, we may rely on our infrastructure to close it for us
 *        and shouldn't report an error to the user [TagClosedAuto].
 */
class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
{

    /**
     * Array stream of tokens being processed.
     * @type HTMLPurifier_Token[]
     */
    protected $tokens;

    /**
     * Current token.
     * @type HTMLPurifier_Token
     */
    protected $token;

    /**
     * Zipper managing the true state.
     * @type HTMLPurifier_Zipper
     */
    protected $zipper;

    /**
     * Current nesting of elements.
     * @type array
     */
    protected $stack;

    /**
     * Injectors active in this stream processing.
     * @type HTMLPurifier_Injector[]
     */
    protected $injectors;

    /**
     * Current instance of HTMLPurifier_Config.
     * @type HTMLPurifier_Config
     */
    protected $config;

    /**
     * Current instance of HTMLPurifier_Context.
     * @type HTMLPurifier_Context
     */
    protected $context;

    /**
     * @param HTMLPurifier_Token[] $tokens
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return HTMLPurifier_Token[]
     * @throws HTMLPurifier_Exception
     */
    public function execute($tokens, $config, $context)
    {
        $definition = $config->getHTMLDefinition();

        // local variables
        $generator = new HTMLPurifier_Generator($config, $context);
        $escape_invalid_tags = $config->get('Core.EscapeInvalidTags');
        // used for autoclose early abortion
        $global_parent_allowed_elements = $definition->info_parent_def->child->getAllowedElements($config);
        $e = $context->get('ErrorCollector', true);
        $i = false; // injector index
        list($zipper, $token) = HTMLPurifier_Zipper::fromArray($tokens);
        if ($token === NULL) {
            return array();
        }
        $reprocess = false; // whether or not to reprocess the same token
        $stack = array();

        // member variables
        $this->stack =& $stack;
        $this->tokens =& $tokens;
        $this->token =& $token;
        $this->zipper =& $zipper;
        $this->config = $config;
        $this->context = $context;

        // context variables
        $context->register('CurrentNesting', $stack);
        $context->register('InputZipper', $zipper);
        $context->register('CurrentToken', $token);

        // -- begin INJECTOR --

        $this->injectors = array();

        $injectors = $config->getBatch('AutoFormat');
        $def_injectors = $definition->info_injector;
        $custom_injectors = $injectors['Custom'];
        unset($injectors['Custom']); // special case
        foreach ($injectors as $injector => $b) {
            // XXX: Fix with a legitimate lookup table of enabled filters
            if (strpos($injector, '.') !== false) {
                continue;
            }
            $injector = "HTMLPurifier_Injector_$injector";
            if (!$b) {
                continue;
            }
            $this->injectors[] = new $injector;
        }
        foreach ($def_injectors as $injector) {
            // assumed to be objects
            $this->injectors[] = $injector;
        }
        foreach ($custom_injectors as $injector) {
            if (!$injector) {
                continue;
            }
            if (is_string($injector)) {
                $injector = "HTMLPurifier_Injector_$injector";
                $injector = new $injector;
            }
            $this->injectors[] = $injector;
        }

        // give the injectors references to the definition and context
        // variables for performance reasons
        foreach ($this->injectors as $ix => $injector) {
            $error = $injector->prepare($config, $context);
            if (!$error) {
                continue;
            }
            array_splice($this->injectors, $ix, 1); // rm the injector
            trigger_error("Cannot enable {$injector->name} injector because $error is not allowed", E_USER_WARNING);
        }

        // -- end INJECTOR --

        // a note on reprocessing:
        //      In order to reduce code duplication, whenever some code needs
        //      to make HTML changes in order to make things "correct", the
        //      new HTML gets sent through the purifier, regardless of its
        //      status. This means that if we add a start token, because it
        //      was totally necessary, we don't have to update nesting; we just
        //      punt ($reprocess = true; continue;) and it does that for us.

        // isset is in loop because $tokens size changes during loop exec
        for (;;
             // only increment if we don't need to reprocess
             $reprocess ? $reprocess = false : $token = $zipper->next($token)) {

            // check for a rewind
            if (is_int($i)) {
                // possibility: disable rewinding if the current token has a
                // rewind set on it already. This would offer protection from
                // infinite loop, but might hinder some advanced rewinding.
                $rewind_offset = $this->injectors[$i]->getRewindOffset();
                if (is_int($rewind_offset)) {
                    for ($j = 0; $j < $rewind_offset; $j++) {
                        if (empty($zipper->front)) break;
                        $token = $zipper->prev($token);
                        // indicate that other injectors should not process this token,
                        // but we need to reprocess it.  See Note [Injector skips]
                        unset($token->skip[$i]);
                        $token->rewind = $i;
                        if ($token instanceof HTMLPurifier_Token_Start) {
                            array_pop($this->stack);
                        } elseif ($token instanceof HTMLPurifier_Token_End) {
                            $this->stack[] = $token->start;
                        }
                    }
                }
                $i = false;
            }

            // handle case of document end
            if ($token === NULL) {
                // kill processing if stack is empty
                if (empty($this->stack)) {
                    break;
                }

                // peek
                $top_nesting = array_pop($this->stack);
                $this->stack[] = $top_nesting;

                // send error [TagClosedSuppress]
                if ($e && !isset($top_nesting->armor['MakeWellFormed_TagClosedError'])) {
                    $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by document end', $top_nesting);
                }

                // append, don't splice, since this is the end
                $token = new HTMLPurifier_Token_End($top_nesting->name);

                // punt!
                $reprocess = true;
                continue;
            }

            //echo '<br>'; printZipper($zipper, $token);//printTokens($this->stack);
            //flush();

            // quick-check: if it's not a tag, no need to process
            if (empty($token->is_tag)) {
                if ($token instanceof HTMLPurifier_Token_Text) {
                    foreach ($this->injectors as $i => $injector) {
                        if (isset($token->skip[$i])) {
                            // See Note [Injector skips]
                            continue;
                        }
                        if ($token->rewind !== null && $token->rewind !== $i) {
                            continue;
                        }
                        // XXX fuckup
                        $r = $token;
                        $injector->handleText($r);
                        $token = $this->processToken($r, $i);
                        $reprocess = true;
                        break;
                    }
                }
                // another possibility is a comment
                continue;
            }

            if (isset($definition->info[$token->name])) {
                $type = $definition->info[$token->name]->child->type;
            } else {
                $type = false; // Type is unknown, treat accordingly
            }

            // quick tag checks: anything that's *not* an end tag
            $ok = false;
            if ($type === 'empty' && $token instanceof HTMLPurifier_Token_Start) {
                // claims to be a start tag but is empty
                $token = new HTMLPurifier_Token_Empty(
                    $token->name,
                    $token->attr,
                    $token->line,
                    $token->col,
                    $token->armor
                );
                $ok = true;
            } elseif ($type && $type !== 'empty' && $token instanceof HTMLPurifier_Token_Empty) {
                // claims to be empty but really is a start tag
                // NB: this assignment is required
                $old_token = $token;
                $token = new HTMLPurifier_Token_End($token->name);
                $token = $this->insertBefore(
                    new HTMLPurifier_Token_Start($old_token->name, $old_token->attr, $old_token->line, $old_token->col, $old_token->armor)
                );
                // punt (since we had to modify the input stream in a non-trivial way)
                $reprocess = true;
                continue;
            } elseif ($token instanceof HTMLPurifier_Token_Empty) {
                // real empty token
                $ok = true;
            } elseif ($token instanceof HTMLPurifier_Token_Start) {
                // start tag

                // ...unless they also have to close their parent
                if (!empty($this->stack)) {

                    // Performance note: you might think that it's rather
                    // inefficient, recalculating the autoclose information
                    // for every tag that a token closes (since when we
                    // do an autoclose, we push a new token into the
                    // stream and then /process/ that, before
                    // re-processing this token.)  But this is
                    // necessary, because an injector can make an
                    // arbitrary transformations to the autoclosing
                    // tokens we introduce, so things may have changed
                    // in the meantime.  Also, doing the inefficient thing is
                    // "easy" to reason about (for certain perverse definitions
                    // of "easy")

                    $parent = array_pop($this->stack);
                    $this->stack[] = $parent;

                    $parent_def = null;
                    $parent_elements = null;
                    $autoclose = false;
                    if (isset($definition->info[$parent->name])) {
                        $parent_def = $definition->info[$parent->name];
                        $parent_elements = $parent_def->child->getAllowedElements($config);
                        $autoclose = !isset($parent_elements[$token->name]);
                    }

                    if ($autoclose && $definition->info[$token->name]->wrap) {
                        // Check if an element can be wrapped by another
                        // element to make it valid in a context (for
                        // example, <ul><ul> needs a <li> in between)
                        $wrapname = $definition->info[$token->name]->wrap;
                        $wrapdef = $definition->info[$wrapname];
                        $elements = $wrapdef->child->getAllowedElements($config);
                        if (isset($elements[$token->name]) && isset($parent_elements[$wrapname])) {
                            $newtoken = new HTMLPurifier_Token_Start($wrapname);
                            $token = $this->insertBefore($newtoken);
                            $reprocess = true;
                            continue;
                        }
                    }

                    $carryover = false;
                    if ($autoclose && $parent_def->formatting) {
                        $carryover = true;
                    }

                    if ($autoclose) {
                        // check if this autoclose is doomed to fail
                        // (this rechecks $parent, which his harmless)
                        $autoclose_ok = isset($global_parent_allowed_elements[$token->name]);
                        if (!$autoclose_ok) {
                            foreach ($this->stack as $ancestor) {
                                $elements = $definition->info[$ancestor->name]->child->getAllowedElements($config);
                                if (isset($elements[$token->name])) {
                                    $autoclose_ok = true;
                                    break;
                                }
                                if ($definition->info[$token->name]->wrap) {
                                    $wrapname = $definition->info[$token->name]->wrap;
                                    $wrapdef = $definition->info[$wrapname];
                                    $wrap_elements = $wrapdef->child->getAllowedElements($config);
                                    if (isset($wrap_elements[$token->name]) && isset($elements[$wrapname])) {
                                        $autoclose_ok = true;
                                        break;
                                    }
                                }
                            }
                        }
                        if ($autoclose_ok) {
                            // errors need to be updated
                            $new_token = new HTMLPurifier_Token_End($parent->name);
                            $new_token->start = $parent;
                            // [TagClosedSuppress]
                            if ($e && !isset($parent->armor['MakeWellFormed_TagClosedError'])) {
                                if (!$carryover) {
                                    $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag auto closed', $parent);
                                } else {
                                    $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag carryover', $parent);
                                }
                            }
                            if ($carryover) {
                                $element = clone $parent;
                                // [TagClosedAuto]
                                $element->armor['MakeWellFormed_TagClosedError'] = true;
                                $element->carryover = true;
                                $token = $this->processToken(array($new_token, $token, $element));
                            } else {
                                $token = $this->insertBefore($new_token);
                            }
                        } else {
                            $token = $this->remove();
                        }
                        $reprocess = true;
                        continue;
                    }

                }
                $ok = true;
            }

            if ($ok) {
                foreach ($this->injectors as $i => $injector) {
                    if (isset($token->skip[$i])) {
                        // See Note [Injector skips]
                        continue;
                    }
                    if ($token->rewind !== null && $token->rewind !== $i) {
                        continue;
                    }
                    $r = $token;
                    $injector->handleElement($r);
                    $token = $this->processToken($r, $i);
                    $reprocess = true;
                    break;
                }
                if (!$reprocess) {
                    // ah, nothing interesting happened; do normal processing
                    if ($token instanceof HTMLPurifier_Token_Start) {
                        $this->stack[] = $token;
                    } elseif ($token instanceof HTMLPurifier_Token_End) {
                        throw new HTMLPurifier_Exception(
                            'Improper handling of end tag in start code; possible error in MakeWellFormed'
                        );
                    }
                }
                continue;
            }

            // sanity check: we should be dealing with a closing tag
            if (!$token instanceof HTMLPurifier_Token_End) {
                throw new HTMLPurifier_Exception('Unaccounted for tag token in input stream, bug in HTML Purifier');
            }

            // make sure that we have something open
            if (empty($this->stack)) {
                if ($escape_invalid_tags) {
                    if ($e) {
                        $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag to text');
                    }
                    $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token));
                } else {
                    if ($e) {
                        $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag removed');
                    }
                    $token = $this->remove();
                }
                $reprocess = true;
                continue;
            }

            // first, check for the simplest case: everything closes neatly.
            // Eventually, everything passes through here; if there are problems
            // we modify the input stream accordingly and then punt, so that
            // the tokens get processed again.
            $current_parent = array_pop($this->stack);
            if ($current_parent->name == $token->name) {
                $token->start = $current_parent;
                foreach ($this->injectors as $i => $injector) {
                    if (isset($token->skip[$i])) {
                        // See Note [Injector skips]
                        continue;
                    }
                    if ($token->rewind !== null && $token->rewind !== $i) {
                        continue;
                    }
                    $r = $token;
                    $injector->handleEnd($r);
                    $token = $this->processToken($r, $i);
                    $this->stack[] = $current_parent;
                    $reprocess = true;
                    break;
                }
                continue;
            }

            // okay, so we're trying to close the wrong tag

            // undo the pop previous pop
            $this->stack[] = $current_parent;

            // scroll back the entire nest, trying to find our tag.
            // (feature could be to specify how far you'd like to go)
            $size = count($this->stack);
            // -2 because -1 is the last element, but we already checked that
            $skipped_tags = false;
            for ($j = $size - 2; $j >= 0; $j--) {
                if ($this->stack[$j]->name == $token->name) {
                    $skipped_tags = array_slice($this->stack, $j);
                    break;
                }
            }

            // we didn't find the tag, so remove
            if ($skipped_tags === false) {
                if ($escape_invalid_tags) {
                    if ($e) {
                        $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag to text');
                    }
                    $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token));
                } else {
                    if ($e) {
                        $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag removed');
                    }
                    $token = $this->remove();
                }
                $reprocess = true;
                continue;
            }

            // do errors, in REVERSE $j order: a,b,c with </a></b></c>
            $c = count($skipped_tags);
            if ($e) {
                for ($j = $c - 1; $j > 0; $j--) {
                    // notice we exclude $j == 0, i.e. the current ending tag, from
                    // the errors... [TagClosedSuppress]
                    if (!isset($skipped_tags[$j]->armor['MakeWellFormed_TagClosedError'])) {
                        $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by element end', $skipped_tags[$j]);
                    }
                }
            }

            // insert tags, in FORWARD $j order: c,b,a with </a></b></c>
            $replace = array($token);
            for ($j = 1; $j < $c; $j++) {
                // ...as well as from the insertions
                $new_token = new HTMLPurifier_Token_End($skipped_tags[$j]->name);
                $new_token->start = $skipped_tags[$j];
                array_unshift($replace, $new_token);
                if (isset($definition->info[$new_token->name]) && $definition->info[$new_token->name]->formatting) {
                    // [TagClosedAuto]
                    $element = clone $skipped_tags[$j];
                    $element->carryover = true;
                    $element->armor['MakeWellFormed_TagClosedError'] = true;
                    $replace[] = $element;
                }
            }
            $token = $this->processToken($replace);
            $reprocess = true;
            continue;
        }

        $context->destroy('CurrentToken');
        $context->destroy('CurrentNesting');
        $context->destroy('InputZipper');

        unset($this->injectors, $this->stack, $this->tokens);
        return $zipper->toArray($token);
    }

    /**
     * Processes arbitrary token values for complicated substitution patterns.
     * In general:
     *
     * If $token is an array, it is a list of tokens to substitute for the
     * current token. These tokens then get individually processed. If there
     * is a leading integer in the list, that integer determines how many
     * tokens from the stream should be removed.
     *
     * If $token is a regular token, it is swapped with the current token.
     *
     * If $token is false, the current token is deleted.
     *
     * If $token is an integer, that number of tokens (with the first token
     * being the current one) will be deleted.
     *
     * @param HTMLPurifier_Token|array|int|bool $token Token substitution value
     * @param HTMLPurifier_Injector|int $injector Injector that performed the substitution; default is if
     *        this is not an injector related operation.
     * @throws HTMLPurifier_Exception
     */
    protected function processToken($token, $injector = -1)
    {
        // Zend OpCache miscompiles $token = array($token), so
        // avoid this pattern.  See: https://github.com/ezyang/htmlpurifier/issues/108

        // normalize forms of token
        if (is_object($token)) {
            $tmp = $token;
            $token = array(1, $tmp);
        }
        if (is_int($token)) {
            $tmp = $token;
            $token = array($tmp);
        }
        if ($token === false) {
            $token = array(1);
        }
        if (!is_array($token)) {
            throw new HTMLPurifier_Exception('Invalid token type from injector');
        }
        if (!is_int($token[0])) {
            array_unshift($token, 1);
        }
        if ($token[0] === 0) {
            throw new HTMLPurifier_Exception('Deleting zero tokens is not valid');
        }

        // $token is now an array with the following form:
        // array(number nodes to delete, new node 1, new node 2, ...)

        $delete = array_shift($token);
        list($old, $r) = $this->zipper->splice($this->token, $delete, $token);

        if ($injector > -1) {
            // See Note [Injector skips]
            // Determine appropriate skips.  Here's what the code does:
            //  *If* we deleted one or more tokens, copy the skips
            //  of those tokens into the skips of the new tokens (in $token).
            //  Also, mark the newly inserted tokens as having come from
            //  $injector.
            $oldskip = isset($old[0]) ? $old[0]->skip : array();
            foreach ($token as $object) {
                $object->skip = $oldskip;
                $object->skip[$injector] = true;
            }
        }

        return $r;

    }

    /**
     * Inserts a token before the current token. Cursor now points to
     * this token.  You must reprocess after this.
     * @param HTMLPurifier_Token $token
     */
    private function insertBefore($token)
    {
        // NB not $this->zipper->insertBefore(), due to positioning
        // differences
        $splice = $this->zipper->splice($this->token, 0, array($token));

        return $splice[1];
    }

    /**
     * Removes current token. Cursor now points to new token occupying previously
     * occupied space.  You must reprocess after this.
     */
    private function remove()
    {
        return $this->zipper->delete();
    }
}

// Note [Injector skips]
// ~~~~~~~~~~~~~~~~~~~~~
// When I originally designed this class, the idea behind the 'skip'
// property of HTMLPurifier_Token was to help avoid infinite loops
// in injector processing.  For example, suppose you wrote an injector
// that bolded swear words.  Naively, you might write it so that
// whenever you saw ****, you replaced it with <strong>****</strong>.
//
// When this happens, we will reprocess all of the tokens with the
// other injectors.  Now there is an opportunity for infinite loop:
// if we rerun the swear-word injector on these tokens, we might
// see **** and then reprocess again to get
// <strong><strong>****</strong></strong> ad infinitum.
//
// Thus, the idea of a skip is that once we process a token with
// an injector, we mark all of those tokens as having "come from"
// the injector, and we never run the injector again on these
// tokens.
//
// There were two more complications, however:
//
//  - With HTMLPurifier_Injector_RemoveEmpty, we noticed that if
//    you had <b><i></i></b>, after you removed the <i></i>, you
//    really would like this injector to go back and reprocess
//    the <b> tag, discovering that it is now empty and can be
//    removed.  So we reintroduced the possibility of infinite looping
//    by adding a "rewind" function, which let you go back to an
//    earlier point in the token stream and reprocess it with injectors.
//    Needless to say, we need to UN-skip the token so it gets
//    reprocessed.
//
//  - Suppose that you successfuly process a token, replace it with
//    one with your skip mark, but now another injector wants to
//    process the skipped token with another token.  Should you continue
//    to skip that new token, or reprocess it?  If you reprocess,
//    you can end up with an infinite loop where one injector converts
//    <a> to <b>, and then another injector converts it back.  So
//    we inherit the skips, but for some reason, I thought that we
//    should inherit the skip from the first token of the token
//    that we deleted.  Why?  Well, it seems to work OK.
//
// If I were to redesign this functionality, I would absolutely not
// go about doing it this way: the semantics are just not very well
// defined, and in any case you probably wanted to operate on trees,
// not token streams.

// vim: et sw=4 sts=4
<?php

/**
 * Removes all unrecognized tags from the list of tokens.
 *
 * This strategy iterates through all the tokens and removes unrecognized
 * tokens. If a token is not recognized but a TagTransform is defined for
 * that element, the element will be transformed accordingly.
 */

class HTMLPurifier_Strategy_RemoveForeignElements extends HTMLPurifier_Strategy
{

    /**
     * @param HTMLPurifier_Token[] $tokens
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array|HTMLPurifier_Token[]
     */
    public function execute($tokens, $config, $context)
    {
        $definition = $config->getHTMLDefinition();
        $generator = new HTMLPurifier_Generator($config, $context);
        $result = array();

        $escape_invalid_tags = $config->get('Core.EscapeInvalidTags');
        $remove_invalid_img = $config->get('Core.RemoveInvalidImg');

        // currently only used to determine if comments should be kept
        $trusted = $config->get('HTML.Trusted');
        $comment_lookup = $config->get('HTML.AllowedComments');
        $comment_regexp = $config->get('HTML.AllowedCommentsRegexp');
        $check_comments = $comment_lookup !== array() || $comment_regexp !== null;

        $remove_script_contents = $config->get('Core.RemoveScriptContents');
        $hidden_elements = $config->get('Core.HiddenElements');

        // remove script contents compatibility
        if ($remove_script_contents === true) {
            $hidden_elements['script'] = true;
        } elseif ($remove_script_contents === false && isset($hidden_elements['script'])) {
            unset($hidden_elements['script']);
        }

        $attr_validator = new HTMLPurifier_AttrValidator();

        // removes tokens until it reaches a closing tag with its value
        $remove_until = false;

        // converts comments into text tokens when this is equal to a tag name
        $textify_comments = false;

        $token = false;
        $context->register('CurrentToken', $token);

        $e = false;
        if ($config->get('Core.CollectErrors')) {
            $e =& $context->get('ErrorCollector');
        }

        foreach ($tokens as $token) {
            if ($remove_until) {
                if (empty($token->is_tag) || $token->name !== $remove_until) {
                    continue;
                }
            }
            if (!empty($token->is_tag)) {
                // DEFINITION CALL

                // before any processing, try to transform the element
                if (isset($definition->info_tag_transform[$token->name])) {
                    $original_name = $token->name;
                    // there is a transformation for this tag
                    // DEFINITION CALL
                    $token = $definition->
                        info_tag_transform[$token->name]->transform($token, $config, $context);
                    if ($e) {
                        $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Tag transform', $original_name);
                    }
                }

                if (isset($definition->info[$token->name])) {
                    // mostly everything's good, but
                    // we need to make sure required attributes are in order
                    if (($token instanceof HTMLPurifier_Token_Start || $token instanceof HTMLPurifier_Token_Empty) &&
                        $definition->info[$token->name]->required_attr &&
                        ($token->name != 'img' || $remove_invalid_img) // ensure config option still works
                    ) {
                        $attr_validator->validateToken($token, $config, $context);
                        $ok = true;
                        foreach ($definition->info[$token->name]->required_attr as $name) {
                            if (!isset($token->attr[$name])) {
                                $ok = false;
                                break;
                            }
                        }
                        if (!$ok) {
                            if ($e) {
                                $e->send(
                                    E_ERROR,
                                    'Strategy_RemoveForeignElements: Missing required attribute',
                                    $name
                                );
                            }
                            continue;
                        }
                        $token->armor['ValidateAttributes'] = true;
                    }

                    if (isset($hidden_elements[$token->name]) && $token instanceof HTMLPurifier_Token_Start) {
                        $textify_comments = $token->name;
                    } elseif ($token->name === $textify_comments && $token instanceof HTMLPurifier_Token_End) {
                        $textify_comments = false;
                    }

                } elseif ($escape_invalid_tags) {
                    // invalid tag, generate HTML representation and insert in
                    if ($e) {
                        $e->send(E_WARNING, 'Strategy_RemoveForeignElements: Foreign element to text');
                    }
                    $token = new HTMLPurifier_Token_Text(
                        $generator->generateFromToken($token)
                    );
                } else {
                    // check if we need to destroy all of the tag's children
                    // CAN BE GENERICIZED
                    if (isset($hidden_elements[$token->name])) {
                        if ($token instanceof HTMLPurifier_Token_Start) {
                            $remove_until = $token->name;
                        } elseif ($token instanceof HTMLPurifier_Token_Empty) {
                            // do nothing: we're still looking
                        } else {
                            $remove_until = false;
                        }
                        if ($e) {
                            $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign meta element removed');
                        }
                    } else {
                        if ($e) {
                            $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign element removed');
                        }
                    }
                    continue;
                }
            } elseif ($token instanceof HTMLPurifier_Token_Comment) {
                // textify comments in script tags when they are allowed
                if ($textify_comments !== false) {
                    $data = $token->data;
                    $token = new HTMLPurifier_Token_Text($data);
                } elseif ($trusted || $check_comments) {
                    // always cleanup comments
                    $trailing_hyphen = false;
                    if ($e) {
                        // perform check whether or not there's a trailing hyphen
                        if (substr($token->data, -1) == '-') {
                            $trailing_hyphen = true;
                        }
                    }
                    $token->data = rtrim($token->data, '-');
                    $found_double_hyphen = false;
                    while (strpos($token->data, '--') !== false) {
                        $found_double_hyphen = true;
                        $token->data = str_replace('--', '-', $token->data);
                    }
                    if ($trusted || !empty($comment_lookup[trim($token->data)]) ||
                        ($comment_regexp !== null && preg_match($comment_regexp, trim($token->data)))) {
                        // OK good
                        if ($e) {
                            if ($trailing_hyphen) {
                                $e->send(
                                    E_NOTICE,
                                    'Strategy_RemoveForeignElements: Trailing hyphen in comment removed'
                                );
                            }
                            if ($found_double_hyphen) {
                                $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Hyphens in comment collapsed');
                            }
                        }
                    } else {
                        if ($e) {
                            $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed');
                        }
                        continue;
                    }
                } else {
                    // strip comments
                    if ($e) {
                        $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed');
                    }
                    continue;
                }
            } elseif ($token instanceof HTMLPurifier_Token_Text) {
            } else {
                continue;
            }
            $result[] = $token;
        }
        if ($remove_until && $e) {
            // we removed tokens until the end, throw error
            $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Token removed to end', $remove_until);
        }
        $context->destroy('CurrentToken');
        return $result;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Validate all attributes in the tokens.
 */

class HTMLPurifier_Strategy_ValidateAttributes extends HTMLPurifier_Strategy
{

    /**
     * @param HTMLPurifier_Token[] $tokens
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return HTMLPurifier_Token[]
     */
    public function execute($tokens, $config, $context)
    {
        // setup validator
        $validator = new HTMLPurifier_AttrValidator();

        $token = false;
        $context->register('CurrentToken', $token);

        foreach ($tokens as $key => $token) {

            // only process tokens that have attributes,
            //   namely start and empty tags
            if (!$token instanceof HTMLPurifier_Token_Start && !$token instanceof HTMLPurifier_Token_Empty) {
                continue;
            }

            // skip tokens that are armored
            if (!empty($token->armor['ValidateAttributes'])) {
                continue;
            }

            // note that we have no facilities here for removing tokens
            $validator->validateToken($token, $config, $context);
        }
        $context->destroy('CurrentToken');
        return $tokens;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Takes a well formed list of tokens and fixes their nesting.
 *
 * HTML elements dictate which elements are allowed to be their children,
 * for example, you can't have a p tag in a span tag.  Other elements have
 * much more rigorous definitions: tables, for instance, require a specific
 * order for their elements.  There are also constraints not expressible by
 * document type definitions, such as the chameleon nature of ins/del
 * tags and global child exclusions.
 *
 * The first major objective of this strategy is to iterate through all
 * the nodes and determine whether or not their children conform to the
 * element's definition.  If they do not, the child definition may
 * optionally supply an amended list of elements that is valid or
 * require that the entire node be deleted (and the previous node
 * rescanned).
 *
 * The second objective is to ensure that explicitly excluded elements of
 * an element do not appear in its children.  Code that accomplishes this
 * task is pervasive through the strategy, though the two are distinct tasks
 * and could, theoretically, be seperated (although it's not recommended).
 *
 * @note Whether or not unrecognized children are silently dropped or
 *       translated into text depends on the child definitions.
 *
 * @todo Enable nodes to be bubbled out of the structure.  This is
 *       easier with our new algorithm.
 */

class HTMLPurifier_Strategy_FixNesting extends HTMLPurifier_Strategy
{

    /**
     * @param HTMLPurifier_Token[] $tokens
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array|HTMLPurifier_Token[]
     */
    public function execute($tokens, $config, $context)
    {

        //####################################################################//
        // Pre-processing

        // O(n) pass to convert to a tree, so that we can efficiently
        // refer to substrings
        $top_node = HTMLPurifier_Arborize::arborize($tokens, $config, $context);

        // get a copy of the HTML definition
        $definition = $config->getHTMLDefinition();

        $excludes_enabled = !$config->get('Core.DisableExcludes');

        // setup the context variable 'IsInline', for chameleon processing
        // is 'false' when we are not inline, 'true' when it must always
        // be inline, and an integer when it is inline for a certain
        // branch of the document tree
        $is_inline = $definition->info_parent_def->descendants_are_inline;
        $context->register('IsInline', $is_inline);

        // setup error collector
        $e =& $context->get('ErrorCollector', true);

        //####################################################################//
        // Loop initialization

        // stack that contains all elements that are excluded
        // it is organized by parent elements, similar to $stack,
        // but it is only populated when an element with exclusions is
        // processed, i.e. there won't be empty exclusions.
        $exclude_stack = array($definition->info_parent_def->excludes);

        // variable that contains the start token while we are processing
        // nodes. This enables error reporting to do its job
        $node = $top_node;
        // dummy token
        list($token, $d) = $node->toTokenPair();
        $context->register('CurrentNode', $node);
        $context->register('CurrentToken', $token);

        //####################################################################//
        // Loop

        // We need to implement a post-order traversal iteratively, to
        // avoid running into stack space limits.  This is pretty tricky
        // to reason about, so we just manually stack-ify the recursive
        // variant:
        //
        //  function f($node) {
        //      foreach ($node->children as $child) {
        //          f($child);
        //      }
        //      validate($node);
        //  }
        //
        // Thus, we will represent a stack frame as array($node,
        // $is_inline, stack of children)
        // e.g. array_reverse($node->children) - already processed
        // children.

        $parent_def = $definition->info_parent_def;
        $stack = array(
            array($top_node,
                  $parent_def->descendants_are_inline,
                  $parent_def->excludes, // exclusions
                  0)
            );

        while (!empty($stack)) {
            list($node, $is_inline, $excludes, $ix) = array_pop($stack);
            // recursive call
            $go = false;
            $def = empty($stack) ? $definition->info_parent_def : $definition->info[$node->name];
            while (isset($node->children[$ix])) {
                $child = $node->children[$ix++];
                if ($child instanceof HTMLPurifier_Node_Element) {
                    $go = true;
                    $stack[] = array($node, $is_inline, $excludes, $ix);
                    $stack[] = array($child,
                        // ToDo: I don't think it matters if it's def or
                        // child_def, but double check this...
                        $is_inline || $def->descendants_are_inline,
                        empty($def->excludes) ? $excludes
                                              : array_merge($excludes, $def->excludes),
                        0);
                    break;
                }
            };
            if ($go) continue;
            list($token, $d) = $node->toTokenPair();
            // base case
            if ($excludes_enabled && isset($excludes[$node->name])) {
                $node->dead = true;
                if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node excluded');
            } else {
                // XXX I suppose it would be slightly more efficient to
                // avoid the allocation here and have children
                // strategies handle it
                $children = array();
                foreach ($node->children as $child) {
                    if (!$child->dead) $children[] = $child;
                }
                $result = $def->child->validateChildren($children, $config, $context);
                if ($result === true) {
                    // nop
                    $node->children = $children;
                } elseif ($result === false) {
                    $node->dead = true;
                    if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node removed');
                } else {
                    $node->children = $result;
                    if ($e) {
                        // XXX This will miss mutations of internal nodes. Perhaps defer to the child validators
                        if (empty($result) && !empty($children)) {
                            $e->send(E_ERROR, 'Strategy_FixNesting: Node contents removed');
                        } else if ($result != $children) {
                            $e->send(E_WARNING, 'Strategy_FixNesting: Node reorganized');
                        }
                    }
                }
            }
        }

        //####################################################################//
        // Post-processing

        // remove context variables
        $context->destroy('IsInline');
        $context->destroy('CurrentNode');
        $context->destroy('CurrentToken');

        //####################################################################//
        // Return

        return HTMLPurifier_Arborize::flatten($node, $config, $context);
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Core strategy composed of the big four strategies.
 */
class HTMLPurifier_Strategy_Core extends HTMLPurifier_Strategy_Composite
{
    public function __construct()
    {
        $this->strategies[] = new HTMLPurifier_Strategy_RemoveForeignElements();
        $this->strategies[] = new HTMLPurifier_Strategy_MakeWellFormed();
        $this->strategies[] = new HTMLPurifier_Strategy_FixNesting();
        $this->strategies[] = new HTMLPurifier_Strategy_ValidateAttributes();
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Represents a document type, contains information on which modules
 * need to be loaded.
 * @note This class is inspected by Printer_HTMLDefinition->renderDoctype.
 *       If structure changes, please update that function.
 */
class HTMLPurifier_Doctype
{
    /**
     * Full name of doctype
     * @type string
     */
    public $name;

    /**
     * List of standard modules (string identifiers or literal objects)
     * that this doctype uses
     * @type array
     */
    public $modules = array();

    /**
     * List of modules to use for tidying up code
     * @type array
     */
    public $tidyModules = array();

    /**
     * Is the language derived from XML (i.e. XHTML)?
     * @type bool
     */
    public $xml = true;

    /**
     * List of aliases for this doctype
     * @type array
     */
    public $aliases = array();

    /**
     * Public DTD identifier
     * @type string
     */
    public $dtdPublic;

    /**
     * System DTD identifier
     * @type string
     */
    public $dtdSystem;

    public function __construct(
        $name = null,
        $xml = true,
        $modules = array(),
        $tidyModules = array(),
        $aliases = array(),
        $dtd_public = null,
        $dtd_system = null
    ) {
        $this->name         = $name;
        $this->xml          = $xml;
        $this->modules      = $modules;
        $this->tidyModules  = $tidyModules;
        $this->aliases      = $aliases;
        $this->dtdPublic    = $dtd_public;
        $this->dtdSystem    = $dtd_system;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Custom validation class, accepts DTD child definitions
 *
 * @warning Currently this class is an all or nothing proposition, that is,
 *          it will only give a bool return value.
 */
class HTMLPurifier_ChildDef_Custom extends HTMLPurifier_ChildDef
{
    /**
     * @type string
     */
    public $type = 'custom';

    /**
     * @type bool
     */
    public $allow_empty = false;

    /**
     * Allowed child pattern as defined by the DTD.
     * @type string
     */
    public $dtd_regex;

    /**
     * PCRE regex derived from $dtd_regex.
     * @type string
     */
    private $_pcre_regex;

    /**
     * @param $dtd_regex Allowed child pattern from the DTD
     */
    public function __construct($dtd_regex)
    {
        $this->dtd_regex = $dtd_regex;
        $this->_compileRegex();
    }

    /**
     * Compiles the PCRE regex from a DTD regex ($dtd_regex to $_pcre_regex)
     */
    protected function _compileRegex()
    {
        $raw = str_replace(' ', '', $this->dtd_regex);
        if ($raw{0} != '(') {
            $raw = "($raw)";
        }
        $el = '[#a-zA-Z0-9_.-]+';
        $reg = $raw;

        // COMPLICATED! AND MIGHT BE BUGGY! I HAVE NO CLUE WHAT I'M
        // DOING! Seriously: if there's problems, please report them.

        // collect all elements into the $elements array
        preg_match_all("/$el/", $reg, $matches);
        foreach ($matches[0] as $match) {
            $this->elements[$match] = true;
        }

        // setup all elements as parentheticals with leading commas
        $reg = preg_replace("/$el/", '(,\\0)', $reg);

        // remove commas when they were not solicited
        $reg = preg_replace("/([^,(|]\(+),/", '\\1', $reg);

        // remove all non-paranthetical commas: they are handled by first regex
        $reg = preg_replace("/,\(/", '(', $reg);

        $this->_pcre_regex = $reg;
    }

    /**
     * @param HTMLPurifier_Node[] $children
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool
     */
    public function validateChildren($children, $config, $context)
    {
        $list_of_children = '';
        $nesting = 0; // depth into the nest
        foreach ($children as $node) {
            if (!empty($node->is_whitespace)) {
                continue;
            }
            $list_of_children .= $node->name . ',';
        }
        // add leading comma to deal with stray comma declarations
        $list_of_children = ',' . rtrim($list_of_children, ',');
        $okay =
            preg_match(
                '/^,?' . $this->_pcre_regex . '$/',
                $list_of_children
            );
        return (bool)$okay;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Definition that disallows all elements.
 * @warning validateChildren() in this class is actually never called, because
 *          empty elements are corrected in HTMLPurifier_Strategy_MakeWellFormed
 *          before child definitions are parsed in earnest by
 *          HTMLPurifier_Strategy_FixNesting.
 */
class HTMLPurifier_ChildDef_Empty extends HTMLPurifier_ChildDef
{
    /**
     * @type bool
     */
    public $allow_empty = true;

    /**
     * @type string
     */
    public $type = 'empty';

    public function __construct()
    {
    }

    /**
     * @param HTMLPurifier_Node[] $children
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    public function validateChildren($children, $config, $context)
    {
        return array();
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Definition for list containers ul and ol.
 *
 * What does this do?  The big thing is to handle ol/ul at the top
 * level of list nodes, which should be handled specially by /folding/
 * them into the previous list node.  We generally shouldn't ever
 * see other disallowed elements, because the autoclose behavior
 * in MakeWellFormed handles it.
 */
class HTMLPurifier_ChildDef_List extends HTMLPurifier_ChildDef
{
    /**
     * @type string
     */
    public $type = 'list';
    /**
     * @type array
     */
    // lying a little bit, so that we can handle ul and ol ourselves
    // XXX: This whole business with 'wrap' is all a bit unsatisfactory
    public $elements = array('li' => true, 'ul' => true, 'ol' => true);

    /**
     * @param array $children
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    public function validateChildren($children, $config, $context)
    {
        // Flag for subclasses
        $this->whitespace = false;

        // if there are no tokens, delete parent node
        if (empty($children)) {
            return false;
        }

        // if li is not allowed, delete parent node
        if (!isset($config->getHTMLDefinition()->info['li'])) {
            trigger_error("Cannot allow ul/ol without allowing li", E_USER_WARNING);
            return false;
        }

        // the new set of children
        $result = array();

        // a little sanity check to make sure it's not ALL whitespace
        $all_whitespace = true;

        $current_li = null;

        foreach ($children as $node) {
            if (!empty($node->is_whitespace)) {
                $result[] = $node;
                continue;
            }
            $all_whitespace = false; // phew, we're not talking about whitespace

            if ($node->name === 'li') {
                // good
                $current_li = $node;
                $result[] = $node;
            } else {
                // we want to tuck this into the previous li
                // Invariant: we expect the node to be ol/ul
                // ToDo: Make this more robust in the case of not ol/ul
                // by distinguishing between existing li and li created
                // to handle non-list elements; non-list elements should
                // not be appended to an existing li; only li created
                // for non-list. This distinction is not currently made.
                if ($current_li === null) {
                    $current_li = new HTMLPurifier_Node_Element('li');
                    $result[] = $current_li;
                }
                $current_li->children[] = $node;
                $current_li->empty = false; // XXX fascinating! Check for this error elsewhere ToDo
            }
        }
        if (empty($result)) {
            return false;
        }
        if ($all_whitespace) {
            return false;
        }
        return $result;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Takes the contents of blockquote when in strict and reformats for validation.
 */
class HTMLPurifier_ChildDef_StrictBlockquote extends HTMLPurifier_ChildDef_Required
{
    /**
     * @type array
     */
    protected $real_elements;

    /**
     * @type array
     */
    protected $fake_elements;

    /**
     * @type bool
     */
    public $allow_empty = true;

    /**
     * @type string
     */
    public $type = 'strictblockquote';

    /**
     * @type bool
     */
    protected $init = false;

    /**
     * @param HTMLPurifier_Config $config
     * @return array
     * @note We don't want MakeWellFormed to auto-close inline elements since
     *       they might be allowed.
     */
    public function getAllowedElements($config)
    {
        $this->init($config);
        return $this->fake_elements;
    }

    /**
     * @param array $children
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    public function validateChildren($children, $config, $context)
    {
        $this->init($config);

        // trick the parent class into thinking it allows more
        $this->elements = $this->fake_elements;
        $result = parent::validateChildren($children, $config, $context);
        $this->elements = $this->real_elements;

        if ($result === false) {
            return array();
        }
        if ($result === true) {
            $result = $children;
        }

        $def = $config->getHTMLDefinition();
        $block_wrap_name = $def->info_block_wrapper;
        $block_wrap = false;
        $ret = array();

        foreach ($result as $node) {
            if ($block_wrap === false) {
                if (($node instanceof HTMLPurifier_Node_Text && !$node->is_whitespace) ||
                    ($node instanceof HTMLPurifier_Node_Element && !isset($this->elements[$node->name]))) {
                        $block_wrap = new HTMLPurifier_Node_Element($def->info_block_wrapper);
                        $ret[] = $block_wrap;
                }
            } else {
                if ($node instanceof HTMLPurifier_Node_Element && isset($this->elements[$node->name])) {
                    $block_wrap = false;

                }
            }
            if ($block_wrap) {
                $block_wrap->children[] = $node;
            } else {
                $ret[] = $node;
            }
        }
        return $ret;
    }

    /**
     * @param HTMLPurifier_Config $config
     */
    private function init($config)
    {
        if (!$this->init) {
            $def = $config->getHTMLDefinition();
            // allow all inline elements
            $this->real_elements = $this->elements;
            $this->fake_elements = $def->info_content_sets['Flow'];
            $this->fake_elements['#PCDATA'] = true;
            $this->init = true;
        }
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Definition that allows a set of elements, and allows no children.
 * @note This is a hack to reuse code from HTMLPurifier_ChildDef_Required,
 *       really, one shouldn't inherit from the other.  Only altered behavior
 *       is to overload a returned false with an array.  Thus, it will never
 *       return false.
 */
class HTMLPurifier_ChildDef_Optional extends HTMLPurifier_ChildDef_Required
{
    /**
     * @type bool
     */
    public $allow_empty = true;

    /**
     * @type string
     */
    public $type = 'optional';

    /**
     * @param array $children
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    public function validateChildren($children, $config, $context)
    {
        $result = parent::validateChildren($children, $config, $context);
        // we assume that $children is not modified
        if ($result === false) {
            if (empty($children)) {
                return true;
            } elseif ($this->whitespace) {
                return $children;
            } else {
                return array();
            }
        }
        return $result;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Definition that uses different definitions depending on context.
 *
 * The del and ins tags are notable because they allow different types of
 * elements depending on whether or not they're in a block or inline context.
 * Chameleon allows this behavior to happen by using two different
 * definitions depending on context.  While this somewhat generalized,
 * it is specifically intended for those two tags.
 */
class HTMLPurifier_ChildDef_Chameleon extends HTMLPurifier_ChildDef
{

    /**
     * Instance of the definition object to use when inline. Usually stricter.
     * @type HTMLPurifier_ChildDef_Optional
     */
    public $inline;

    /**
     * Instance of the definition object to use when block.
     * @type HTMLPurifier_ChildDef_Optional
     */
    public $block;

    /**
     * @type string
     */
    public $type = 'chameleon';

    /**
     * @param array $inline List of elements to allow when inline.
     * @param array $block List of elements to allow when block.
     */
    public function __construct($inline, $block)
    {
        $this->inline = new HTMLPurifier_ChildDef_Optional($inline);
        $this->block = new HTMLPurifier_ChildDef_Optional($block);
        $this->elements = $this->block->elements;
    }

    /**
     * @param HTMLPurifier_Node[] $children
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return bool
     */
    public function validateChildren($children, $config, $context)
    {
        if ($context->get('IsInline') === false) {
            return $this->block->validateChildren(
                $children,
                $config,
                $context
            );
        } else {
            return $this->inline->validateChildren(
                $children,
                $config,
                $context
            );
        }
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Definition that allows a set of elements, but disallows empty children.
 */
class HTMLPurifier_ChildDef_Required extends HTMLPurifier_ChildDef
{
    /**
     * Lookup table of allowed elements.
     * @type array
     */
    public $elements = array();

    /**
     * Whether or not the last passed node was all whitespace.
     * @type bool
     */
    protected $whitespace = false;

    /**
     * @param array|string $elements List of allowed element names (lowercase).
     */
    public function __construct($elements)
    {
        if (is_string($elements)) {
            $elements = str_replace(' ', '', $elements);
            $elements = explode('|', $elements);
        }
        $keys = array_keys($elements);
        if ($keys == array_keys($keys)) {
            $elements = array_flip($elements);
            foreach ($elements as $i => $x) {
                $elements[$i] = true;
                if (empty($i)) {
                    unset($elements[$i]);
                } // remove blank
            }
        }
        $this->elements = $elements;
    }

    /**
     * @type bool
     */
    public $allow_empty = false;

    /**
     * @type string
     */
    public $type = 'required';

    /**
     * @param array $children
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    public function validateChildren($children, $config, $context)
    {
        // Flag for subclasses
        $this->whitespace = false;

        // if there are no tokens, delete parent node
        if (empty($children)) {
            return false;
        }

        // the new set of children
        $result = array();

        // whether or not parsed character data is allowed
        // this controls whether or not we silently drop a tag
        // or generate escaped HTML from it
        $pcdata_allowed = isset($this->elements['#PCDATA']);

        // a little sanity check to make sure it's not ALL whitespace
        $all_whitespace = true;

        $stack = array_reverse($children);
        while (!empty($stack)) {
            $node = array_pop($stack);
            if (!empty($node->is_whitespace)) {
                $result[] = $node;
                continue;
            }
            $all_whitespace = false; // phew, we're not talking about whitespace

            if (!isset($this->elements[$node->name])) {
                // special case text
                // XXX One of these ought to be redundant or something
                if ($pcdata_allowed && $node instanceof HTMLPurifier_Node_Text) {
                    $result[] = $node;
                    continue;
                }
                // spill the child contents in
                // ToDo: Make configurable
                if ($node instanceof HTMLPurifier_Node_Element) {
                    for ($i = count($node->children) - 1; $i >= 0; $i--) {
                        $stack[] = $node->children[$i];
                    }
                    continue;
                }
                continue;
            }
            $result[] = $node;
        }
        if (empty($result)) {
            return false;
        }
        if ($all_whitespace) {
            $this->whitespace = true;
            return false;
        }
        return $result;
    }
}

// vim: et sw=4 sts=4
<?php

/**
 * Definition for tables.  The general idea is to extract out all of the
 * essential bits, and then reconstruct it later.
 *
 * This is a bit confusing, because the DTDs and the W3C
 * validators seem to disagree on the appropriate definition. The
 * DTD claims:
 *
 *      (CAPTION?, (COL*|COLGROUP*), THEAD?, TFOOT?, TBODY+)
 *
 * But actually, the HTML4 spec then has this to say:
 *
 *      The TBODY start tag is always required except when the table
 *      contains only one table body and no table head or foot sections.
 *      The TBODY end tag may always be safely omitted.
 *
 * So the DTD is kind of wrong.  The validator is, unfortunately, kind
 * of on crack.
 *
 * The definition changed again in XHTML1.1; and in my opinion, this
 * formulation makes the most sense.
 *
 *      caption?, ( col* | colgroup* ), (( thead?, tfoot?, tbody+ ) | ( tr+ ))
 *
 * Essentially, we have two modes: thead/tfoot/tbody mode, and tr mode.
 * If we encounter a thead, tfoot or tbody, we are placed in the former
 * mode, and we *must* wrap any stray tr segments with a tbody. But if
 * we don't run into any of them, just have tr tags is OK.
 */
class HTMLPurifier_ChildDef_Table extends HTMLPurifier_ChildDef
{
    /**
     * @type bool
     */
    public $allow_empty = false;

    /**
     * @type string
     */
    public $type = 'table';

    /**
     * @type array
     */
    public $elements = array(
        'tr' => true,
        'tbody' => true,
        'thead' => true,
        'tfoot' => true,
        'caption' => true,
        'colgroup' => true,
        'col' => true
    );

    public function __construct()
    {
    }

    /**
     * @param array $children
     * @param HTMLPurifier_Config $config
     * @param HTMLPurifier_Context $context
     * @return array
     */
    public function validateChildren($children, $config, $context)
    {
        if (empty($children)) {
            return false;
        }

        // only one of these elements is allowed in a table
        $caption = false;
        $thead = false;
        $tfoot = false;

        // whitespace
        $initial_ws = array();
        $after_caption_ws = array();
        $after_thead_ws = array();
        $after_tfoot_ws = array();

        // as many of these as you want
        $cols = array();
        $content = array();

        $tbody_mode = false; // if true, then we need to wrap any stray
                             // <tr>s with a <tbody>.

        $ws_accum =& $initial_ws;

        foreach ($children as $node) {
            if ($node instanceof HTMLPurifier_Node_Comment) {
                $ws_accum[] = $node;
                continue;
            }
            switch ($node->name) {
            case 'tbody':
                $tbody_mode = true;
                // fall through
            case 'tr':
                $content[] = $node;
                $ws_accum =& $content;
                break;
            case 'caption':
                // there can only be one caption!
                if ($caption !== false)  break;
                $caption = $node;
                $ws_accum =& $after_caption_ws;
                break;
            case 'thead':
                $tbody_mode = true;
                // XXX This breaks rendering properties with
                // Firefox, which never floats a <thead> to
                // the top. Ever. (Our scheme will float the
                // first <thead> to the top.)  So maybe
                // <thead>s that are not first should be
                // turned into <tbody>? Very tricky, indeed.
                if ($thead === false) {
                    $thead = $node;
                    $ws_accum =& $after_thead_ws;
                } else {
                    // Oops, there's a second one! What
                    // should we do?  Current behavior is to
                    // transmutate the first and last entries into
                    // tbody tags, and then put into content.
                    // Maybe a better idea is to *attach
                    // it* to the existing thead or tfoot?
                    // We don't do this, because Firefox
                    // doesn't float an extra tfoot to the
                    // bottom like it does for the first one.
                    $node->name = 'tbody';
                    $content[] = $node;
                    $ws_accum =& $content;
                }
                break;
            case 'tfoot':
                // see above for some aveats
                $tbody_mode = true;
                if ($tfoot === false) {
                    $tfoot = $node;
                    $ws_accum =& $after_tfoot_ws;
                } else {
                    $node->name = 'tbody';
                    $content[] = $node;
                    $ws_accum =& $content;
                }
                break;
            case 'colgroup':
            case 'col':
                $cols[] = $node;
                $ws_accum =& $cols;
                break;
            case '#PCDATA':
                // How is whitespace handled? We treat is as sticky to
                // the *end* of the previous element. So all of the
                // nonsense we have worked on is to keep things
                // together.
                if (!empty($node->is_whitespace)) {
                    $ws_accum[] = $node;
                }
                break;
            }
        }

        if (empty($content)) {
            return false;
        }

        $ret = $initial_ws;
        if ($caption !== false) {
            $ret[] = $caption;
            $ret = array_merge($ret, $after_caption_ws);
        }
        if ($cols !== false) {
            $ret = array_merge($ret, $cols);
        }
        if ($thead !== false) {
            $ret[] = $thead;
            $ret = array_merge($ret, $after_thead_ws);
        }
        if ($tfoot !== false) {
            $ret[] = $tfoot;
            $ret = array_merge($ret, $after_tfoot_ws);
        }

        if ($tbody_mode) {
            // we have to shuffle tr into tbody
            $current_tr_tbody = null;

            foreach($content as $node) {
                switch ($node->name) {
                case 'tbody':
                    $current_tr_tbody = null;
                    $ret[] = $node;
                    break;
                case 'tr':
                    if ($current_tr_tbody === null) {
                        $current_tr_tbody = new HTMLPurifier_Node_Element('tbody');
                        $ret[] = $current_tr_tbody;
                    }
                    $current_tr_tbody->children[] = $node;
                    break;
                case '#PCDATA':
                    //assert($node->is_whitespace);
                    if ($current_tr_tbody === null) {
                        $ret[] = $node;
                    } else {
                        $current_tr_tbody->children[] = $node;
                    }
                    break;
                }
            }
        } else {
            $ret = array_merge($ret, $content);
        }

        return $ret;

    }
}

// vim: et sw=4 sts=4
<?php

/**
 * @file
 * Defines a function wrapper for HTML Purifier for quick use.
 * @note ''HTMLPurifier()'' is NOT the same as ''new HTMLPurifier()''
 */

/**
 * Purify HTML.
 * @param string $html String HTML to purify
 * @param mixed $config Configuration to use, can be any value accepted by
 *        HTMLPurifier_Config::create()
 * @return string
 */
function HTMLPurifier($html, $config = null)
{
    static $purifier = false;
    if (!$purifier) {
        $purifier = new HTMLPurifier();
    }
    return $purifier->purify($html, $config);
}

// vim: et sw=4 sts=4
#!/usr/bin/php
<?php

chdir(dirname(__FILE__));
require_once 'common.php';
assertCli();

/**
 * @file
 * Flushes the definition serial cache. This file should be
 * called if changes to any subclasses of HTMLPurifier_Definition
 * or related classes (such as HTMLPurifier_HTMLModule) are made. This
 * may also be necessary if you've modified a customized version.
 *
 * @param Accepts one argument, cache type to flush; otherwise flushes all
 *      the caches.
 */

echo "Flushing cache... \n";

require_once(dirname(__FILE__) . '/../library/HTMLPurifier.auto.php');

$config = HTMLPurifier_Config::createDefault();

$names = array('HTML', 'CSS', 'URI', 'Test');
if (isset($argv[1])) {
    if (in_array($argv[1], $names)) {
        $names = array($argv[1]);
    } else {
        throw new Exception("Cache parameter {$argv[1]} is not a valid cache");
    }
}

foreach ($names as $name) {
    echo " - Flushing $name\n";
    $cache = new HTMLPurifier_DefinitionCache_Serializer($name);
    $cache->flush($config);
}

echo "Cache flushed successfully.\n";

// vim: et sw=4 sts=4
#!/usr/bin/php
<?php

chdir(dirname(__FILE__));
require_once 'common.php';
assertCli();

/**
 * @file
 * Compiles all of HTML Purifier's library files into one big file
 * named HTMLPurifier.standalone.php. This is usually called during the
 * release process.
 */

/**
 * Global hash that tracks already loaded includes
 */
$GLOBALS['loaded'] = array();

/**
 * Custom FSTools for this script that overloads some behavior
 * @warning The overloading of copy() is not necessarily global for
 *          this script. Watch out!
 */
class MergeLibraryFSTools extends FSTools
{
    public function copyable($entry)
    {
        // Skip hidden files
        if ($entry[0] == '.') {
            return false;
        }
        return true;
    }
    public function copy($source, $dest)
    {
        copy_and_remove_includes($source, $dest);
    }
}
$FS = new MergeLibraryFSTools();

/**
 * Replaces the includes inside PHP source code with the corresponding
 * source.
 * @param string $text PHP source code to replace includes from
 */
function replace_includes($text)
{
    // also remove vim modelines
    return preg_replace_callback(
        "/require(?:_once)? ['\"]([^'\"]+)['\"];/",
        'replace_includes_callback',
        $text
    );
}

/**
 * Removes leading PHP tags from included files. Assumes that there is
 * no trailing tag. Also removes vim modelines.
 * @note This is safe for files that have internal <?php
 * @param string $text Text to have leading PHP tag from
 */
function remove_php_tags($text)
{
    $text = preg_replace('#// vim:.+#', '', $text);
    return substr($text, 5);
}

/**
 * Copies the contents of a directory to the standalone directory
 * @param string $dir Directory to copy
 */
function make_dir_standalone($dir)
{
    global $FS;
    return $FS->copyr($dir, 'standalone/' . $dir);
}

/**
 * Copies the contents of a file to the standalone directory
 * @param string $file File to copy
 */
function make_file_standalone($file)
{
    global $FS;
    $FS->mkdirr('standalone/' . dirname($file));
    copy_and_remove_includes($file, 'standalone/' . $file);
    return true;
}

/**
 * Copies a file to another location recursively, if it is a PHP file
 * remove includes
 * @param string $file Original file
 * @param string $sfile New location of file
 */
function copy_and_remove_includes($file, $sfile)
{
    $contents = file_get_contents($file);
    if (strrchr($file, '.') === '.php') $contents = replace_includes($contents);
    return file_put_contents($sfile, $contents);
}

/**
 * @param $matches preg_replace_callback matches array, where index 1
 *        is the filename to include
 */
function replace_includes_callback($matches)
{
    $file = $matches[1];
    $preserve = array(
      // PEAR (external)
      'XML/HTMLSax3.php' => 1
    );
    if (isset($preserve[$file])) {
        return $matches[0];
    }
    if (isset($GLOBALS['loaded'][$file])) return '';
    $GLOBALS['loaded'][$file] = true;
    return replace_includes(remove_php_tags(file_get_contents($file)));
}

echo 'Generating includes file... ';
shell_exec('php generate-includes.php');
echo "done!\n";

chdir(dirname(__FILE__) . '/../library/');

echo 'Creating full file...';
$contents = replace_includes(file_get_contents('HTMLPurifier.includes.php'));
$contents = str_replace(
    // Note that bootstrap is now inside the standalone file
    "define('HTMLPURIFIER_PREFIX', realpath(dirname(__FILE__) . '/..'));",
    "define('HTMLPURIFIER_PREFIX', dirname(__FILE__) . '/standalone');
    set_include_path(HTMLPURIFIER_PREFIX . PATH_SEPARATOR . get_include_path());",
    $contents
);
file_put_contents('HTMLPurifier.standalone.php', $contents);
echo ' done!' . PHP_EOL;

echo 'Creating standalone directory...';
$FS->rmdirr('standalone'); // ensure a clean copy

// data files
$FS->mkdirr('standalone/HTMLPurifier/DefinitionCache/Serializer');
make_file_standalone('HTMLPurifier/EntityLookup/entities.ser');
make_file_standalone('HTMLPurifier/ConfigSchema/schema.ser');

// non-standard inclusion setup
make_dir_standalone('HTMLPurifier/ConfigSchema');
make_dir_standalone('HTMLPurifier/Language');
make_dir_standalone('HTMLPurifier/Filter');
make_dir_standalone('HTMLPurifier/Printer');
make_file_standalone('HTMLPurifier/Printer.php');
make_file_standalone('HTMLPurifier/Lexer/PH5P.php');

echo ' done!' . PHP_EOL;

// vim: et sw=4 sts=4
#!/usr/bin/php
<?php

chdir(dirname(__FILE__));
require_once 'common.php';
assertCli();

/**
 * @file
 * Converts all instances of $config->set and $config->get to the new
 * format, as described by docs/dev-config-bcbreaks.txt
 */

$FS = new FSTools();
chdir(dirname(__FILE__) . '/..');
$raw_files = $FS->globr('.', '*.php');
foreach ($raw_files as $file) {
    $file = substr($file, 2); // rm leading './'
    if (strpos($file, 'library/standalone/') === 0) continue;
    if (strpos($file, 'maintenance/update-config.php') === 0) continue;
    if (strpos($file, 'test-settings.php') === 0) continue;
    if (substr_count($file, '.') > 1) continue; // rm meta files
    // process the file
    $contents = file_get_contents($file);
    $contents = preg_replace(
        "#config->(set|get)\('(.+?)', '(.+?)'#",
        "config->\\1('\\2.\\3'",
        $contents
    );
    if ($contents === '') continue;
    file_put_contents($file, $contents);
}

// vim: et sw=4 sts=4
#!/usr/bin/php
<?php

chdir(dirname(__FILE__));
require_once 'common.php';
assertCli();

/**
 * @file
 * Adds vimline to files
 */

chdir(dirname(__FILE__) . '/..');
$FS = new FSTools();

$vimline = 'vim: et sw=4 sts=4';

$files = $FS->globr('.', '*');
foreach ($files as $file) {
    if (
        !is_file($file) ||
        prefix_is('./docs/doxygen', $file) ||
        prefix_is('./library/standalone', $file) ||
        prefix_is('./docs/specimens', $file) ||
        postfix_is('.ser', $file) ||
        postfix_is('.tgz', $file) ||
        postfix_is('.patch', $file) ||
        postfix_is('.dtd', $file) ||
        postfix_is('.ent', $file) ||
        postfix_is('.png', $file) ||
        postfix_is('.ico', $file) ||
        // wontfix
        postfix_is('.vtest', $file) ||
        postfix_is('.svg', $file) ||
        postfix_is('.phpt', $file) ||
        postfix_is('VERSION', $file) ||
        postfix_is('WHATSNEW', $file) ||
        postfix_is('configdoc/usage.xml', $file) ||
        postfix_is('library/HTMLPurifier.includes.php', $file) ||
        postfix_is('library/HTMLPurifier.safe-includes.php', $file) ||
        postfix_is('smoketests/xssAttacks.xml', $file) ||
        // phpt files
        postfix_is('.diff', $file) ||
        postfix_is('.exp', $file) ||
        postfix_is('.log', $file) ||
        postfix_is('.out', $file) ||

        $file == './library/HTMLPurifier/Lexer/PH5P.php' ||
        $file == './maintenance/PH5P.php'
    ) continue;
    $ext = strrchr($file, '.');
    if (
        postfix_is('README', $file) ||
        postfix_is('LICENSE', $file) ||
        postfix_is('CREDITS', $file) ||
        postfix_is('INSTALL', $file) ||
        postfix_is('NEWS', $file) ||
        postfix_is('TODO', $file) ||
        postfix_is('WYSIWYG', $file) ||
        postfix_is('Changelog', $file)
    ) $ext = '.txt';
    if (postfix_is('Doxyfile', $file)) $ext = 'Doxyfile';
    if (postfix_is('.php.in', $file)) $ext = '.php';
    $no_nl = false;
    switch ($ext) {
        case '.php':
        case '.inc':
        case '.js':
            $line = '// %s';
            break;
        case '.html':
        case '.xsl':
        case '.xml':
        case '.htc':
            $line = "<!-- %s\n-->";
            break;
        case '.htmlt':
            $no_nl = true;
            $line = '--# %s';
            break;
        case '.ini':
            $line = '; %s';
            break;
        case '.css':
            $line = '/* %s */';
            break;
        case '.bat':
            $line = 'rem %s';
            break;
        case '.txt':
        case '.utf8':
            if (
                prefix_is('./library/HTMLPurifier/ConfigSchema', $file) ||
                prefix_is('./smoketests/test-schema', $file) ||
                prefix_is('./tests/HTMLPurifier/StringHashParser', $file)
            ) {
                $no_nl = true;
                $line = '--# %s';
            } else {
                $line = '    %s';
            }
            break;
        case 'Doxyfile':
            $line = '# %s';
            break;
        default:
            throw new Exception('Unknown file: ' . $file);
    }

    echo "$file\n";
    $contents = file_get_contents($file);

    $regex = '~' . str_replace('%s', 'vim: .+', preg_quote($line, '~')) .  '~m';
    $contents = preg_replace($regex, '', $contents);

    $contents = rtrim($contents);

    if (strpos($contents, "\r\n") !== false) $nl = "\r\n";
    elseif (strpos($contents, "\n") !== false) $nl = "\n";
    elseif (strpos($contents, "\r") !== false) $nl = "\r";
    else $nl = PHP_EOL;

    if (!$no_nl) $contents .= $nl;
    $contents .= $nl . str_replace('%s', $vimline, $line) . $nl;

    file_put_contents($file, $contents);

}

// vim: et sw=4 sts=4
#!/usr/bin/php
<?php

chdir(dirname(__FILE__));
require_once 'common.php';
assertCli();

echo "Please do not run this script. It is here for historical purposes only.";
exit;

/**
 * @file
 * Removes ConfigSchema function calls from source files.
 */

chdir(dirname(__FILE__) . '/../library/');
$FS = new FSTools();

$files = $FS->globr('.', '*.php');
foreach ($files as $file) {
    if (substr_count(basename($file), '.') > 1) continue;
    $old_code = file_get_contents($file);
    $new_code = preg_replace("#^HTMLPurifier_ConfigSchema::.+?\);[\n\r]*#ms", '', $old_code);
    if ($old_code !== $new_code) {
        file_put_contents($file, $new_code);
    }
    if (preg_match('#^\s+HTMLPurifier_ConfigSchema::#m', $new_code)) {
        echo "Indented ConfigSchema call in $file\n";
    }
}

// vim: et sw=4 sts=4
#!/usr/bin/php
<?php

chdir(dirname(__FILE__));
require_once 'common.php';
assertCli();

/**
 * @file
 * Parses *.ent files into an entity lookup table, and then serializes and
 * writes the whole kaboodle to a file. The resulting file is cached so
 * that this script does not need to be run. This script should rarely,
 * if ever, be run, since HTML's entities are fairly immutable.
 */

// here's where the entity files are located, assuming working directory
// is the same as the location of this PHP file. Needs trailing slash.
$entity_dir = '../docs/entities/';

// defines the output file for the serialized content.
$output_file = '../library/HTMLPurifier/EntityLookup/entities.ser';

// courtesy of a PHP manual comment
function unichr($dec)
{
    if ($dec < 128) {
        $utf  = chr($dec);
    } elseif ($dec < 2048) {
        $utf  = chr(192 + (($dec - ($dec % 64)) / 64));
        $utf .= chr(128 + ($dec % 64));
    } else {
        $utf  = chr(224 + (($dec - ($dec % 4096)) / 4096));
        $utf .= chr(128 + ((($dec % 4096) - ($dec % 64)) / 64));
        $utf .= chr(128 + ($dec % 64));
    }
    return $utf;
}

if ( !is_dir($entity_dir) ) exit("Fatal Error: Can't find entity directory.\n");
if ( file_exists($output_file) ) exit("Fatal Error: output file already exists.\n");

$dh = @opendir($entity_dir);
if ( !$dh ) exit("Fatal Error: Cannot read entity directory.\n");

$entity_files = array();
while (($file = readdir($dh)) !== false) {
    if (@$file[0] === '.') continue;
    if (substr(strrchr($file, "."), 1) !== 'ent') continue;
    $entity_files[] = $file;
}
closedir($dh);

if ( !$entity_files ) exit("Fatal Error: No entity files to parse.\n");

$entity_table = array();
$regexp = '/<!ENTITY\s+([A-Za-z0-9]+)\s+"&#(?:38;#)?([0-9]+);">/';

foreach ( $entity_files as $file ) {
    $contents = file_get_contents($entity_dir . $file);
    $matches = array();
    preg_match_all($regexp, $contents, $matches, PREG_SET_ORDER);
    foreach ($matches as $match) {
        $entity_table[$match[1]] = unichr($match[2]);
    }
}

$output = serialize($entity_table);

$fh = fopen($output_file, 'w');
fwrite($fh, $output);
fclose($fh);

echo "Completed successfully.";

// vim: et sw=4 sts=4
#!/usr/bin/php
<?php

chdir(dirname(__FILE__));
require_once 'common.php';
require_once '../library/HTMLPurifier.auto.php';
assertCli();

if (version_compare(PHP_VERSION, '5.2.2', '<')) {
    echo "This script requires PHP 5.2.2 or later, for tokenizer line numbers.";
    exit(1);
}

/**
 * @file
 * Scans HTML Purifier source code for $config tokens and records the
 * directive being used; configdoc can use this info later.
 *
 * Currently, this just dumps all the info onto the console. Eventually, it
 * will create an XML file that our XSLT transform can use.
 */

$FS = new FSTools();
chdir(dirname(__FILE__) . '/../library/');
$raw_files = $FS->globr('.', '*.php');
$files = array();
foreach ($raw_files as $file) {
    $file = substr($file, 2); // rm leading './'
    if (strncmp('standalone/', $file, 11) === 0) continue; // rm generated files
    if (substr_count($file, '.') > 1) continue; // rm meta files
    $files[] = $file;
}

/**
 * Moves the $i cursor to the next non-whitespace token
 */
function consumeWhitespace($tokens, &$i)
{
    do {$i++;} while (is_array($tokens[$i]) && $tokens[$i][0] === T_WHITESPACE);
}

/**
 * Tests whether or not a token is a particular type. There are three run-cases:
 *      - ($token, $expect_token): tests if the token is $expect_token type;
 *      - ($token, $expect_value): tests if the token is the string $expect_value;
 *      - ($token, $expect_token, $expect_value): tests if token is $expect_token type, and
 *        its string representation is $expect_value
 */
function testToken($token, $value_or_token, $value = null)
{
    if (is_null($value)) {
        if (is_int($value_or_token)) return is_array($token) && $token[0] === $value_or_token;
        else return $token === $value_or_token;
    } else {
        return is_array($token) && $token[0] === $value_or_token && $token[1] === $value;
    }
}

$counter = 0;
$full_counter = 0;
$tracker = array();

foreach ($files as $file) {
    $tokens = token_get_all(file_get_contents($file));
    $file = str_replace('\\', '/', $file);
    for ($i = 0, $c = count($tokens); $i < $c; $i++) {
        $ok = false;
        // Match $config
        if (!$ok && testToken($tokens[$i], T_VARIABLE, '$config')) $ok = true;
        // Match $this->config
        while (!$ok && testToken($tokens[$i], T_VARIABLE, '$this')) {
            consumeWhitespace($tokens, $i);
            if (!testToken($tokens[$i], T_OBJECT_OPERATOR)) break;
            consumeWhitespace($tokens, $i);
            if (testToken($tokens[$i], T_STRING, 'config')) $ok = true;
            break;
        }
        if (!$ok) continue;

        $ok = false;
        for($i++; $i < $c; $i++) {
            if ($tokens[$i] === ',' || $tokens[$i] === ')' || $tokens[$i] === ';') {
                break;
            }
            if (is_string($tokens[$i])) continue;
            if ($tokens[$i][0] === T_OBJECT_OPERATOR) {
                $ok = true;
                break;
            }
        }
        if (!$ok) continue;

        $line = $tokens[$i][2];

        consumeWhitespace($tokens, $i);
        if (!testToken($tokens[$i], T_STRING, 'get')) continue;

        consumeWhitespace($tokens, $i);
        if (!testToken($tokens[$i], '(')) continue;

        $full_counter++;

        $matched = false;
        do {

            // What we currently don't match are batch retrievals, and
            // wildcard retrievals. This data might be useful in the future,
            // which is why we have a do {} while loop that doesn't actually
            // do anything.

            consumeWhitespace($tokens, $i);
            if (!testToken($tokens[$i], T_CONSTANT_ENCAPSED_STRING)) continue;
            $id = substr($tokens[$i][1], 1, -1);

            $counter++;
            $matched = true;

            if (!isset($tracker[$id])) $tracker[$id] = array();
            if (!isset($tracker[$id][$file])) $tracker[$id][$file] = array();
            $tracker[$id][$file][] = $line;

        } while (0);

        //echo "$file:$line uses $namespace.$directive\n";
    }
}

echo "\n$counter/$full_counter instances of \$config or \$this->config found in source code.\n";

echo "Generating XML... ";

$xw = new XMLWriter();
$xw->openURI('../configdoc/usage.xml');
$xw->setIndent(true);
$xw->startDocument('1.0', 'UTF-8');
$xw->startElement('usage');
foreach ($tracker as $id => $files) {
    $xw->startElement('directive');
    $xw->writeAttribute('id', $id);
    foreach ($files as $file => $lines) {
        $xw->startElement('file');
        $xw->writeAttribute('name', $file);
        foreach ($lines as $line) {
            $xw->writeElement('line', $line);
        }
        $xw->endElement();
    }
    $xw->endElement();
}
$xw->endElement();
$xw->flush();

echo "done!\n";

// vim: et sw=4 sts=4
#!/usr/bin/php
<?php

chdir(dirname(__FILE__));
require_once 'common.php';
assertCli();

echo "Please do not run this script. It is here for historical purposes only.";
exit;

/**
 * @file
 * Extracts all definitions inside a configuration schema
 * (HTMLPurifier_ConfigSchema) and exports them as plain text files.
 *
 * @todo Extract version numbers.
 */

define('HTMLPURIFIER_SCHEMA_STRICT', true); // description data needs to be collected
require_once dirname(__FILE__) . '/../library/HTMLPurifier.auto.php';

// We need includes to ensure all HTMLPurifier_ConfigSchema calls are
// performed.
require_once 'HTMLPurifier.includes.php';

// Also, these extra files will be necessary.
require_once 'HTMLPurifier/Filter/ExtractStyleBlocks.php';

/**
 * Takes a hash and saves its contents to library/HTMLPurifier/ConfigSchema/
 */
function saveHash($hash)
{
    if ($hash === false) return;
    $dir = realpath(dirname(__FILE__) . '/../library/HTMLPurifier/ConfigSchema');
    $name = $hash['ID'] . '.txt';
    $file = $dir . '/' . $name;
    if (file_exists($file)) {
        trigger_error("File already exists; skipped $name");
        return;
    }
    $file = new FSTools_File($file);
    $file->open('w');
    $multiline = false;
    foreach ($hash as $key => $value) {
        $multiline = $multiline || (strpos($value, "\n") !== false);
        if ($multiline) {
            $file->put("--$key--" . PHP_EOL);
            $file->put(str_replace("\n", PHP_EOL, $value) . PHP_EOL);
        } else {
            if ($key == 'ID') {
                $file->put("$value" . PHP_EOL);
            } else {
                $file->put("$key: $value" . PHP_EOL);
            }
        }
    }
    $file->close();
}

$schema  = HTMLPurifier_ConfigSchema::instance();
$adapter = new HTMLPurifier_ConfigSchema_StringHashReverseAdapter($schema);

foreach ($schema->info as $ns => $ns_array) {
    saveHash($adapter->get($ns));
    foreach ($ns_array as $dir => $x) {
        saveHash($adapter->get($ns, $dir));
    }
}

// vim: et sw=4 sts=4
#!/usr/bin/php
<?php

chdir(dirname(__FILE__));
require_once 'common.php';
require_once '../library/HTMLPurifier.auto.php';
assertCli();

/**
 * @file
 * Renames a configuration directive.  This involves renaming the file,
 * adding an alias, and then regenerating the cache.  You still have to
 * manually go through and fix any calls to the directive.
 * @warning This script doesn't handle multi-stringhash files.
 */

$argv = $_SERVER['argv'];
if (count($argv) < 3) {
    echo "Usage: {$argv[0]} OldName NewName\n";
    exit(1);
}

chdir('../library/HTMLPurifier/ConfigSchema/schema');

$old = $argv[1];
$new = $argv[2];

if (!file_exists("$old.txt")) {
    echo "Cannot move undefined configuration directive $old\n";
    exit(1);
}

if ($old === $new) {
    echo "Attempting to move to self, aborting\n";
    exit(1);
}

if (file_exists("$new.txt")) {
    echo "Cannot move to already defined directive $new\n";
    exit(1);
}

$file = "$old.txt";
$builder = new HTMLPurifier_ConfigSchema_InterchangeBuilder();
$interchange = new HTMLPurifier_ConfigSchema_Interchange();
$builder->buildFile($interchange, $file);
$contents = file_get_contents($file);

if (strpos($contents, "\r\n") !== false) {
    $nl = "\r\n";
} elseif (strpos($contents, "\r") !== false) {
    $nl = "\r";
} else {
    $nl = "\n";
}

// replace name with new name
$contents = str_replace($old, $new, $contents);

if ($interchange->directives[$old]->aliases) {
    $pos_alias = strpos($contents, 'ALIASES:');
    $pos_ins = strpos($contents, $nl, $pos_alias);
    if ($pos_ins === false) $pos_ins = strlen($contents);
    $contents =
        substr($contents, 0, $pos_ins) . ", $old" . substr($contents, $pos_ins);
    file_put_contents($file, $contents);
} else {
    $lines = explode($nl, $contents);
    $insert = false;
    foreach ($lines as $n => $line) {
        if (strncmp($line, '--', 2) === 0) {
            $insert = $n;
            break;
        }
    }
    if (!$insert) {
        $lines[] = "ALIASES: $old";
    } else {
        array_splice($lines, $insert, 0, "ALIASES: $old");
    }
    file_put_contents($file, implode($nl, $lines));
}

rename("$old.txt", "$new.txt") || exit(1);
#!/usr/bin/php
<?php

chdir(dirname(__FILE__));
require_once 'common.php';
require_once '../tests/path2class.func.php';
require_once '../library/HTMLPurifier/Bootstrap.php';
assertCli();

/**
 * @file
 * Generates an include stub for users who do not want to use the autoloader.
 * When new files are added to HTML Purifier's main codebase, this file should
 * be called.
 */

chdir(dirname(__FILE__) . '/../library/');
$FS = new FSTools();

$exclude_dirs = array(
    'HTMLPurifier/Language/',
    'HTMLPurifier/ConfigSchema/',
    'HTMLPurifier/Filter/',
    'HTMLPurifier/Printer/',
    /* These should be excluded, but need to have ConfigSchema support first

    */
);
$exclude_files = array(
    'HTMLPurifier/Lexer/PEARSax3.php',
    'HTMLPurifier/Lexer/PH5P.php',
    'HTMLPurifier/Printer.php',
);

// Determine what files need to be included:
echo 'Scanning for files... ';
$raw_files = $FS->globr('.', '*.php');
if (!$raw_files) throw new Exception('Did not find any PHP source files');
$files = array();
foreach ($raw_files as $file) {
    $file = substr($file, 2); // rm leading './'
    if (strncmp('standalone/', $file, 11) === 0) continue; // rm generated files
    if (substr_count($file, '.') > 1) continue; // rm meta files
    $ok = true;
    foreach ($exclude_dirs as $dir) {
        if (strncmp($dir, $file, strlen($dir)) === 0) {
            $ok = false;
            break;
        }
    }
    if (!$ok) continue; // rm excluded directories
    if (in_array($file, $exclude_files)) continue; // rm excluded files
    $files[] = $file;
}
echo "done!\n";

// Reorder list so that dependencies are included first:

/**
 * Returns a lookup array of dependencies for a file.
 *
 * @note This function expects that format $name extends $parent on one line
 *
 * @param string $file
 *      File to check dependencies of.
 * @return array
 *      Lookup array of files the file is dependent on, sorted accordingly.
 */
function get_dependency_lookup($file)
{
    static $cache = array();
    if (isset($cache[$file])) return $cache[$file];
    if (!file_exists($file)) {
        echo "File doesn't exist: $file\n";
        return array();
    }
    $fh = fopen($file, 'r');
    $deps = array();
    while (!feof($fh)) {
        $line = fgets($fh);
        if (strncmp('class', $line, 5) === 0) {
            // The implementation here is fragile and will break if we attempt
            // to use interfaces. Beware!
            $arr = explode(' extends ', trim($line, ' {'."\n\r"), 2);
            if (count($arr) < 2) break;
            $parent = $arr[1];
            $dep_file = HTMLPurifier_Bootstrap::getPath($parent);
            if (!$dep_file) break;
            $deps[$dep_file] = true;
            break;
        }
    }
    fclose($fh);
    foreach (array_keys($deps) as $file) {
        // Extra dependencies must come *before* base dependencies
        $deps = get_dependency_lookup($file) + $deps;
    }
    $cache[$file] = $deps;
    return $deps;
}

/**
 * Sorts files based on dependencies. This function is lazy and will not
 * group files with dependencies together; it will merely ensure that a file
 * is never included before its dependencies are.
 *
 * @param $files
 *      Files array to sort.
 * @return
 *      Sorted array ($files is not modified by reference!)
 */
function dep_sort($files)
{
    $ret = array();
    $cache = array();
    foreach ($files as $file) {
        if (isset($cache[$file])) continue;
        $deps = get_dependency_lookup($file);
        foreach (array_keys($deps) as $dep) {
            if (!isset($cache[$dep])) {
                $ret[] = $dep;
                $cache[$dep] = true;
            }
        }
        $cache[$file] = true;
        $ret[] = $file;
    }
    return $ret;
}

$files = dep_sort($files);

// Build the actual include stub:

$version = trim(file_get_contents('../VERSION'));

// stub
$php = "<?php

/**
 * @file
 * This file was auto-generated by generate-includes.php and includes all of
 * the core files required by HTML Purifier. Use this if performance is a
 * primary concern and you are using an opcode cache. PLEASE DO NOT EDIT THIS
 * FILE, changes will be overwritten the next time the script is run.
 *
 * @version $version
 *
 * @warning
 *      You must *not* include any other HTML Purifier files before this file,
 *      because 'require' not 'require_once' is used.
 *
 * @warning
 *      This file requires that the include path contains the HTML Purifier
 *      library directory; this is not auto-set.
 */

";

foreach ($files as $file) {
    $php .= "require '$file';" . PHP_EOL;
}

echo "Writing HTMLPurifier.includes.php... ";
file_put_contents('HTMLPurifier.includes.php', $php);
echo "done!\n";

$php = "<?php

/**
 * @file
 * This file was auto-generated by generate-includes.php and includes all of
 * the core files required by HTML Purifier. This is a convenience stub that
 * includes all files using dirname(__FILE__) and require_once. PLEASE DO NOT
 * EDIT THIS FILE, changes will be overwritten the next time the script is run.
 *
 * Changes to include_path are not necessary.
 */

\$__dir = dirname(__FILE__);

";

foreach ($files as $file) {
    $php .= "require_once \$__dir . '/$file';" . PHP_EOL;
}

echo "Writing HTMLPurifier.safe-includes.php... ";
file_put_contents('HTMLPurifier.safe-includes.php', $php);
echo "done!\n";

// vim: et sw=4 sts=4
<?php

/**
 * @file
 * This file compares our version of PH5P with Jero's original version, and
 * generates a patch of the differences. This script should be run whenever
 * library/HTMLPurifier/Lexer/PH5P.php is modified.
 */

$orig = realpath(dirname(__FILE__) . '/PH5P.php');
$new  = realpath(dirname(__FILE__) . '/../library/HTMLPurifier/Lexer/PH5P.php');
$newt = dirname(__FILE__) . '/PH5P.new.php'; // temporary file

// minor text-processing of new file to get into same format as original
$new_src = file_get_contents($new);
$new_src = '<?php' . PHP_EOL . substr($new_src, strpos($new_src, 'class HTML5 {'));

file_put_contents($newt, $new_src);
shell_exec("diff -u \"$orig\" \"$newt\" > PH5P.patch");
unlink($newt);

// vim: et sw=4 sts=4
#!/usr/bin/php
<?php

chdir(dirname(__FILE__));
require_once 'common.php';
assertCli();

/**
 * @file
 * Runs all generation/flush cache scripts to ensure that somewhat volatile
 * generated files are up-to-date.
 */

function e($cmd)
{
    echo "\$ $cmd\n";
    passthru($cmd, $status);
    echo "\n";
    if ($status) exit($status);
}

$php = empty($_SERVER['argv'][1]) ? 'php' : $_SERVER['argv'][1];

e($php . ' generate-includes.php');
e($php . ' generate-schema-cache.php');
e($php . ' flush-definition-cache.php');
e($php . ' generate-standalone.php');
e($php . ' config-scanner.php');

// vim: et sw=4 sts=4
#!/usr/bin/php
<?php

chdir(dirname(__FILE__));
require_once 'common.php';
assertCli();

/**
 * @file
 * Removes trailing whitespace from files.
 */

chdir(dirname(__FILE__) . '/..');
$FS = new FSTools();

$files = $FS->globr('.', '{,.}*', GLOB_BRACE);
foreach ($files as $file) {
    if (
        !is_file($file) ||
        prefix_is('./.git', $file) ||
        prefix_is('./docs/doxygen', $file) ||
        postfix_is('.ser', $file) ||
        postfix_is('.tgz', $file) ||
        postfix_is('.patch', $file) ||
        postfix_is('.dtd', $file) ||
        postfix_is('.ent', $file) ||
        $file == './library/HTMLPurifier/Lexer/PH5P.php' ||
        $file == './maintenance/PH5P.php'
    ) continue;
    $contents = file_get_contents($file);
    $result = preg_replace('/^(.*?)[ \t]+(\r?)$/m', '\1\2', $contents, -1, $count);
    if (!$count) continue;
    echo "$file\n";
    file_put_contents($file, $result);
}

// vim: et sw=4 sts=4
#!/usr/bin/php
<?php

/**
 * @file
 * Deprecated in favor of generate-standalone.php.
 */

require dirname(__FILE__) . '/generate-standalone.php';

// vim: et sw=4 sts=4
<?php

function assertCli()
{
    if (php_sapi_name() != 'cli' && !getenv('PHP_IS_CLI')) {
        echo 'Script cannot be called from web-browser (if you are indeed calling via cli,
set environment variable PHP_IS_CLI to work around this).';
        exit(1);
    }
}

function prefix_is($comp, $subject)
{
    return strncmp($comp, $subject, strlen($comp)) === 0;
}

function postfix_is($comp, $subject)
{
    return strlen($subject) < $comp ? false : substr($subject, -strlen($comp)) === $comp;
}

// Load useful stuff like FSTools
require_once dirname(__FILE__) . '/../extras/HTMLPurifierExtras.auto.php';

// vim: et sw=4 sts=4
#!/usr/bin/php
<?php

require_once dirname(__FILE__) . '/common.php';
require_once dirname(__FILE__) . '/../library/HTMLPurifier.auto.php';
assertCli();

/**
 * @file
 * Generates a schema cache file, saving it to
 * library/HTMLPurifier/ConfigSchema/schema.ser.
 *
 * This should be run when new configuration options are added to
 * HTML Purifier. A cached version is available via the repository
 * so this does not normally have to be regenerated.
 *
 * If you have a directory containing custom configuration schema files,
 * you can simple add a path to that directory as a parameter to
 * this, and they will get included.
 */

$target = dirname(__FILE__) . '/../library/HTMLPurifier/ConfigSchema/schema.ser';

$builder = new HTMLPurifier_ConfigSchema_InterchangeBuilder();
$interchange = new HTMLPurifier_ConfigSchema_Interchange();

$builder->buildDir($interchange);

$loader = dirname(__FILE__) . '/../config-schema.php';
if (file_exists($loader)) include $loader;
foreach ($_SERVER['argv'] as $i => $dir) {
    if ($i === 0) continue;
    $builder->buildDir($interchange, realpath($dir));
}

$interchange->validate();

$schema_builder = new HTMLPurifier_ConfigSchema_Builder_ConfigSchema();
$schema = $schema_builder->build($interchange);

echo "Saving schema... ";
file_put_contents($target, serialize($schema));
echo "done!\n";

// vim: et sw=4 sts=4
<?php
class HTML5
{
    private $data;
    private $char;
    private $EOF;
    private $state;
    private $tree;
    private $token;
    private $content_model;
    private $escape = false;
    private $entities = array('AElig;','AElig','AMP;','AMP','Aacute;','Aacute',
    'Acirc;','Acirc','Agrave;','Agrave','Alpha;','Aring;','Aring','Atilde;',
    'Atilde','Auml;','Auml','Beta;','COPY;','COPY','Ccedil;','Ccedil','Chi;',
    'Dagger;','Delta;','ETH;','ETH','Eacute;','Eacute','Ecirc;','Ecirc','Egrave;',
    'Egrave','Epsilon;','Eta;','Euml;','Euml','GT;','GT','Gamma;','Iacute;',
    'Iacute','Icirc;','Icirc','Igrave;','Igrave','Iota;','Iuml;','Iuml','Kappa;',
    'LT;','LT','Lambda;','Mu;','Ntilde;','Ntilde','Nu;','OElig;','Oacute;',
    'Oacute','Ocirc;','Ocirc','Ograve;','Ograve','Omega;','Omicron;','Oslash;',
    'Oslash','Otilde;','Otilde','Ouml;','Ouml','Phi;','Pi;','Prime;','Psi;',
    'QUOT;','QUOT','REG;','REG','Rho;','Scaron;','Sigma;','THORN;','THORN',
    'TRADE;','Tau;','Theta;','Uacute;','Uacute','Ucirc;','Ucirc','Ugrave;',
    'Ugrave','Upsilon;','Uuml;','Uuml','Xi;','Yacute;','Yacute','Yuml;','Zeta;',
    'aacute;','aacute','acirc;','acirc','acute;','acute','aelig;','aelig',
    'agrave;','agrave','alefsym;','alpha;','amp;','amp','and;','ang;','apos;',
    'aring;','aring','asymp;','atilde;','atilde','auml;','auml','bdquo;','beta;',
    'brvbar;','brvbar','bull;','cap;','ccedil;','ccedil','cedil;','cedil',
    'cent;','cent','chi;','circ;','clubs;','cong;','copy;','copy','crarr;',
    'cup;','curren;','curren','dArr;','dagger;','darr;','deg;','deg','delta;',
    'diams;','divide;','divide','eacute;','eacute','ecirc;','ecirc','egrave;',
    'egrave','empty;','emsp;','ensp;','epsilon;','equiv;','eta;','eth;','eth',
    'euml;','euml','euro;','exist;','fnof;','forall;','frac12;','frac12',
    'frac14;','frac14','frac34;','frac34','frasl;','gamma;','ge;','gt;','gt',
    'hArr;','harr;','hearts;','hellip;','iacute;','iacute','icirc;','icirc',
    'iexcl;','iexcl','igrave;','igrave','image;','infin;','int;','iota;',
    'iquest;','iquest','isin;','iuml;','iuml','kappa;','lArr;','lambda;','lang;',
    'laquo;','laquo','larr;','lceil;','ldquo;','le;','lfloor;','lowast;','loz;',
    'lrm;','lsaquo;','lsquo;','lt;','lt','macr;','macr','mdash;','micro;','micro',
    'middot;','middot','minus;','mu;','nabla;','nbsp;','nbsp','ndash;','ne;',
    'ni;','not;','not','notin;','nsub;','ntilde;','ntilde','nu;','oacute;',
    'oacute','ocirc;','ocirc','oelig;','ograve;','ograve','oline;','omega;',
    'omicron;','oplus;','or;','ordf;','ordf','ordm;','ordm','oslash;','oslash',
    'otilde;','otilde','otimes;','ouml;','ouml','para;','para','part;','permil;',
    'perp;','phi;','pi;','piv;','plusmn;','plusmn','pound;','pound','prime;',
    'prod;','prop;','psi;','quot;','quot','rArr;','radic;','rang;','raquo;',
    'raquo','rarr;','rceil;','rdquo;','real;','reg;','reg','rfloor;','rho;',
    'rlm;','rsaquo;','rsquo;','sbquo;','scaron;','sdot;','sect;','sect','shy;',
    'shy','sigma;','sigmaf;','sim;','spades;','sub;','sube;','sum;','sup1;',
    'sup1','sup2;','sup2','sup3;','sup3','sup;','supe;','szlig;','szlig','tau;',
    'there4;','theta;','thetasym;','thinsp;','thorn;','thorn','tilde;','times;',
    'times','trade;','uArr;','uacute;','uacute','uarr;','ucirc;','ucirc',
    'ugrave;','ugrave','uml;','uml','upsih;','upsilon;','uuml;','uuml','weierp;',
    'xi;','yacute;','yacute','yen;','yen','yuml;','yuml','zeta;','zwj;','zwnj;');

    const PCDATA    = 0;
    const RCDATA    = 1;
    const CDATA     = 2;
    const PLAINTEXT = 3;

    const DOCTYPE  = 0;
    const STARTTAG = 1;
    const ENDTAG   = 2;
    const COMMENT  = 3;
    const CHARACTR = 4;
    const EOF      = 5;

    public function __construct($data)
    {
        $data = str_replace("\r\n", "\n", $data);
        $date = str_replace("\r", null, $data);

        $this->data = $data;
        $this->char = -1;
        $this->EOF  = strlen($data);
        $this->tree = new HTML5TreeConstructer;
        $this->content_model = self::PCDATA;

        $this->state = 'data';

        while($this->state !== null) {
            $this->{$this->state.'State'}();
        }
    }

    public function save()
    {
        return $this->tree->save();
    }

    private function char()
    {
        return ($this->char < $this->EOF)
            ? $this->data[$this->char]
            : false;
    }

    private function character($s, $l = 0)
    {
        if($s + $l < $this->EOF) {
            if($l === 0) {
                return $this->data[$s];
            } else {
                return substr($this->data, $s, $l);
            }
        }
    }

    private function characters($char_class, $start)
    {
        return preg_replace('#^(['.$char_class.']+).*#s', '\\1', substr($this->data, $start));
    }

    private function dataState()
    {
        // Consume the next input character
        $this->char++;
        $char = $this->char();

        if($char === '&' && ($this->content_model === self::PCDATA || $this->content_model === self::RCDATA)) {
            /* U+0026 AMPERSAND (&)
            When the content model flag is set to one of the PCDATA or RCDATA
            states: switch to the entity data state. Otherwise: treat it as per
            the "anything else"    entry below. */
            $this->state = 'entityData';

        } elseif($char === '-') {
            /* If the content model flag is set to either the RCDATA state or
            the CDATA state, and the escape flag is false, and there are at
            least three characters before this one in the input stream, and the
            last four characters in the input stream, including this one, are
            U+003C LESS-THAN SIGN, U+0021 EXCLAMATION MARK, U+002D HYPHEN-MINUS,
            and U+002D HYPHEN-MINUS ("<!--"), then set the escape flag to true. */
            if(($this->content_model === self::RCDATA || $this->content_model ===
            self::CDATA) && $this->escape === false &&
            $this->char >= 3 && $this->character($this->char - 4, 4) === '<!--') {
                $this->escape = true;
            }

            /* In any case, emit the input character as a character token. Stay
            in the data state. */
            $this->emitToken(array(
                'type' => self::CHARACTR,
                'data' => $char
            ));

        /* U+003C LESS-THAN SIGN (<) */
        } elseif($char === '<' && ($this->content_model === self::PCDATA ||
        (($this->content_model === self::RCDATA ||
        $this->content_model === self::CDATA) && $this->escape === false))) {
            /* When the content model flag is set to the PCDATA state: switch
            to the tag open state.

            When the content model flag is set to either the RCDATA state or
            the CDATA state and the escape flag is false: switch to the tag
            open state.

            Otherwise: treat it as per the "anything else" entry below. */
            $this->state = 'tagOpen';

        /* U+003E GREATER-THAN SIGN (>) */
        } elseif($char === '>') {
            /* If the content model flag is set to either the RCDATA state or
            the CDATA state, and the escape flag is true, and the last three
            characters in the input stream including this one are U+002D
            HYPHEN-MINUS, U+002D HYPHEN-MINUS, U+003E GREATER-THAN SIGN ("-->"),
            set the escape flag to false. */
            if(($this->content_model === self::RCDATA ||
            $this->content_model === self::CDATA) && $this->escape === true &&
            $this->character($this->char, 3) === '-->') {
                $this->escape = false;
            }

            /* In any case, emit the input character as a character token.
            Stay in the data state. */
            $this->emitToken(array(
                'type' => self::CHARACTR,
                'data' => $char
            ));

        } elseif($this->char === $this->EOF) {
            /* EOF
            Emit an end-of-file token. */
            $this->EOF();

        } elseif($this->content_model === self::PLAINTEXT) {
            /* When the content model flag is set to the PLAINTEXT state
            THIS DIFFERS GREATLY FROM THE SPEC: Get the remaining characters of
            the text and emit it as a character token. */
            $this->emitToken(array(
                'type' => self::CHARACTR,
                'data' => substr($this->data, $this->char)
            ));

            $this->EOF();

        } else {
            /* Anything else
            THIS DIFFERS GREATLY FROM THE SPEC: Get as many character that
            otherwise would also be treated as a character token and emit it
            as a single character token. Stay in the data state. */
            $len  = strcspn($this->data, '<&', $this->char);
            $char = substr($this->data, $this->char, $len);
            $this->char += $len - 1;

            $this->emitToken(array(
                'type' => self::CHARACTR,
                'data' => $char
            ));

            $this->state = 'data';
        }
    }

    private function entityDataState()
    {
        // Attempt to consume an entity.
        $entity = $this->entity();

        // If nothing is returned, emit a U+0026 AMPERSAND character token.
        // Otherwise, emit the character token that was returned.
        $char = (!$entity) ? '&' : $entity;
        $this->emitToken($char);

        // Finally, switch to the data state.
        $this->state = 'data';
    }

    private function tagOpenState()
    {
        switch($this->content_model) {
            case self::RCDATA:
            case self::CDATA:
                /* If the next input character is a U+002F SOLIDUS (/) character,
                consume it and switch to the close tag open state. If the next
                input character is not a U+002F SOLIDUS (/) character, emit a
                U+003C LESS-THAN SIGN character token and switch to the data
                state to process the next input character. */
                if($this->character($this->char + 1) === '/') {
                    $this->char++;
                    $this->state = 'closeTagOpen';

                } else {
                    $this->emitToken(array(
                        'type' => self::CHARACTR,
                        'data' => '<'
                    ));

                    $this->state = 'data';
                }
            break;

            case self::PCDATA:
                // If the content model flag is set to the PCDATA state
                // Consume the next input character:
                $this->char++;
                $char = $this->char();

                if($char === '!') {
                    /* U+0021 EXCLAMATION MARK (!)
                    Switch to the markup declaration open state. */
                    $this->state = 'markupDeclarationOpen';

                } elseif($char === '/') {
                    /* U+002F SOLIDUS (/)
                    Switch to the close tag open state. */
                    $this->state = 'closeTagOpen';

                } elseif(preg_match('/^[A-Za-z]$/', $char)) {
                    /* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z
                    Create a new start tag token, set its tag name to the lowercase
                    version of the input character (add 0x0020 to the character's code
                    point), then switch to the tag name state. (Don't emit the token
                    yet; further details will be filled in before it is emitted.) */
                    $this->token = array(
                        'name'  => strtolower($char),
                        'type'  => self::STARTTAG,
                        'attr'  => array()
                    );

                    $this->state = 'tagName';

                } elseif($char === '>') {
                    /* U+003E GREATER-THAN SIGN (>)
                    Parse error. Emit a U+003C LESS-THAN SIGN character token and a
                    U+003E GREATER-THAN SIGN character token. Switch to the data state. */
                    $this->emitToken(array(
                        'type' => self::CHARACTR,
                        'data' => '<>'
                    ));

                    $this->state = 'data';

                } elseif($char === '?') {
                    /* U+003F QUESTION MARK (?)
                    Parse error. Switch to the bogus comment state. */
                    $this->state = 'bogusComment';

                } else {
                    /* Anything else
                    Parse error. Emit a U+003C LESS-THAN SIGN character token and
                    reconsume the current input character in the data state. */
                    $this->emitToken(array(
                        'type' => self::CHARACTR,
                        'data' => '<'
                    ));

                    $this->char--;
                    $this->state = 'data';
                }
            break;
        }
    }

    private function closeTagOpenState()
    {
        $next_node = strtolower($this->characters('A-Za-z', $this->char + 1));
        $the_same = count($this->tree->stack) > 0 && $next_node === end($this->tree->stack)->nodeName;

        if(($this->content_model === self::RCDATA || $this->content_model === self::CDATA) &&
        (!$the_same || ($the_same && (!preg_match('/[\t\n\x0b\x0c >\/]/',
        $this->character($this->char + 1 + strlen($next_node))) || $this->EOF === $this->char)))) {
            /* If the content model flag is set to the RCDATA or CDATA states then
            examine the next few characters. If they do not match the tag name of
            the last start tag token emitted (case insensitively), or if they do but
            they are not immediately followed by one of the following characters:
                * U+0009 CHARACTER TABULATION
                * U+000A LINE FEED (LF)
                * U+000B LINE TABULATION
                * U+000C FORM FEED (FF)
                * U+0020 SPACE
                * U+003E GREATER-THAN SIGN (>)
                * U+002F SOLIDUS (/)
                * EOF
            ...then there is a parse error. Emit a U+003C LESS-THAN SIGN character
            token, a U+002F SOLIDUS character token, and switch to the data state
            to process the next input character. */
            $this->emitToken(array(
                'type' => self::CHARACTR,
                'data' => '</'
            ));

            $this->state = 'data';

        } else {
            /* Otherwise, if the content model flag is set to the PCDATA state,
            or if the next few characters do match that tag name, consume the
            next input character: */
            $this->char++;
            $char = $this->char();

            if(preg_match('/^[A-Za-z]$/', $char)) {
                /* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z
                Create a new end tag token, set its tag name to the lowercase version
                of the input character (add 0x0020 to the character's code point), then
                switch to the tag name state. (Don't emit the token yet; further details
                will be filled in before it is emitted.) */
                $this->token = array(
                    'name'  => strtolower($char),
                    'type'  => self::ENDTAG
                );

                $this->state = 'tagName';

            } elseif($char === '>') {
                /* U+003E GREATER-THAN SIGN (>)
                Parse error. Switch to the data state. */
                $this->state = 'data';

            } elseif($this->char === $this->EOF) {
                /* EOF
                Parse error. Emit a U+003C LESS-THAN SIGN character token and a U+002F
                SOLIDUS character token. Reconsume the EOF character in the data state. */
                $this->emitToken(array(
                    'type' => self::CHARACTR,
                    'data' => '</'
                ));

                $this->char--;
                $this->state = 'data';

            } else {
                /* Parse error. Switch to the bogus comment state. */
                $this->state = 'bogusComment';
            }
        }
    }

    private function tagNameState()
    {
        // Consume the next input character:
        $this->char++;
        $char = $this->character($this->char);

        if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
            /* U+0009 CHARACTER TABULATION
            U+000A LINE FEED (LF)
            U+000B LINE TABULATION
            U+000C FORM FEED (FF)
            U+0020 SPACE
            Switch to the before attribute name state. */
            $this->state = 'beforeAttributeName';

        } elseif($char === '>') {
            /* U+003E GREATER-THAN SIGN (>)
            Emit the current tag token. Switch to the data state. */
            $this->emitToken($this->token);
            $this->state = 'data';

        } elseif($this->char === $this->EOF) {
            /* EOF
            Parse error. Emit the current tag token. Reconsume the EOF
            character in the data state. */
            $this->emitToken($this->token);

            $this->char--;
            $this->state = 'data';

        } elseif($char === '/') {
            /* U+002F SOLIDUS (/)
            Parse error unless this is a permitted slash. Switch to the before
            attribute name state. */
            $this->state = 'beforeAttributeName';

        } else {
            /* Anything else
            Append the current input character to the current tag token's tag name.
            Stay in the tag name state. */
            $this->token['name'] .= strtolower($char);
            $this->state = 'tagName';
        }
    }

    private function beforeAttributeNameState()
    {
        // Consume the next input character:
        $this->char++;
        $char = $this->character($this->char);

        if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
            /* U+0009 CHARACTER TABULATION
            U+000A LINE FEED (LF)
            U+000B LINE TABULATION
            U+000C FORM FEED (FF)
            U+0020 SPACE
            Stay in the before attribute name state. */
            $this->state = 'beforeAttributeName';

        } elseif($char === '>') {
            /* U+003E GREATER-THAN SIGN (>)
            Emit the current tag token. Switch to the data state. */
            $this->emitToken($this->token);
            $this->state = 'data';

        } elseif($char === '/') {
            /* U+002F SOLIDUS (/)
            Parse error unless this is a permitted slash. Stay in the before
            attribute name state. */
            $this->state = 'beforeAttributeName';

        } elseif($this->char === $this->EOF) {
            /* EOF
            Parse error. Emit the current tag token. Reconsume the EOF
            character in the data state. */
            $this->emitToken($this->token);

            $this->char--;
            $this->state = 'data';

        } else {
            /* Anything else
            Start a new attribute in the current tag token. Set that attribute's
            name to the current input character, and its value to the empty string.
            Switch to the attribute name state. */
            $this->token['attr'][] = array(
                'name'  => strtolower($char),
                'value' => null
            );

            $this->state = 'attributeName';
        }
    }

    private function attributeNameState()
    {
        // Consume the next input character:
        $this->char++;
        $char = $this->character($this->char);

        if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
            /* U+0009 CHARACTER TABULATION
            U+000A LINE FEED (LF)
            U+000B LINE TABULATION
            U+000C FORM FEED (FF)
            U+0020 SPACE
            Stay in the before attribute name state. */
            $this->state = 'afterAttributeName';

        } elseif($char === '=') {
            /* U+003D EQUALS SIGN (=)
            Switch to the before attribute value state. */
            $this->state = 'beforeAttributeValue';

        } elseif($char === '>') {
            /* U+003E GREATER-THAN SIGN (>)
            Emit the current tag token. Switch to the data state. */
            $this->emitToken($this->token);
            $this->state = 'data';

        } elseif($char === '/' && $this->character($this->char + 1) !== '>') {
            /* U+002F SOLIDUS (/)
            Parse error unless this is a permitted slash. Switch to the before
            attribute name state. */
            $this->state = 'beforeAttributeName';

        } elseif($this->char === $this->EOF) {
            /* EOF
            Parse error. Emit the current tag token. Reconsume the EOF
            character in the data state. */
            $this->emitToken($this->token);

            $this->char--;
            $this->state = 'data';

        } else {
            /* Anything else
            Append the current input character to the current attribute's name.
            Stay in the attribute name state. */
            $last = count($this->token['attr']) - 1;
            $this->token['attr'][$last]['name'] .= strtolower($char);

            $this->state = 'attributeName';
        }
    }

    private function afterAttributeNameState()
    {
        // Consume the next input character:
        $this->char++;
        $char = $this->character($this->char);

        if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
            /* U+0009 CHARACTER TABULATION
            U+000A LINE FEED (LF)
            U+000B LINE TABULATION
            U+000C FORM FEED (FF)
            U+0020 SPACE
            Stay in the after attribute name state. */
            $this->state = 'afterAttributeName';

        } elseif($char === '=') {
            /* U+003D EQUALS SIGN (=)
            Switch to the before attribute value state. */
            $this->state = 'beforeAttributeValue';

        } elseif($char === '>') {
            /* U+003E GREATER-THAN SIGN (>)
            Emit the current tag token. Switch to the data state. */
            $this->emitToken($this->token);
            $this->state = 'data';

        } elseif($char === '/' && $this->character($this->char + 1) !== '>') {
            /* U+002F SOLIDUS (/)
            Parse error unless this is a permitted slash. Switch to the
            before attribute name state. */
            $this->state = 'beforeAttributeName';

        } elseif($this->char === $this->EOF) {
            /* EOF
            Parse error. Emit the current tag token. Reconsume the EOF
            character in the data state. */
            $this->emitToken($this->token);

            $this->char--;
            $this->state = 'data';

        } else {
            /* Anything else
            Start a new attribute in the current tag token. Set that attribute's
            name to the current input character, and its value to the empty string.
            Switch to the attribute name state. */
            $this->token['attr'][] = array(
                'name'  => strtolower($char),
                'value' => null
            );

            $this->state = 'attributeName';
        }
    }

    private function beforeAttributeValueState()
    {
        // Consume the next input character:
        $this->char++;
        $char = $this->character($this->char);

        if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
            /* U+0009 CHARACTER TABULATION
            U+000A LINE FEED (LF)
            U+000B LINE TABULATION
            U+000C FORM FEED (FF)
            U+0020 SPACE
            Stay in the before attribute value state. */
            $this->state = 'beforeAttributeValue';

        } elseif($char === '"') {
            /* U+0022 QUOTATION MARK (")
            Switch to the attribute value (double-quoted) state. */
            $this->state = 'attributeValueDoubleQuoted';

        } elseif($char === '&') {
            /* U+0026 AMPERSAND (&)
            Switch to the attribute value (unquoted) state and reconsume
            this input character. */
            $this->char--;
            $this->state = 'attributeValueUnquoted';

        } elseif($char === '\'') {
            /* U+0027 APOSTROPHE (')
            Switch to the attribute value (single-quoted) state. */
            $this->state = 'attributeValueSingleQuoted';

        } elseif($char === '>') {
            /* U+003E GREATER-THAN SIGN (>)
            Emit the current tag token. Switch to the data state. */
            $this->emitToken($this->token);
            $this->state = 'data';

        } else {
            /* Anything else
            Append the current input character to the current attribute's value.
            Switch to the attribute value (unquoted) state. */
            $last = count($this->token['attr']) - 1;
            $this->token['attr'][$last]['value'] .= $char;

            $this->state = 'attributeValueUnquoted';
        }
    }

    private function attributeValueDoubleQuotedState()
    {
        // Consume the next input character:
        $this->char++;
        $char = $this->character($this->char);

        if($char === '"') {
            /* U+0022 QUOTATION MARK (")
            Switch to the before attribute name state. */
            $this->state = 'beforeAttributeName';

        } elseif($char === '&') {
            /* U+0026 AMPERSAND (&)
            Switch to the entity in attribute value state. */
            $this->entityInAttributeValueState('double');

        } elseif($this->char === $this->EOF) {
            /* EOF
            Parse error. Emit the current tag token. Reconsume the character
            in the data state. */
            $this->emitToken($this->token);

            $this->char--;
            $this->state = 'data';

        } else {
            /* Anything else
            Append the current input character to the current attribute's value.
            Stay in the attribute value (double-quoted) state. */
            $last = count($this->token['attr']) - 1;
            $this->token['attr'][$last]['value'] .= $char;

            $this->state = 'attributeValueDoubleQuoted';
        }
    }

    private function attributeValueSingleQuotedState()
    {
        // Consume the next input character:
        $this->char++;
        $char = $this->character($this->char);

        if($char === '\'') {
            /* U+0022 QUOTATION MARK (')
            Switch to the before attribute name state. */
            $this->state = 'beforeAttributeName';

        } elseif($char === '&') {
            /* U+0026 AMPERSAND (&)
            Switch to the entity in attribute value state. */
            $this->entityInAttributeValueState('single');

        } elseif($this->char === $this->EOF) {
            /* EOF
            Parse error. Emit the current tag token. Reconsume the character
            in the data state. */
            $this->emitToken($this->token);

            $this->char--;
            $this->state = 'data';

        } else {
            /* Anything else
            Append the current input character to the current attribute's value.
            Stay in the attribute value (single-quoted) state. */
            $last = count($this->token['attr']) - 1;
            $this->token['attr'][$last]['value'] .= $char;

            $this->state = 'attributeValueSingleQuoted';
        }
    }

    private function attributeValueUnquotedState()
    {
        // Consume the next input character:
        $this->char++;
        $char = $this->character($this->char);

        if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
            /* U+0009 CHARACTER TABULATION
            U+000A LINE FEED (LF)
            U+000B LINE TABULATION
            U+000C FORM FEED (FF)
            U+0020 SPACE
            Switch to the before attribute name state. */
            $this->state = 'beforeAttributeName';

        } elseif($char === '&') {
            /* U+0026 AMPERSAND (&)
            Switch to the entity in attribute value state. */
            $this->entityInAttributeValueState('non');

        } elseif($char === '>') {
            /* U+003E GREATER-THAN SIGN (>)
            Emit the current tag token. Switch to the data state. */
            $this->emitToken($this->token);
            $this->state = 'data';

        } else {
            /* Anything else
            Append the current input character to the current attribute's value.
            Stay in the attribute value (unquoted) state. */
            $last = count($this->token['attr']) - 1;
            $this->token['attr'][$last]['value'] .= $char;

            $this->state = 'attributeValueUnquoted';
        }
    }

    private function entityInAttributeValueState()
    {
        // Attempt to consume an entity.
        $entity = $this->entity();

        // If nothing is returned, append a U+0026 AMPERSAND character to the
        // current attribute's value. Otherwise, emit the character token that
        // was returned.
        $char = (!$entity)
            ? '&'
            : $entity;

        $this->emitToken($char);
    }

    private function bogusCommentState()
    {
        /* Consume every character up to the first U+003E GREATER-THAN SIGN
        character (>) or the end of the file (EOF), whichever comes first. Emit
        a comment token whose data is the concatenation of all the characters
        starting from and including the character that caused the state machine
        to switch into the bogus comment state, up to and including the last
        consumed character before the U+003E character, if any, or up to the
        end of the file otherwise. (If the comment was started by the end of
        the file (EOF), the token is empty.) */
        $data = $this->characters('^>', $this->char);
        $this->emitToken(array(
            'data' => $data,
            'type' => self::COMMENT
        ));

        $this->char += strlen($data);

        /* Switch to the data state. */
        $this->state = 'data';

        /* If the end of the file was reached, reconsume the EOF character. */
        if($this->char === $this->EOF) {
            $this->char = $this->EOF - 1;
        }
    }

    private function markupDeclarationOpenState()
    {
        /* If the next two characters are both U+002D HYPHEN-MINUS (-)
        characters, consume those two characters, create a comment token whose
        data is the empty string, and switch to the comment state. */
        if($this->character($this->char + 1, 2) === '--') {
            $this->char += 2;
            $this->state = 'comment';
            $this->token = array(
                'data' => null,
                'type' => self::COMMENT
            );

        /* Otherwise if the next seven chacacters are a case-insensitive match
        for the word "DOCTYPE", then consume those characters and switch to the
        DOCTYPE state. */
        } elseif(strtolower($this->character($this->char + 1, 7)) === 'doctype') {
            $this->char += 7;
            $this->state = 'doctype';

        /* Otherwise, is is a parse error. Switch to the bogus comment state.
        The next character that is consumed, if any, is the first character
        that will be in the comment. */
        } else {
            $this->char++;
            $this->state = 'bogusComment';
        }
    }

    private function commentState()
    {
        /* Consume the next input character: */
        $this->char++;
        $char = $this->char();

        /* U+002D HYPHEN-MINUS (-) */
        if($char === '-') {
            /* Switch to the comment dash state  */
            $this->state = 'commentDash';

        /* EOF */
        } elseif($this->char === $this->EOF) {
            /* Parse error. Emit the comment token. Reconsume the EOF character
            in the data state. */
            $this->emitToken($this->token);
            $this->char--;
            $this->state = 'data';

        /* Anything else */
        } else {
            /* Append the input character to the comment token's data. Stay in
            the comment state. */
            $this->token['data'] .= $char;
        }
    }

    private function commentDashState()
    {
        /* Consume the next input character: */
        $this->char++;
        $char = $this->char();

        /* U+002D HYPHEN-MINUS (-) */
        if($char === '-') {
            /* Switch to the comment end state  */
            $this->state = 'commentEnd';

        /* EOF */
        } elseif($this->char === $this->EOF) {
            /* Parse error. Emit the comment token. Reconsume the EOF character
            in the data state. */
            $this->emitToken($this->token);
            $this->char--;
            $this->state = 'data';

        /* Anything else */
        } else {
            /* Append a U+002D HYPHEN-MINUS (-) character and the input
            character to the comment token's data. Switch to the comment state. */
            $this->token['data'] .= '-'.$char;
            $this->state = 'comment';
        }
    }

    private function commentEndState()
    {
        /* Consume the next input character: */
        $this->char++;
        $char = $this->char();

        if($char === '>') {
            $this->emitToken($this->token);
            $this->state = 'data';

        } elseif($char === '-') {
            $this->token['data'] .= '-';

        } elseif($this->char === $this->EOF) {
            $this->emitToken($this->token);
            $this->char--;
            $this->state = 'data';

        } else {
            $this->token['data'] .= '--'.$char;
            $this->state = 'comment';
        }
    }

    private function doctypeState()
    {
        /* Consume the next input character: */
        $this->char++;
        $char = $this->char();

        if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
            $this->state = 'beforeDoctypeName';

        } else {
            $this->char--;
            $this->state = 'beforeDoctypeName';
        }
    }

    private function beforeDoctypeNameState()
    {
        /* Consume the next input character: */
        $this->char++;
        $char = $this->char();

        if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
            // Stay in the before DOCTYPE name state.

        } elseif(preg_match('/^[a-z]$/', $char)) {
            $this->token = array(
                'name' => strtoupper($char),
                'type' => self::DOCTYPE,
                'error' => true
            );

            $this->state = 'doctypeName';

        } elseif($char === '>') {
            $this->emitToken(array(
                'name' => null,
                'type' => self::DOCTYPE,
                'error' => true
            ));

            $this->state = 'data';

        } elseif($this->char === $this->EOF) {
            $this->emitToken(array(
                'name' => null,
                'type' => self::DOCTYPE,
                'error' => true
            ));

            $this->char--;
            $this->state = 'data';

        } else {
            $this->token = array(
                'name' => $char,
                'type' => self::DOCTYPE,
                'error' => true
            );

            $this->state = 'doctypeName';
        }
    }

    private function doctypeNameState()
    {
        /* Consume the next input character: */
        $this->char++;
        $char = $this->char();

        if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
            $this->state = 'AfterDoctypeName';

        } elseif($char === '>') {
            $this->emitToken($this->token);
            $this->state = 'data';

        } elseif(preg_match('/^[a-z]$/', $char)) {
            $this->token['name'] .= strtoupper($char);

        } elseif($this->char === $this->EOF) {
            $this->emitToken($this->token);
            $this->char--;
            $this->state = 'data';

        } else {
            $this->token['name'] .= $char;
        }

        $this->token['error'] = ($this->token['name'] === 'HTML')
            ? false
            : true;
    }

    private function afterDoctypeNameState()
    {
        /* Consume the next input character: */
        $this->char++;
        $char = $this->char();

        if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
            // Stay in the DOCTYPE name state.

        } elseif($char === '>') {
            $this->emitToken($this->token);
            $this->state = 'data';

        } elseif($this->char === $this->EOF) {
            $this->emitToken($this->token);
            $this->char--;
            $this->state = 'data';

        } else {
            $this->token['error'] = true;
            $this->state = 'bogusDoctype';
        }
    }

    private function bogusDoctypeState()
    {
        /* Consume the next input character: */
        $this->char++;
        $char = $this->char();

        if($char === '>') {
            $this->emitToken($this->token);
            $this->state = 'data';

        } elseif($this->char === $this->EOF) {
            $this->emitToken($this->token);
            $this->char--;
            $this->state = 'data';

        } else {
            // Stay in the bogus DOCTYPE state.
        }
    }

    private function entity()
    {
        $start = $this->char;

        // This section defines how to consume an entity. This definition is
        // used when parsing entities in text and in attributes.

        // The behaviour depends on the identity of the next character (the
        // one immediately after the U+0026 AMPERSAND character):

        switch($this->character($this->char + 1)) {
            // U+0023 NUMBER SIGN (#)
            case '#':

                // The behaviour further depends on the character after the
                // U+0023 NUMBER SIGN:
                switch($this->character($this->char + 1)) {
                    // U+0078 LATIN SMALL LETTER X
                    // U+0058 LATIN CAPITAL LETTER X
                    case 'x':
                    case 'X':
                        // Follow the steps below, but using the range of
                        // characters U+0030 DIGIT ZERO through to U+0039 DIGIT
                        // NINE, U+0061 LATIN SMALL LETTER A through to U+0066
                        // LATIN SMALL LETTER F, and U+0041 LATIN CAPITAL LETTER
                        // A, through to U+0046 LATIN CAPITAL LETTER F (in other
                        // words, 0-9, A-F, a-f).
                        $char = 1;
                        $char_class = '0-9A-Fa-f';
                    break;

                    // Anything else
                    default:
                        // Follow the steps below, but using the range of
                        // characters U+0030 DIGIT ZERO through to U+0039 DIGIT
                        // NINE (i.e. just 0-9).
                        $char = 0;
                        $char_class = '0-9';
                    break;
                }

                // Consume as many characters as match the range of characters
                // given above.
                $this->char++;
                $e_name = $this->characters($char_class, $this->char + $char + 1);
                $entity = $this->character($start, $this->char);
                $cond = strlen($e_name) > 0;

                // The rest of the parsing happens below.
            break;

            // Anything else
            default:
                // Consume the maximum number of characters possible, with the
                // consumed characters case-sensitively matching one of the
                // identifiers in the first column of the entities table.
                $e_name = $this->characters('0-9A-Za-z;', $this->char + 1);
                $len = strlen($e_name);

                for($c = 1; $c <= $len; $c++) {
                    $id = substr($e_name, 0, $c);
                    $this->char++;

                    if(in_array($id, $this->entities)) {
                        $entity = $id;
                        break;
                    }
                }

                $cond = isset($entity);
                // The rest of the parsing happens below.
            break;
        }

        if(!$cond) {
            // If no match can be made, then this is a parse error. No
            // characters are consumed, and nothing is returned.
            $this->char = $start;
            return false;
        }

        // Return a character token for the character corresponding to the
        // entity name (as given by the second column of the entities table).
        return html_entity_decode('&'.$entity.';', ENT_QUOTES, 'UTF-8');
    }

    private function emitToken($token)
    {
        $emit = $this->tree->emitToken($token);

        if(is_int($emit)) {
            $this->content_model = $emit;

        } elseif($token['type'] === self::ENDTAG) {
            $this->content_model = self::PCDATA;
        }
    }

    private function EOF()
    {
        $this->state = null;
        $this->tree->emitToken(array(
            'type' => self::EOF
        ));
    }
}

class HTML5TreeConstructer
{
    public $stack = array();

    private $phase;
    private $mode;
    private $dom;
    private $foster_parent = null;
    private $a_formatting  = array();

    private $head_pointer = null;
    private $form_pointer = null;

    private $scoping = array('button','caption','html','marquee','object','table','td','th');
    private $formatting = array('a','b','big','em','font','i','nobr','s','small','strike','strong','tt','u');
    private $special = array('address','area','base','basefont','bgsound',
    'blockquote','body','br','center','col','colgroup','dd','dir','div','dl',
    'dt','embed','fieldset','form','frame','frameset','h1','h2','h3','h4','h5',
    'h6','head','hr','iframe','image','img','input','isindex','li','link',
    'listing','menu','meta','noembed','noframes','noscript','ol','optgroup',
    'option','p','param','plaintext','pre','script','select','spacer','style',
    'tbody','textarea','tfoot','thead','title','tr','ul','wbr');

    // The different phases.
    const INIT_PHASE = 0;
    const ROOT_PHASE = 1;
    const MAIN_PHASE = 2;
    const END_PHASE  = 3;

    // The different insertion modes for the main phase.
    const BEFOR_HEAD = 0;
    const IN_HEAD    = 1;
    const AFTER_HEAD = 2;
    const IN_BODY    = 3;
    const IN_TABLE   = 4;
    const IN_CAPTION = 5;
    const IN_CGROUP  = 6;
    const IN_TBODY   = 7;
    const IN_ROW     = 8;
    const IN_CELL    = 9;
    const IN_SELECT  = 10;
    const AFTER_BODY = 11;
    const IN_FRAME   = 12;
    const AFTR_FRAME = 13;

    // The different types of elements.
    const SPECIAL    = 0;
    const SCOPING    = 1;
    const FORMATTING = 2;
    const PHRASING   = 3;

    const MARKER     = 0;

    public function __construct()
    {
        $this->phase = self::INIT_PHASE;
        $this->mode = self::BEFOR_HEAD;
        $this->dom = new DOMDocument;

        $this->dom->encoding = 'UTF-8';
        $this->dom->preserveWhiteSpace = true;
        $this->dom->substituteEntities = true;
        $this->dom->strictErrorChecking = false;
    }

    // Process tag tokens
    public function emitToken($token)
    {
        switch($this->phase) {
            case self::INIT_PHASE: return $this->initPhase($token); break;
            case self::ROOT_PHASE: return $this->rootElementPhase($token); break;
            case self::MAIN_PHASE: return $this->mainPhase($token); break;
            case self::END_PHASE : return $this->trailingEndPhase($token); break;
        }
    }

    private function initPhase($token)
    {
        /* Initially, the tree construction stage must handle each token
        emitted from the tokenisation stage as follows: */

        /* A DOCTYPE token that is marked as being in error
        A comment token
        A start tag token
        An end tag token
        A character token that is not one of one of U+0009 CHARACTER TABULATION,
            U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
            or U+0020 SPACE
        An end-of-file token */
        if((isset($token['error']) && $token['error']) ||
        $token['type'] === HTML5::COMMENT ||
        $token['type'] === HTML5::STARTTAG ||
        $token['type'] === HTML5::ENDTAG ||
        $token['type'] === HTML5::EOF ||
        ($token['type'] === HTML5::CHARACTR && isset($token['data']) &&
        !preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']))) {
            /* This specification does not define how to handle this case. In
            particular, user agents may ignore the entirety of this specification
            altogether for such documents, and instead invoke special parse modes
            with a greater emphasis on backwards compatibility. */

            $this->phase = self::ROOT_PHASE;
            return $this->rootElementPhase($token);

        /* A DOCTYPE token marked as being correct */
        } elseif(isset($token['error']) && !$token['error']) {
            /* Append a DocumentType node to the Document  node, with the name
            attribute set to the name given in the DOCTYPE token (which will be
            "HTML"), and the other attributes specific to DocumentType objects
            set to null, empty lists, or the empty string as appropriate. */
            $doctype = new DOMDocumentType(null, null, 'HTML');

            /* Then, switch to the root element phase of the tree construction
            stage. */
            $this->phase = self::ROOT_PHASE;

        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
        or U+0020 SPACE */
        } elseif(isset($token['data']) && preg_match('/^[\t\n\x0b\x0c ]+$/',
        $token['data'])) {
            /* Append that character  to the Document node. */
            $text = $this->dom->createTextNode($token['data']);
            $this->dom->appendChild($text);
        }
    }

    private function rootElementPhase($token)
    {
        /* After the initial phase, as each token is emitted from the tokenisation
        stage, it must be processed as described in this section. */

        /* A DOCTYPE token */
        if($token['type'] === HTML5::DOCTYPE) {
            // Parse error. Ignore the token.

        /* A comment token */
        } elseif($token['type'] === HTML5::COMMENT) {
            /* Append a Comment node to the Document object with the data
            attribute set to the data given in the comment token. */
            $comment = $this->dom->createComment($token['data']);
            $this->dom->appendChild($comment);

        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
        or U+0020 SPACE */
        } elseif($token['type'] === HTML5::CHARACTR &&
        preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) {
            /* Append that character  to the Document node. */
            $text = $this->dom->createTextNode($token['data']);
            $this->dom->appendChild($text);

        /* A character token that is not one of U+0009 CHARACTER TABULATION,
            U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED
            (FF), or U+0020 SPACE
        A start tag token
        An end tag token
        An end-of-file token */
        } elseif(($token['type'] === HTML5::CHARACTR &&
        !preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) ||
        $token['type'] === HTML5::STARTTAG ||
        $token['type'] === HTML5::ENDTAG ||
        $token['type'] === HTML5::EOF) {
            /* Create an HTMLElement node with the tag name html, in the HTML
            namespace. Append it to the Document object. Switch to the main
            phase and reprocess the current token. */
            $html = $this->dom->createElement('html');
            $this->dom->appendChild($html);
            $this->stack[] = $html;

            $this->phase = self::MAIN_PHASE;
            return $this->mainPhase($token);
        }
    }

    private function mainPhase($token)
    {
        /* Tokens in the main phase must be handled as follows: */

        /* A DOCTYPE token */
        if($token['type'] === HTML5::DOCTYPE) {
            // Parse error. Ignore the token.

        /* A start tag token with the tag name "html" */
        } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'html') {
            /* If this start tag token was not the first start tag token, then
            it is a parse error. */

            /* For each attribute on the token, check to see if the attribute
            is already present on the top element of the stack of open elements.
            If it is not, add the attribute and its corresponding value to that
            element. */
            foreach($token['attr'] as $attr) {
                if(!$this->stack[0]->hasAttribute($attr['name'])) {
                    $this->stack[0]->setAttribute($attr['name'], $attr['value']);
                }
            }

        /* An end-of-file token */
        } elseif($token['type'] === HTML5::EOF) {
            /* Generate implied end tags. */
            $this->generateImpliedEndTags();

        /* Anything else. */
        } else {
            /* Depends on the insertion mode: */
            switch($this->mode) {
                case self::BEFOR_HEAD: return $this->beforeHead($token); break;
                case self::IN_HEAD:    return $this->inHead($token); break;
                case self::AFTER_HEAD: return $this->afterHead($token); break;
                case self::IN_BODY:    return $this->inBody($token); break;
                case self::IN_TABLE:   return $this->inTable($token); break;
                case self::IN_CAPTION: return $this->inCaption($token); break;
                case self::IN_CGROUP:  return $this->inColumnGroup($token); break;
                case self::IN_TBODY:   return $this->inTableBody($token); break;
                case self::IN_ROW:     return $this->inRow($token); break;
                case self::IN_CELL:    return $this->inCell($token); break;
                case self::IN_SELECT:  return $this->inSelect($token); break;
                case self::AFTER_BODY: return $this->afterBody($token); break;
                case self::IN_FRAME:   return $this->inFrameset($token); break;
                case self::AFTR_FRAME: return $this->afterFrameset($token); break;
                case self::END_PHASE:  return $this->trailingEndPhase($token); break;
            }
        }
    }

    private function beforeHead($token)
    {
        /* Handle the token as follows: */

        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
        or U+0020 SPACE */
        if($token['type'] === HTML5::CHARACTR &&
        preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) {
            /* Append the character to the current node. */
            $this->insertText($token['data']);

        /* A comment token */
        } elseif($token['type'] === HTML5::COMMENT) {
            /* Append a Comment node to the current node with the data attribute
            set to the data given in the comment token. */
            $this->insertComment($token['data']);

        /* A start tag token with the tag name "head" */
        } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'head') {
            /* Create an element for the token, append the new element to the
            current node and push it onto the stack of open elements. */
            $element = $this->insertElement($token);

            /* Set the head element pointer to this new element node. */
            $this->head_pointer = $element;

            /* Change the insertion mode to "in head". */
            $this->mode = self::IN_HEAD;

        /* A start tag token whose tag name is one of: "base", "link", "meta",
        "script", "style", "title". Or an end tag with the tag name "html".
        Or a character token that is not one of U+0009 CHARACTER TABULATION,
        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
        or U+0020 SPACE. Or any other start tag token */
        } elseif($token['type'] === HTML5::STARTTAG ||
        ($token['type'] === HTML5::ENDTAG && $token['name'] === 'html') ||
        ($token['type'] === HTML5::CHARACTR && !preg_match('/^[\t\n\x0b\x0c ]$/',
        $token['data']))) {
            /* Act as if a start tag token with the tag name "head" and no
            attributes had been seen, then reprocess the current token. */
            $this->beforeHead(array(
                'name' => 'head',
                'type' => HTML5::STARTTAG,
                'attr' => array()
            ));

            return $this->inHead($token);

        /* Any other end tag */
        } elseif($token['type'] === HTML5::ENDTAG) {
            /* Parse error. Ignore the token. */
        }
    }

    private function inHead($token)
    {
        /* Handle the token as follows: */

        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
        or U+0020 SPACE.

        THIS DIFFERS FROM THE SPEC: If the current node is either a title, style
        or script element, append the character to the current node regardless
        of its content. */
        if(($token['type'] === HTML5::CHARACTR &&
        preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || (
        $token['type'] === HTML5::CHARACTR && in_array(end($this->stack)->nodeName,
        array('title', 'style', 'script')))) {
            /* Append the character to the current node. */
            $this->insertText($token['data']);

        /* A comment token */
        } elseif($token['type'] === HTML5::COMMENT) {
            /* Append a Comment node to the current node with the data attribute
            set to the data given in the comment token. */
            $this->insertComment($token['data']);

        } elseif($token['type'] === HTML5::ENDTAG &&
        in_array($token['name'], array('title', 'style', 'script'))) {
            array_pop($this->stack);
            return HTML5::PCDATA;

        /* A start tag with the tag name "title" */
        } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'title') {
            /* Create an element for the token and append the new element to the
            node pointed to by the head element pointer, or, if that is null
            (innerHTML case), to the current node. */
            if($this->head_pointer !== null) {
                $element = $this->insertElement($token, false);
                $this->head_pointer->appendChild($element);

            } else {
                $element = $this->insertElement($token);
            }

            /* Switch the tokeniser's content model flag  to the RCDATA state. */
            return HTML5::RCDATA;

        /* A start tag with the tag name "style" */
        } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'style') {
            /* Create an element for the token and append the new element to the
            node pointed to by the head element pointer, or, if that is null
            (innerHTML case), to the current node. */
            if($this->head_pointer !== null) {
                $element = $this->insertElement($token, false);
                $this->head_pointer->appendChild($element);

            } else {
                $this->insertElement($token);
            }

            /* Switch the tokeniser's content model flag  to the CDATA state. */
            return HTML5::CDATA;

        /* A start tag with the tag name "script" */
        } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'script') {
            /* Create an element for the token. */
            $element = $this->insertElement($token, false);
            $this->head_pointer->appendChild($element);

            /* Switch the tokeniser's content model flag  to the CDATA state. */
            return HTML5::CDATA;

        /* A start tag with the tag name "base", "link", or "meta" */
        } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'],
        array('base', 'link', 'meta'))) {
            /* Create an element for the token and append the new element to the
            node pointed to by the head element pointer, or, if that is null
            (innerHTML case), to the current node. */
            if($this->head_pointer !== null) {
                $element = $this->insertElement($token, false);
                $this->head_pointer->appendChild($element);
                array_pop($this->stack);

            } else {
                $this->insertElement($token);
            }

        /* An end tag with the tag name "head" */
        } elseif($token['type'] === HTML5::ENDTAG && $token['name'] === 'head') {
            /* If the current node is a head element, pop the current node off
            the stack of open elements. */
            if($this->head_pointer->isSameNode(end($this->stack))) {
                array_pop($this->stack);

            /* Otherwise, this is a parse error. */
            } else {
                // k
            }

            /* Change the insertion mode to "after head". */
            $this->mode = self::AFTER_HEAD;

        /* A start tag with the tag name "head" or an end tag except "html". */
        } elseif(($token['type'] === HTML5::STARTTAG && $token['name'] === 'head') ||
        ($token['type'] === HTML5::ENDTAG && $token['name'] !== 'html')) {
            // Parse error. Ignore the token.

        /* Anything else */
        } else {
            /* If the current node is a head element, act as if an end tag
            token with the tag name "head" had been seen. */
            if($this->head_pointer->isSameNode(end($this->stack))) {
                $this->inHead(array(
                    'name' => 'head',
                    'type' => HTML5::ENDTAG
                ));

            /* Otherwise, change the insertion mode to "after head". */
            } else {
                $this->mode = self::AFTER_HEAD;
            }

            /* Then, reprocess the current token. */
            return $this->afterHead($token);
        }
    }

    private function afterHead($token)
    {
        /* Handle the token as follows: */

        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
        or U+0020 SPACE */
        if($token['type'] === HTML5::CHARACTR &&
        preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) {
            /* Append the character to the current node. */
            $this->insertText($token['data']);

        /* A comment token */
        } elseif($token['type'] === HTML5::COMMENT) {
            /* Append a Comment node to the current node with the data attribute
            set to the data given in the comment token. */
            $this->insertComment($token['data']);

        /* A start tag token with the tag name "body" */
        } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'body') {
            /* Insert a body element for the token. */
            $this->insertElement($token);

            /* Change the insertion mode to "in body". */
            $this->mode = self::IN_BODY;

        /* A start tag token with the tag name "frameset" */
        } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'frameset') {
            /* Insert a frameset element for the token. */
            $this->insertElement($token);

            /* Change the insertion mode to "in frameset". */
            $this->mode = self::IN_FRAME;

        /* A start tag token whose tag name is one of: "base", "link", "meta",
        "script", "style", "title" */
        } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'],
        array('base', 'link', 'meta', 'script', 'style', 'title'))) {
            /* Parse error. Switch the insertion mode back to "in head" and
            reprocess the token. */
            $this->mode = self::IN_HEAD;
            return $this->inHead($token);

        /* Anything else */
        } else {
            /* Act as if a start tag token with the tag name "body" and no
            attributes had been seen, and then reprocess the current token. */
            $this->afterHead(array(
                'name' => 'body',
                'type' => HTML5::STARTTAG,
                'attr' => array()
            ));

            return $this->inBody($token);
        }
    }

    private function inBody($token)
    {
        /* Handle the token as follows: */

        switch($token['type']) {
            /* A character token */
            case HTML5::CHARACTR:
                /* Reconstruct the active formatting elements, if any. */
                $this->reconstructActiveFormattingElements();

                /* Append the token's character to the current node. */
                $this->insertText($token['data']);
            break;

            /* A comment token */
            case HTML5::COMMENT:
                /* Append a Comment node to the current node with the data
                attribute set to the data given in the comment token. */
                $this->insertComment($token['data']);
            break;

            case HTML5::STARTTAG:
            switch($token['name']) {
                /* A start tag token whose tag name is one of: "script",
                "style" */
                case 'script': case 'style':
                    /* Process the token as if the insertion mode had been "in
                    head". */
                    return $this->inHead($token);
                break;

                /* A start tag token whose tag name is one of: "base", "link",
                "meta", "title" */
                case 'base': case 'link': case 'meta': case 'title':
                    /* Parse error. Process the token as if the insertion mode
                    had    been "in head". */
                    return $this->inHead($token);
                break;

                /* A start tag token with the tag name "body" */
                case 'body':
                    /* Parse error. If the second element on the stack of open
                    elements is not a body element, or, if the stack of open
                    elements has only one node on it, then ignore the token.
                    (innerHTML case) */
                    if(count($this->stack) === 1 || $this->stack[1]->nodeName !== 'body') {
                        // Ignore

                    /* Otherwise, for each attribute on the token, check to see
                    if the attribute is already present on the body element (the
                    second element)    on the stack of open elements. If it is not,
                    add the attribute and its corresponding value to that
                    element. */
                    } else {
                        foreach($token['attr'] as $attr) {
                            if(!$this->stack[1]->hasAttribute($attr['name'])) {
                                $this->stack[1]->setAttribute($attr['name'], $attr['value']);
                            }
                        }
                    }
                break;

                /* A start tag whose tag name is one of: "address",
                "blockquote", "center", "dir", "div", "dl", "fieldset",
                "listing", "menu", "ol", "p", "ul" */
                case 'address': case 'blockquote': case 'center': case 'dir':
                case 'div': case 'dl': case 'fieldset': case 'listing':
                case 'menu': case 'ol': case 'p': case 'ul':
                    /* If the stack of open elements has a p element in scope,
                    then act as if an end tag with the tag name p had been
                    seen. */
                    if($this->elementInScope('p')) {
                        $this->emitToken(array(
                            'name' => 'p',
                            'type' => HTML5::ENDTAG
                        ));
                    }

                    /* Insert an HTML element for the token. */
                    $this->insertElement($token);
                break;

                /* A start tag whose tag name is "form" */
                case 'form':
                    /* If the form element pointer is not null, ignore the
                    token with a parse error. */
                    if($this->form_pointer !== null) {
                        // Ignore.

                    /* Otherwise: */
                    } else {
                        /* If the stack of open elements has a p element in
                        scope, then act as if an end tag with the tag name p
                        had been seen. */
                        if($this->elementInScope('p')) {
                            $this->emitToken(array(
                                'name' => 'p',
                                'type' => HTML5::ENDTAG
                            ));
                        }

                        /* Insert an HTML element for the token, and set the
                        form element pointer to point to the element created. */
                        $element = $this->insertElement($token);
                        $this->form_pointer = $element;
                    }
                break;

                /* A start tag whose tag name is "li", "dd" or "dt" */
                case 'li': case 'dd': case 'dt':
                    /* If the stack of open elements has a p  element in scope,
                    then act as if an end tag with the tag name p had been
                    seen. */
                    if($this->elementInScope('p')) {
                        $this->emitToken(array(
                            'name' => 'p',
                            'type' => HTML5::ENDTAG
                        ));
                    }

                    $stack_length = count($this->stack) - 1;

                    for($n = $stack_length; 0 <= $n; $n--) {
                        /* 1. Initialise node to be the current node (the
                        bottommost node of the stack). */
                        $stop = false;
                        $node = $this->stack[$n];
                        $cat  = $this->getElementCategory($node->tagName);

                        /* 2. If node is an li, dd or dt element, then pop all
                        the    nodes from the current node up to node, including
                        node, then stop this algorithm. */
                        if($token['name'] === $node->tagName ||    ($token['name'] !== 'li'
                        && ($node->tagName === 'dd' || $node->tagName === 'dt'))) {
                            for($x = $stack_length; $x >= $n ; $x--) {
                                array_pop($this->stack);
                            }

                            break;
                        }

                        /* 3. If node is not in the formatting category, and is
                        not    in the phrasing category, and is not an address or
                        div element, then stop this algorithm. */
                        if($cat !== self::FORMATTING && $cat !== self::PHRASING &&
                        $node->tagName !== 'address' && $node->tagName !== 'div') {
                            break;
                        }
                    }

                    /* Finally, insert an HTML element with the same tag
                    name as the    token's. */
                    $this->insertElement($token);
                break;

                /* A start tag token whose tag name is "plaintext" */
                case 'plaintext':
                    /* If the stack of open elements has a p  element in scope,
                    then act as if an end tag with the tag name p had been
                    seen. */
                    if($this->elementInScope('p')) {
                        $this->emitToken(array(
                            'name' => 'p',
                            'type' => HTML5::ENDTAG
                        ));
                    }

                    /* Insert an HTML element for the token. */
                    $this->insertElement($token);

                    return HTML5::PLAINTEXT;
                break;

                /* A start tag whose tag name is one of: "h1", "h2", "h3", "h4",
                "h5", "h6" */
                case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6':
                    /* If the stack of open elements has a p  element in scope,
                    then act as if an end tag with the tag name p had been seen. */
                    if($this->elementInScope('p')) {
                        $this->emitToken(array(
                            'name' => 'p',
                            'type' => HTML5::ENDTAG
                        ));
                    }

                    /* If the stack of open elements has in scope an element whose
                    tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then
                    this is a parse error; pop elements from the stack until an
                    element with one of those tag names has been popped from the
                    stack. */
                    while($this->elementInScope(array('h1', 'h2', 'h3', 'h4', 'h5', 'h6'))) {
                        array_pop($this->stack);
                    }

                    /* Insert an HTML element for the token. */
                    $this->insertElement($token);
                break;

                /* A start tag whose tag name is "a" */
                case 'a':
                    /* If the list of active formatting elements contains
                    an element whose tag name is "a" between the end of the
                    list and the last marker on the list (or the start of
                    the list if there is no marker on the list), then this
                    is a parse error; act as if an end tag with the tag name
                    "a" had been seen, then remove that element from the list
                    of active formatting elements and the stack of open
                    elements if the end tag didn't already remove it (it
                    might not have if the element is not in table scope). */
                    $leng = count($this->a_formatting);

                    for($n = $leng - 1; $n >= 0; $n--) {
                        if($this->a_formatting[$n] === self::MARKER) {
                            break;

                        } elseif($this->a_formatting[$n]->nodeName === 'a') {
                            $this->emitToken(array(
                                'name' => 'a',
                                'type' => HTML5::ENDTAG
                            ));
                            break;
                        }
                    }

                    /* Reconstruct the active formatting elements, if any. */
                    $this->reconstructActiveFormattingElements();

                    /* Insert an HTML element for the token. */
                    $el = $this->insertElement($token);

                    /* Add that element to the list of active formatting
                    elements. */
                    $this->a_formatting[] = $el;
                break;

                /* A start tag whose tag name is one of: "b", "big", "em", "font",
                "i", "nobr", "s", "small", "strike", "strong", "tt", "u" */
                case 'b': case 'big': case 'em': case 'font': case 'i':
                case 'nobr': case 's': case 'small': case 'strike':
                case 'strong': case 'tt': case 'u':
                    /* Reconstruct the active formatting elements, if any. */
                    $this->reconstructActiveFormattingElements();

                    /* Insert an HTML element for the token. */
                    $el = $this->insertElement($token);

                    /* Add that element to the list of active formatting
                    elements. */
                    $this->a_formatting[] = $el;
                break;

                /* A start tag token whose tag name is "button" */
                case 'button':
                    /* If the stack of open elements has a button element in scope,
                    then this is a parse error; act as if an end tag with the tag
                    name "button" had been seen, then reprocess the token. (We don't
                    do that. Unnecessary.) */
                    if($this->elementInScope('button')) {
                        $this->inBody(array(
                            'name' => 'button',
                            'type' => HTML5::ENDTAG
                        ));
                    }

                    /* Reconstruct the active formatting elements, if any. */
                    $this->reconstructActiveFormattingElements();

                    /* Insert an HTML element for the token. */
                    $this->insertElement($token);

                    /* Insert a marker at the end of the list of active
                    formatting elements. */
                    $this->a_formatting[] = self::MARKER;
                break;

                /* A start tag token whose tag name is one of: "marquee", "object" */
                case 'marquee': case 'object':
                    /* Reconstruct the active formatting elements, if any. */
                    $this->reconstructActiveFormattingElements();

                    /* Insert an HTML element for the token. */
                    $this->insertElement($token);

                    /* Insert a marker at the end of the list of active
                    formatting elements. */
                    $this->a_formatting[] = self::MARKER;
                break;

                /* A start tag token whose tag name is "xmp" */
                case 'xmp':
                    /* Reconstruct the active formatting elements, if any. */
                    $this->reconstructActiveFormattingElements();

                    /* Insert an HTML element for the token. */
                    $this->insertElement($token);

                    /* Switch the content model flag to the CDATA state. */
                    return HTML5::CDATA;
                break;

                /* A start tag whose tag name is "table" */
                case 'table':
                    /* If the stack of open elements has a p element in scope,
                    then act as if an end tag with the tag name p had been seen. */
                    if($this->elementInScope('p')) {
                        $this->emitToken(array(
                            'name' => 'p',
                            'type' => HTML5::ENDTAG
                        ));
                    }

                    /* Insert an HTML element for the token. */
                    $this->insertElement($token);

                    /* Change the insertion mode to "in table". */
                    $this->mode = self::IN_TABLE;
                break;

                /* A start tag whose tag name is one of: "area", "basefont",
                "bgsound", "br", "embed", "img", "param", "spacer", "wbr" */
                case 'area': case 'basefont': case 'bgsound': case 'br':
                case 'embed': case 'img': case 'param': case 'spacer':
                case 'wbr':
                    /* Reconstruct the active formatting elements, if any. */
                    $this->reconstructActiveFormattingElements();

                    /* Insert an HTML element for the token. */
                    $this->insertElement($token);

                    /* Immediately pop the current node off the stack of open elements. */
                    array_pop($this->stack);
                break;

                /* A start tag whose tag name is "hr" */
                case 'hr':
                    /* If the stack of open elements has a p element in scope,
                    then act as if an end tag with the tag name p had been seen. */
                    if($this->elementInScope('p')) {
                        $this->emitToken(array(
                            'name' => 'p',
                            'type' => HTML5::ENDTAG
                        ));
                    }

                    /* Insert an HTML element for the token. */
                    $this->insertElement($token);

                    /* Immediately pop the current node off the stack of open elements. */
                    array_pop($this->stack);
                break;

                /* A start tag whose tag name is "image" */
                case 'image':
                    /* Parse error. Change the token's tag name to "img" and
                    reprocess it. (Don't ask.) */
                    $token['name'] = 'img';
                    return $this->inBody($token);
                break;

                /* A start tag whose tag name is "input" */
                case 'input':
                    /* Reconstruct the active formatting elements, if any. */
                    $this->reconstructActiveFormattingElements();

                    /* Insert an input element for the token. */
                    $element = $this->insertElement($token, false);

                    /* If the form element pointer is not null, then associate the
                    input element with the form element pointed to by the form
                    element pointer. */
                    $this->form_pointer !== null
                        ? $this->form_pointer->appendChild($element)
                        : end($this->stack)->appendChild($element);

                    /* Pop that input element off the stack of open elements. */
                    array_pop($this->stack);
                break;

                /* A start tag whose tag name is "isindex" */
                case 'isindex':
                    /* Parse error. */
                    // w/e

                    /* If the form element pointer is not null,
                    then ignore the token. */
                    if($this->form_pointer === null) {
                        /* Act as if a start tag token with the tag name "form" had
                        been seen. */
                        $this->inBody(array(
                            'name' => 'body',
                            'type' => HTML5::STARTTAG,
                            'attr' => array()
                        ));

                        /* Act as if a start tag token with the tag name "hr" had
                        been seen. */
                        $this->inBody(array(
                            'name' => 'hr',
                            'type' => HTML5::STARTTAG,
                            'attr' => array()
                        ));

                        /* Act as if a start tag token with the tag name "p" had
                        been seen. */
                        $this->inBody(array(
                            'name' => 'p',
                            'type' => HTML5::STARTTAG,
                            'attr' => array()
                        ));

                        /* Act as if a start tag token with the tag name "label"
                        had been seen. */
                        $this->inBody(array(
                            'name' => 'label',
                            'type' => HTML5::STARTTAG,
                            'attr' => array()
                        ));

                        /* Act as if a stream of character tokens had been seen. */
                        $this->insertText('This is a searchable index. '.
                        'Insert your search keywords here: ');

                        /* Act as if a start tag token with the tag name "input"
                        had been seen, with all the attributes from the "isindex"
                        token, except with the "name" attribute set to the value
                        "isindex" (ignoring any explicit "name" attribute). */
                        $attr = $token['attr'];
                        $attr[] = array('name' => 'name', 'value' => 'isindex');

                        $this->inBody(array(
                            'name' => 'input',
                            'type' => HTML5::STARTTAG,
                            'attr' => $attr
                        ));

                        /* Act as if a stream of character tokens had been seen
                        (see below for what they should say). */
                        $this->insertText('This is a searchable index. '.
                        'Insert your search keywords here: ');

                        /* Act as if an end tag token with the tag name "label"
                        had been seen. */
                        $this->inBody(array(
                            'name' => 'label',
                            'type' => HTML5::ENDTAG
                        ));

                        /* Act as if an end tag token with the tag name "p" had
                        been seen. */
                        $this->inBody(array(
                            'name' => 'p',
                            'type' => HTML5::ENDTAG
                        ));

                        /* Act as if a start tag token with the tag name "hr" had
                        been seen. */
                        $this->inBody(array(
                            'name' => 'hr',
                            'type' => HTML5::ENDTAG
                        ));

                        /* Act as if an end tag token with the tag name "form" had
                        been seen. */
                        $this->inBody(array(
                            'name' => 'form',
                            'type' => HTML5::ENDTAG
                        ));
                    }
                break;

                /* A start tag whose tag name is "textarea" */
                case 'textarea':
                    $this->insertElement($token);

                    /* Switch the tokeniser's content model flag to the
                    RCDATA state. */
                    return HTML5::RCDATA;
                break;

                /* A start tag whose tag name is one of: "iframe", "noembed",
                "noframes" */
                case 'iframe': case 'noembed': case 'noframes':
                    $this->insertElement($token);

                    /* Switch the tokeniser's content model flag to the CDATA state. */
                    return HTML5::CDATA;
                break;

                /* A start tag whose tag name is "select" */
                case 'select':
                    /* Reconstruct the active formatting elements, if any. */
                    $this->reconstructActiveFormattingElements();

                    /* Insert an HTML element for the token. */
                    $this->insertElement($token);

                    /* Change the insertion mode to "in select". */
                    $this->mode = self::IN_SELECT;
                break;

                /* A start or end tag whose tag name is one of: "caption", "col",
                "colgroup", "frame", "frameset", "head", "option", "optgroup",
                "tbody", "td", "tfoot", "th", "thead", "tr". */
                case 'caption': case 'col': case 'colgroup': case 'frame':
                case 'frameset': case 'head': case 'option': case 'optgroup':
                case 'tbody': case 'td': case 'tfoot': case 'th': case 'thead':
                case 'tr':
                    // Parse error. Ignore the token.
                break;

                /* A start or end tag whose tag name is one of: "event-source",
                "section", "nav", "article", "aside", "header", "footer",
                "datagrid", "command" */
                case 'event-source': case 'section': case 'nav': case 'article':
                case 'aside': case 'header': case 'footer': case 'datagrid':
                case 'command':
                    // Work in progress!
                break;

                /* A start tag token not covered by the previous entries */
                default:
                    /* Reconstruct the active formatting elements, if any. */
                    $this->reconstructActiveFormattingElements();

                    $this->insertElement($token);
                break;
            }
            break;

            case HTML5::ENDTAG:
            switch($token['name']) {
                /* An end tag with the tag name "body" */
                case 'body':
                    /* If the second element in the stack of open elements is
                    not a body element, this is a parse error. Ignore the token.
                    (innerHTML case) */
                    if(count($this->stack) < 2 || $this->stack[1]->nodeName !== 'body') {
                        // Ignore.

                    /* If the current node is not the body element, then this
                    is a parse error. */
                    } elseif(end($this->stack)->nodeName !== 'body') {
                        // Parse error.
                    }

                    /* Change the insertion mode to "after body". */
                    $this->mode = self::AFTER_BODY;
                break;

                /* An end tag with the tag name "html" */
                case 'html':
                    /* Act as if an end tag with tag name "body" had been seen,
                    then, if that token wasn't ignored, reprocess the current
                    token. */
                    $this->inBody(array(
                        'name' => 'body',
                        'type' => HTML5::ENDTAG
                    ));

                    return $this->afterBody($token);
                break;

                /* An end tag whose tag name is one of: "address", "blockquote",
                "center", "dir", "div", "dl", "fieldset", "listing", "menu",
                "ol", "pre", "ul" */
                case 'address': case 'blockquote': case 'center': case 'dir':
                case 'div': case 'dl': case 'fieldset': case 'listing':
                case 'menu': case 'ol': case 'pre': case 'ul':
                    /* If the stack of open elements has an element in scope
                    with the same tag name as that of the token, then generate
                    implied end tags. */
                    if($this->elementInScope($token['name'])) {
                        $this->generateImpliedEndTags();

                        /* Now, if the current node is not an element with
                        the same tag name as that of the token, then this
                        is a parse error. */
                        // w/e

                        /* If the stack of open elements has an element in
                        scope with the same tag name as that of the token,
                        then pop elements from this stack until an element
                        with that tag name has been popped from the stack. */
                        for($n = count($this->stack) - 1; $n >= 0; $n--) {
                            if($this->stack[$n]->nodeName === $token['name']) {
                                $n = -1;
                            }

                            array_pop($this->stack);
                        }
                    }
                break;

                /* An end tag whose tag name is "form" */
                case 'form':
                    /* If the stack of open elements has an element in scope
                    with the same tag name as that of the token, then generate
                    implied    end tags. */
                    if($this->elementInScope($token['name'])) {
                        $this->generateImpliedEndTags();

                    }

                    if(end($this->stack)->nodeName !== $token['name']) {
                        /* Now, if the current node is not an element with the
                        same tag name as that of the token, then this is a parse
                        error. */
                        // w/e

                    } else {
                        /* Otherwise, if the current node is an element with
                        the same tag name as that of the token pop that element
                        from the stack. */
                        array_pop($this->stack);
                    }

                    /* In any case, set the form element pointer to null. */
                    $this->form_pointer = null;
                break;

                /* An end tag whose tag name is "p" */
                case 'p':
                    /* If the stack of open elements has a p element in scope,
                    then generate implied end tags, except for p elements. */
                    if($this->elementInScope('p')) {
                        $this->generateImpliedEndTags(array('p'));

                        /* If the current node is not a p element, then this is
                        a parse error. */
                        // k

                        /* If the stack of open elements has a p element in
                        scope, then pop elements from this stack until the stack
                        no longer has a p element in scope. */
                        for($n = count($this->stack) - 1; $n >= 0; $n--) {
                            if($this->elementInScope('p')) {
                                array_pop($this->stack);

                            } else {
                                break;
                            }
                        }
                    }
                break;

                /* An end tag whose tag name is "dd", "dt", or "li" */
                case 'dd': case 'dt': case 'li':
                    /* If the stack of open elements has an element in scope
                    whose tag name matches the tag name of the token, then
                    generate implied end tags, except for elements with the
                    same tag name as the token. */
                    if($this->elementInScope($token['name'])) {
                        $this->generateImpliedEndTags(array($token['name']));

                        /* If the current node is not an element with the same
                        tag name as the token, then this is a parse error. */
                        // w/e

                        /* If the stack of open elements has an element in scope
                        whose tag name matches the tag name of the token, then
                        pop elements from this stack until an element with that
                        tag name has been popped from the stack. */
                        for($n = count($this->stack) - 1; $n >= 0; $n--) {
                            if($this->stack[$n]->nodeName === $token['name']) {
                                $n = -1;
                            }

                            array_pop($this->stack);
                        }
                    }
                break;

                /* An end tag whose tag name is one of: "h1", "h2", "h3", "h4",
                "h5", "h6" */
                case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6':
                    $elements = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6');

                    /* If the stack of open elements has in scope an element whose
                    tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then
                    generate implied end tags. */
                    if($this->elementInScope($elements)) {
                        $this->generateImpliedEndTags();

                        /* Now, if the current node is not an element with the same
                        tag name as that of the token, then this is a parse error. */
                        // w/e

                        /* If the stack of open elements has in scope an element
                        whose tag name is one of "h1", "h2", "h3", "h4", "h5", or
                        "h6", then pop elements from the stack until an element
                        with one of those tag names has been popped from the stack. */
                        while($this->elementInScope($elements)) {
                            array_pop($this->stack);
                        }
                    }
                break;

                /* An end tag whose tag name is one of: "a", "b", "big", "em",
                "font", "i", "nobr", "s", "small", "strike", "strong", "tt", "u" */
                case 'a': case 'b': case 'big': case 'em': case 'font':
                case 'i': case 'nobr': case 's': case 'small': case 'strike':
                case 'strong': case 'tt': case 'u':
                    /* 1. Let the formatting element be the last element in
                    the list of active formatting elements that:
                        * is between the end of the list and the last scope
                        marker in the list, if any, or the start of the list
                        otherwise, and
                        * has the same tag name as the token.
                    */
                    while(true) {
                        for($a = count($this->a_formatting) - 1; $a >= 0; $a--) {
                            if($this->a_formatting[$a] === self::MARKER) {
                                break;

                            } elseif($this->a_formatting[$a]->tagName === $token['name']) {
                                $formatting_element = $this->a_formatting[$a];
                                $in_stack = in_array($formatting_element, $this->stack, true);
                                $fe_af_pos = $a;
                                break;
                            }
                        }

                        /* If there is no such node, or, if that node is
                        also in the stack of open elements but the element
                        is not in scope, then this is a parse error. Abort
                        these steps. The token is ignored. */
                        if(!isset($formatting_element) || ($in_stack &&
                        !$this->elementInScope($token['name']))) {
                            break;

                        /* Otherwise, if there is such a node, but that node
                        is not in the stack of open elements, then this is a
                        parse error; remove the element from the list, and
                        abort these steps. */
                        } elseif(isset($formatting_element) && !$in_stack) {
                            unset($this->a_formatting[$fe_af_pos]);
                            $this->a_formatting = array_merge($this->a_formatting);
                            break;
                        }

                        /* 2. Let the furthest block be the topmost node in the
                        stack of open elements that is lower in the stack
                        than the formatting element, and is not an element in
                        the phrasing or formatting categories. There might
                        not be one. */
                        $fe_s_pos = array_search($formatting_element, $this->stack, true);
                        $length = count($this->stack);

                        for($s = $fe_s_pos + 1; $s < $length; $s++) {
                            $category = $this->getElementCategory($this->stack[$s]->nodeName);

                            if($category !== self::PHRASING && $category !== self::FORMATTING) {
                                $furthest_block = $this->stack[$s];
                            }
                        }

                        /* 3. If there is no furthest block, then the UA must
                        skip the subsequent steps and instead just pop all
                        the nodes from the bottom of the stack of open
                        elements, from the current node up to the formatting
                        element, and remove the formatting element from the
                        list of active formatting elements. */
                        if(!isset($furthest_block)) {
                            for($n = $length - 1; $n >= $fe_s_pos; $n--) {
                                array_pop($this->stack);
                            }

                            unset($this->a_formatting[$fe_af_pos]);
                            $this->a_formatting = array_merge($this->a_formatting);
                            break;
                        }

                        /* 4. Let the common ancestor be the element
                        immediately above the formatting element in the stack
                        of open elements. */
                        $common_ancestor = $this->stack[$fe_s_pos - 1];

                        /* 5. If the furthest block has a parent node, then
                        remove the furthest block from its parent node. */
                        if($furthest_block->parentNode !== null) {
                            $furthest_block->parentNode->removeChild($furthest_block);
                        }

                        /* 6. Let a bookmark note the position of the
                        formatting element in the list of active formatting
                        elements relative to the elements on either side
                        of it in the list. */
                        $bookmark = $fe_af_pos;

                        /* 7. Let node and last node  be the furthest block.
                        Follow these steps: */
                        $node = $furthest_block;
                        $last_node = $furthest_block;

                        while(true) {
                            for($n = array_search($node, $this->stack, true) - 1; $n >= 0; $n--) {
                                /* 7.1 Let node be the element immediately
                                prior to node in the stack of open elements. */
                                $node = $this->stack[$n];

                                /* 7.2 If node is not in the list of active
                                formatting elements, then remove node from
                                the stack of open elements and then go back
                                to step 1. */
                                if(!in_array($node, $this->a_formatting, true)) {
                                    unset($this->stack[$n]);
                                    $this->stack = array_merge($this->stack);

                                } else {
                                    break;
                                }
                            }

                            /* 7.3 Otherwise, if node is the formatting
                            element, then go to the next step in the overall
                            algorithm. */
                            if($node === $formatting_element) {
                                break;

                            /* 7.4 Otherwise, if last node is the furthest
                            block, then move the aforementioned bookmark to
                            be immediately after the node in the list of
                            active formatting elements. */
                            } elseif($last_node === $furthest_block) {
                                $bookmark = array_search($node, $this->a_formatting, true) + 1;
                            }

                            /* 7.5 If node has any children, perform a
                            shallow clone of node, replace the entry for
                            node in the list of active formatting elements
                            with an entry for the clone, replace the entry
                            for node in the stack of open elements with an
                            entry for the clone, and let node be the clone. */
                            if($node->hasChildNodes()) {
                                $clone = $node->cloneNode();
                                $s_pos = array_search($node, $this->stack, true);
                                $a_pos = array_search($node, $this->a_formatting, true);

                                $this->stack[$s_pos] = $clone;
                                $this->a_formatting[$a_pos] = $clone;
                                $node = $clone;
                            }

                            /* 7.6 Insert last node into node, first removing
                            it from its previous parent node if any. */
                            if($last_node->parentNode !== null) {
                                $last_node->parentNode->removeChild($last_node);
                            }

                            $node->appendChild($last_node);

                            /* 7.7 Let last node be node. */
                            $last_node = $node;
                        }

                        /* 8. Insert whatever last node ended up being in
                        the previous step into the common ancestor node,
                        first removing it from its previous parent node if
                        any. */
                        if($last_node->parentNode !== null) {
                            $last_node->parentNode->removeChild($last_node);
                        }

                        $common_ancestor->appendChild($last_node);

                        /* 9. Perform a shallow clone of the formatting
                        element. */
                        $clone = $formatting_element->cloneNode();

                        /* 10. Take all of the child nodes of the furthest
                        block and append them to the clone created in the
                        last step. */
                        while($furthest_block->hasChildNodes()) {
                            $child = $furthest_block->firstChild;
                            $furthest_block->removeChild($child);
                            $clone->appendChild($child);
                        }

                        /* 11. Append that clone to the furthest block. */
                        $furthest_block->appendChild($clone);

                        /* 12. Remove the formatting element from the list
                        of active formatting elements, and insert the clone
                        into the list of active formatting elements at the
                        position of the aforementioned bookmark. */
                        $fe_af_pos = array_search($formatting_element, $this->a_formatting, true);
                        unset($this->a_formatting[$fe_af_pos]);
                        $this->a_formatting = array_merge($this->a_formatting);

                        $af_part1 = array_slice($this->a_formatting, 0, $bookmark - 1);
                        $af_part2 = array_slice($this->a_formatting, $bookmark, count($this->a_formatting));
                        $this->a_formatting = array_merge($af_part1, array($clone), $af_part2);

                        /* 13. Remove the formatting element from the stack
                        of open elements, and insert the clone into the stack
                        of open elements immediately after (i.e. in a more
                        deeply nested position than) the position of the
                        furthest block in that stack. */
                        $fe_s_pos = array_search($formatting_element, $this->stack, true);
                        $fb_s_pos = array_search($furthest_block, $this->stack, true);
                        unset($this->stack[$fe_s_pos]);

                        $s_part1 = array_slice($this->stack, 0, $fb_s_pos);
                        $s_part2 = array_slice($this->stack, $fb_s_pos + 1, count($this->stack));
                        $this->stack = array_merge($s_part1, array($clone), $s_part2);

                        /* 14. Jump back to step 1 in this series of steps. */
                        unset($formatting_element, $fe_af_pos, $fe_s_pos, $furthest_block);
                    }
                break;

                /* An end tag token whose tag name is one of: "button",
                "marquee", "object" */
                case 'button': case 'marquee': case 'object':
                    /* If the stack of open elements has an element in scope whose
                    tag name matches the tag name of the token, then generate implied
                    tags. */
                    if($this->elementInScope($token['name'])) {
                        $this->generateImpliedEndTags();

                        /* Now, if the current node is not an element with the same
                        tag name as the token, then this is a parse error. */
                        // k

                        /* Now, if the stack of open elements has an element in scope
                        whose tag name matches the tag name of the token, then pop
                        elements from the stack until that element has been popped from
                        the stack, and clear the list of active formatting elements up
                        to the last marker. */
                        for($n = count($this->stack) - 1; $n >= 0; $n--) {
                            if($this->stack[$n]->nodeName === $token['name']) {
                                $n = -1;
                            }

                            array_pop($this->stack);
                        }

                        $marker = end(array_keys($this->a_formatting, self::MARKER, true));

                        for($n = count($this->a_formatting) - 1; $n > $marker; $n--) {
                            array_pop($this->a_formatting);
                        }
                    }
                break;

                /* Or an end tag whose tag name is one of: "area", "basefont",
                "bgsound", "br", "embed", "hr", "iframe", "image", "img",
                "input", "isindex", "noembed", "noframes", "param", "select",
                "spacer", "table", "textarea", "wbr" */
                case 'area': case 'basefont': case 'bgsound': case 'br':
                case 'embed': case 'hr': case 'iframe': case 'image':
                case 'img': case 'input': case 'isindex': case 'noembed':
                case 'noframes': case 'param': case 'select': case 'spacer':
                case 'table': case 'textarea': case 'wbr':
                    // Parse error. Ignore the token.
                break;

                /* An end tag token not covered by the previous entries */
                default:
                    for($n = count($this->stack) - 1; $n >= 0; $n--) {
                        /* Initialise node to be the current node (the bottommost
                        node of the stack). */
                        $node = end($this->stack);

                        /* If node has the same tag name as the end tag token,
                        then: */
                        if($token['name'] === $node->nodeName) {
                            /* Generate implied end tags. */
                            $this->generateImpliedEndTags();

                            /* If the tag name of the end tag token does not
                            match the tag name of the current node, this is a
                            parse error. */
                            // k

                            /* Pop all the nodes from the current node up to
                            node, including node, then stop this algorithm. */
                            for($x = count($this->stack) - $n; $x >= $n; $x--) {
                                array_pop($this->stack);
                            }

                        } else {
                            $category = $this->getElementCategory($node);

                            if($category !== self::SPECIAL && $category !== self::SCOPING) {
                                /* Otherwise, if node is in neither the formatting
                                category nor the phrasing category, then this is a
                                parse error. Stop this algorithm. The end tag token
                                is ignored. */
                                return false;
                            }
                        }
                    }
                break;
            }
            break;
        }
    }

    private function inTable($token)
    {
        $clear = array('html', 'table');

        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
        or U+0020 SPACE */
        if($token['type'] === HTML5::CHARACTR &&
        preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) {
            /* Append the character to the current node. */
            $text = $this->dom->createTextNode($token['data']);
            end($this->stack)->appendChild($text);

        /* A comment token */
        } elseif($token['type'] === HTML5::COMMENT) {
            /* Append a Comment node to the current node with the data
            attribute set to the data given in the comment token. */
            $comment = $this->dom->createComment($token['data']);
            end($this->stack)->appendChild($comment);

        /* A start tag whose tag name is "caption" */
        } elseif($token['type'] === HTML5::STARTTAG &&
        $token['name'] === 'caption') {
            /* Clear the stack back to a table context. */
            $this->clearStackToTableContext($clear);

            /* Insert a marker at the end of the list of active
            formatting elements. */
            $this->a_formatting[] = self::MARKER;

            /* Insert an HTML element for the token, then switch the
            insertion mode to "in caption". */
            $this->insertElement($token);
            $this->mode = self::IN_CAPTION;

        /* A start tag whose tag name is "colgroup" */
        } elseif($token['type'] === HTML5::STARTTAG &&
        $token['name'] === 'colgroup') {
            /* Clear the stack back to a table context. */
            $this->clearStackToTableContext($clear);

            /* Insert an HTML element for the token, then switch the
            insertion mode to "in column group". */
            $this->insertElement($token);
            $this->mode = self::IN_CGROUP;

        /* A start tag whose tag name is "col" */
        } elseif($token['type'] === HTML5::STARTTAG &&
        $token['name'] === 'col') {
            $this->inTable(array(
                'name' => 'colgroup',
                'type' => HTML5::STARTTAG,
                'attr' => array()
            ));

            $this->inColumnGroup($token);

        /* A start tag whose tag name is one of: "tbody", "tfoot", "thead" */
        } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'],
        array('tbody', 'tfoot', 'thead'))) {
            /* Clear the stack back to a table context. */
            $this->clearStackToTableContext($clear);

            /* Insert an HTML element for the token, then switch the insertion
            mode to "in table body". */
            $this->insertElement($token);
            $this->mode = self::IN_TBODY;

        /* A start tag whose tag name is one of: "td", "th", "tr" */
        } elseif($token['type'] === HTML5::STARTTAG &&
        in_array($token['name'], array('td', 'th', 'tr'))) {
            /* Act as if a start tag token with the tag name "tbody" had been
            seen, then reprocess the current token. */
            $this->inTable(array(
                'name' => 'tbody',
                'type' => HTML5::STARTTAG,
                'attr' => array()
            ));

            return $this->inTableBody($token);

        /* A start tag whose tag name is "table" */
        } elseif($token['type'] === HTML5::STARTTAG &&
        $token['name'] === 'table') {
            /* Parse error. Act as if an end tag token with the tag name "table"
            had been seen, then, if that token wasn't ignored, reprocess the
            current token. */
            $this->inTable(array(
                'name' => 'table',
                'type' => HTML5::ENDTAG
            ));

            return $this->mainPhase($token);

        /* An end tag whose tag name is "table" */
        } elseif($token['type'] === HTML5::ENDTAG &&
        $token['name'] === 'table') {
            /* If the stack of open elements does not have an element in table
            scope with the same tag name as the token, this is a parse error.
            Ignore the token. (innerHTML case) */
            if(!$this->elementInScope($token['name'], true)) {
                return false;

            /* Otherwise: */
            } else {
                /* Generate implied end tags. */
                $this->generateImpliedEndTags();

                /* Now, if the current node is not a table element, then this
                is a parse error. */
                // w/e

                /* Pop elements from this stack until a table element has been
                popped from the stack. */
                while(true) {
                    $current = end($this->stack)->nodeName;
                    array_pop($this->stack);

                    if($current === 'table') {
                        break;
                    }
                }

                /* Reset the insertion mode appropriately. */
                $this->resetInsertionMode();
            }

        /* An end tag whose tag name is one of: "body", "caption", "col",
        "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr" */
        } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'],
        array('body', 'caption', 'col', 'colgroup', 'html', 'tbody', 'td',
        'tfoot', 'th', 'thead', 'tr'))) {
            // Parse error. Ignore the token.

        /* Anything else */
        } else {
            /* Parse error. Process the token as if the insertion mode was "in
            body", with the following exception: */

            /* If the current node is a table, tbody, tfoot, thead, or tr
            element, then, whenever a node would be inserted into the current
            node, it must instead be inserted into the foster parent element. */
            if(in_array(end($this->stack)->nodeName,
            array('table', 'tbody', 'tfoot', 'thead', 'tr'))) {
                /* The foster parent element is the parent element of the last
                table element in the stack of open elements, if there is a
                table element and it has such a parent element. If there is no
                table element in the stack of open elements (innerHTML case),
                then the foster parent element is the first element in the
                stack of open elements (the html  element). Otherwise, if there
                is a table element in the stack of open elements, but the last
                table element in the stack of open elements has no parent, or
                its parent node is not an element, then the foster parent
                element is the element before the last table element in the
                stack of open elements. */
                for($n = count($this->stack) - 1; $n >= 0; $n--) {
                    if($this->stack[$n]->nodeName === 'table') {
                        $table = $this->stack[$n];
                        break;
                    }
                }

                if(isset($table) && $table->parentNode !== null) {
                    $this->foster_parent = $table->parentNode;

                } elseif(!isset($table)) {
                    $this->foster_parent = $this->stack[0];

                } elseif(isset($table) && ($table->parentNode === null ||
                $table->parentNode->nodeType !== XML_ELEMENT_NODE)) {
                    $this->foster_parent = $this->stack[$n - 1];
                }
            }

            $this->inBody($token);
        }
    }

    private function inCaption($token)
    {
        /* An end tag whose tag name is "caption" */
        if($token['type'] === HTML5::ENDTAG && $token['name'] === 'caption') {
            /* If the stack of open elements does not have an element in table
            scope with the same tag name as the token, this is a parse error.
            Ignore the token. (innerHTML case) */
            if(!$this->elementInScope($token['name'], true)) {
                // Ignore

            /* Otherwise: */
            } else {
                /* Generate implied end tags. */
                $this->generateImpliedEndTags();

                /* Now, if the current node is not a caption element, then this
                is a parse error. */
                // w/e

                /* Pop elements from this stack until a caption element has
                been popped from the stack. */
                while(true) {
                    $node = end($this->stack)->nodeName;
                    array_pop($this->stack);

                    if($node === 'caption') {
                        break;
                    }
                }

                /* Clear the list of active formatting elements up to the last
                marker. */
                $this->clearTheActiveFormattingElementsUpToTheLastMarker();

                /* Switch the insertion mode to "in table". */
                $this->mode = self::IN_TABLE;
            }

        /* A start tag whose tag name is one of: "caption", "col", "colgroup",
        "tbody", "td", "tfoot", "th", "thead", "tr", or an end tag whose tag
        name is "table" */
        } elseif(($token['type'] === HTML5::STARTTAG && in_array($token['name'],
        array('caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
        'thead', 'tr'))) || ($token['type'] === HTML5::ENDTAG &&
        $token['name'] === 'table')) {
            /* Parse error. Act as if an end tag with the tag name "caption"
            had been seen, then, if that token wasn't ignored, reprocess the
            current token. */
            $this->inCaption(array(
                'name' => 'caption',
                'type' => HTML5::ENDTAG
            ));

            return $this->inTable($token);

        /* An end tag whose tag name is one of: "body", "col", "colgroup",
        "html", "tbody", "td", "tfoot", "th", "thead", "tr" */
        } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'],
        array('body', 'col', 'colgroup', 'html', 'tbody', 'tfoot', 'th',
        'thead', 'tr'))) {
            // Parse error. Ignore the token.

        /* Anything else */
        } else {
            /* Process the token as if the insertion mode was "in body". */
            $this->inBody($token);
        }
    }

    private function inColumnGroup($token)
    {
        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
        or U+0020 SPACE */
        if($token['type'] === HTML5::CHARACTR &&
        preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) {
            /* Append the character to the current node. */
            $text = $this->dom->createTextNode($token['data']);
            end($this->stack)->appendChild($text);

        /* A comment token */
        } elseif($token['type'] === HTML5::COMMENT) {
            /* Append a Comment node to the current node with the data
            attribute set to the data given in the comment token. */
            $comment = $this->dom->createComment($token['data']);
            end($this->stack)->appendChild($comment);

        /* A start tag whose tag name is "col" */
        } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'col') {
            /* Insert a col element for the token. Immediately pop the current
            node off the stack of open elements. */
            $this->insertElement($token);
            array_pop($this->stack);

        /* An end tag whose tag name is "colgroup" */
        } elseif($token['type'] === HTML5::ENDTAG &&
        $token['name'] === 'colgroup') {
            /* If the current node is the root html element, then this is a
            parse error, ignore the token. (innerHTML case) */
            if(end($this->stack)->nodeName === 'html') {
                // Ignore

            /* Otherwise, pop the current node (which will be a colgroup
            element) from the stack of open elements. Switch the insertion
            mode to "in table". */
            } else {
                array_pop($this->stack);
                $this->mode = self::IN_TABLE;
            }

        /* An end tag whose tag name is "col" */
        } elseif($token['type'] === HTML5::ENDTAG && $token['name'] === 'col') {
            /* Parse error. Ignore the token. */

        /* Anything else */
        } else {
            /* Act as if an end tag with the tag name "colgroup" had been seen,
            and then, if that token wasn't ignored, reprocess the current token. */
            $this->inColumnGroup(array(
                'name' => 'colgroup',
                'type' => HTML5::ENDTAG
            ));

            return $this->inTable($token);
        }
    }

    private function inTableBody($token)
    {
        $clear = array('tbody', 'tfoot', 'thead', 'html');

        /* A start tag whose tag name is "tr" */
        if($token['type'] === HTML5::STARTTAG && $token['name'] === 'tr') {
            /* Clear the stack back to a table body context. */
            $this->clearStackToTableContext($clear);

            /* Insert a tr element for the token, then switch the insertion
            mode to "in row". */
            $this->insertElement($token);
            $this->mode = self::IN_ROW;

        /* A start tag whose tag name is one of: "th", "td" */
        } elseif($token['type'] === HTML5::STARTTAG &&
        ($token['name'] === 'th' ||    $token['name'] === 'td')) {
            /* Parse error. Act as if a start tag with the tag name "tr" had
            been seen, then reprocess the current token. */
            $this->inTableBody(array(
                'name' => 'tr',
                'type' => HTML5::STARTTAG,
                'attr' => array()
            ));

            return $this->inRow($token);

        /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */
        } elseif($token['type'] === HTML5::ENDTAG &&
        in_array($token['name'], array('tbody', 'tfoot', 'thead'))) {
            /* If the stack of open elements does not have an element in table
            scope with the same tag name as the token, this is a parse error.
            Ignore the token. */
            if(!$this->elementInScope($token['name'], true)) {
                // Ignore

            /* Otherwise: */
            } else {
                /* Clear the stack back to a table body context. */
                $this->clearStackToTableContext($clear);

                /* Pop the current node from the stack of open elements. Switch
                the insertion mode to "in table". */
                array_pop($this->stack);
                $this->mode = self::IN_TABLE;
            }

        /* A start tag whose tag name is one of: "caption", "col", "colgroup",
        "tbody", "tfoot", "thead", or an end tag whose tag name is "table" */
        } elseif(($token['type'] === HTML5::STARTTAG && in_array($token['name'],
        array('caption', 'col', 'colgroup', 'tbody', 'tfoor', 'thead'))) ||
        ($token['type'] === HTML5::STARTTAG && $token['name'] === 'table')) {
            /* If the stack of open elements does not have a tbody, thead, or
            tfoot element in table scope, this is a parse error. Ignore the
            token. (innerHTML case) */
            if(!$this->elementInScope(array('tbody', 'thead', 'tfoot'), true)) {
                // Ignore.

            /* Otherwise: */
            } else {
                /* Clear the stack back to a table body context. */
                $this->clearStackToTableContext($clear);

                /* Act as if an end tag with the same tag name as the current
                node ("tbody", "tfoot", or "thead") had been seen, then
                reprocess the current token. */
                $this->inTableBody(array(
                    'name' => end($this->stack)->nodeName,
                    'type' => HTML5::ENDTAG
                ));

                return $this->mainPhase($token);
            }

        /* An end tag whose tag name is one of: "body", "caption", "col",
        "colgroup", "html", "td", "th", "tr" */
        } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'],
        array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th', 'tr'))) {
            /* Parse error. Ignore the token. */

        /* Anything else */
        } else {
            /* Process the token as if the insertion mode was "in table". */
            $this->inTable($token);
        }
    }

    private function inRow($token)
    {
        $clear = array('tr', 'html');

        /* A start tag whose tag name is one of: "th", "td" */
        if($token['type'] === HTML5::STARTTAG &&
        ($token['name'] === 'th' || $token['name'] === 'td')) {
            /* Clear the stack back to a table row context. */
            $this->clearStackToTableContext($clear);

            /* Insert an HTML element for the token, then switch the insertion
            mode to "in cell". */
            $this->insertElement($token);
            $this->mode = self::IN_CELL;

            /* Insert a marker at the end of the list of active formatting
            elements. */
            $this->a_formatting[] = self::MARKER;

        /* An end tag whose tag name is "tr" */
        } elseif($token['type'] === HTML5::ENDTAG && $token['name'] === 'tr') {
            /* If the stack of open elements does not have an element in table
            scope with the same tag name as the token, this is a parse error.
            Ignore the token. (innerHTML case) */
            if(!$this->elementInScope($token['name'], true)) {
                // Ignore.

            /* Otherwise: */
            } else {
                /* Clear the stack back to a table row context. */
                $this->clearStackToTableContext($clear);

                /* Pop the current node (which will be a tr element) from the
                stack of open elements. Switch the insertion mode to "in table
                body". */
                array_pop($this->stack);
                $this->mode = self::IN_TBODY;
            }

        /* A start tag whose tag name is one of: "caption", "col", "colgroup",
        "tbody", "tfoot", "thead", "tr" or an end tag whose tag name is "table" */
        } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'],
        array('caption', 'col', 'colgroup', 'tbody', 'tfoot', 'thead', 'tr'))) {
            /* Act as if an end tag with the tag name "tr" had been seen, then,
            if that token wasn't ignored, reprocess the current token. */
            $this->inRow(array(
                'name' => 'tr',
                'type' => HTML5::ENDTAG
            ));

            return $this->inCell($token);

        /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */
        } elseif($token['type'] === HTML5::ENDTAG &&
        in_array($token['name'], array('tbody', 'tfoot', 'thead'))) {
            /* If the stack of open elements does not have an element in table
            scope with the same tag name as the token, this is a parse error.
            Ignore the token. */
            if(!$this->elementInScope($token['name'], true)) {
                // Ignore.

            /* Otherwise: */
            } else {
                /* Otherwise, act as if an end tag with the tag name "tr" had
                been seen, then reprocess the current token. */
                $this->inRow(array(
                    'name' => 'tr',
                    'type' => HTML5::ENDTAG
                ));

                return $this->inCell($token);
            }

        /* An end tag whose tag name is one of: "body", "caption", "col",
        "colgroup", "html", "td", "th" */
        } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'],
        array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th', 'tr'))) {
            /* Parse error. Ignore the token. */

        /* Anything else */
        } else {
            /* Process the token as if the insertion mode was "in table". */
            $this->inTable($token);
        }
    }

    private function inCell($token)
    {
        /* An end tag whose tag name is one of: "td", "th" */
        if($token['type'] === HTML5::ENDTAG &&
        ($token['name'] === 'td' || $token['name'] === 'th')) {
            /* If the stack of open elements does not have an element in table
            scope with the same tag name as that of the token, then this is a
            parse error and the token must be ignored. */
            if(!$this->elementInScope($token['name'], true)) {
                // Ignore.

            /* Otherwise: */
            } else {
                /* Generate implied end tags, except for elements with the same
                tag name as the token. */
                $this->generateImpliedEndTags(array($token['name']));

                /* Now, if the current node is not an element with the same tag
                name as the token, then this is a parse error. */
                // k

                /* Pop elements from this stack until an element with the same
                tag name as the token has been popped from the stack. */
                while(true) {
                    $node = end($this->stack)->nodeName;
                    array_pop($this->stack);

                    if($node === $token['name']) {
                        break;
                    }
                }

                /* Clear the list of active formatting elements up to the last
                marker. */
                $this->clearTheActiveFormattingElementsUpToTheLastMarker();

                /* Switch the insertion mode to "in row". (The current node
                will be a tr element at this point.) */
                $this->mode = self::IN_ROW;
            }

        /* A start tag whose tag name is one of: "caption", "col", "colgroup",
        "tbody", "td", "tfoot", "th", "thead", "tr" */
        } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'],
        array('caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
        'thead', 'tr'))) {
            /* If the stack of open elements does not have a td or th element
            in table scope, then this is a parse error; ignore the token.
            (innerHTML case) */
            if(!$this->elementInScope(array('td', 'th'), true)) {
                // Ignore.

            /* Otherwise, close the cell (see below) and reprocess the current
            token. */
            } else {
                $this->closeCell();
                return $this->inRow($token);
            }

        /* A start tag whose tag name is one of: "caption", "col", "colgroup",
        "tbody", "td", "tfoot", "th", "thead", "tr" */
        } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'],
        array('caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
        'thead', 'tr'))) {
            /* If the stack of open elements does not have a td or th element
            in table scope, then this is a parse error; ignore the token.
            (innerHTML case) */
            if(!$this->elementInScope(array('td', 'th'), true)) {
                // Ignore.

            /* Otherwise, close the cell (see below) and reprocess the current
            token. */
            } else {
                $this->closeCell();
                return $this->inRow($token);
            }

        /* An end tag whose tag name is one of: "body", "caption", "col",
        "colgroup", "html" */
        } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'],
        array('body', 'caption', 'col', 'colgroup', 'html'))) {
            /* Parse error. Ignore the token. */

        /* An end tag whose tag name is one of: "table", "tbody", "tfoot",
        "thead", "tr" */
        } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'],
        array('table', 'tbody', 'tfoot', 'thead', 'tr'))) {
            /* If the stack of open elements does not have an element in table
            scope with the same tag name as that of the token (which can only
            happen for "tbody", "tfoot" and "thead", or, in the innerHTML case),
            then this is a parse error and the token must be ignored. */
            if(!$this->elementInScope($token['name'], true)) {
                // Ignore.

            /* Otherwise, close the cell (see below) and reprocess the current
            token. */
            } else {
                $this->closeCell();
                return $this->inRow($token);
            }

        /* Anything else */
        } else {
            /* Process the token as if the insertion mode was "in body". */
            $this->inBody($token);
        }
    }

    private function inSelect($token)
    {
        /* Handle the token as follows: */

        /* A character token */
        if($token['type'] === HTML5::CHARACTR) {
            /* Append the token's character to the current node. */
            $this->insertText($token['data']);

        /* A comment token */
        } elseif($token['type'] === HTML5::COMMENT) {
            /* Append a Comment node to the current node with the data
            attribute set to the data given in the comment token. */
            $this->insertComment($token['data']);

        /* A start tag token whose tag name is "option" */
        } elseif($token['type'] === HTML5::STARTTAG &&
        $token['name'] === 'option') {
            /* If the current node is an option element, act as if an end tag
            with the tag name "option" had been seen. */
            if(end($this->stack)->nodeName === 'option') {
                $this->inSelect(array(
                    'name' => 'option',
                    'type' => HTML5::ENDTAG
                ));
            }

            /* Insert an HTML element for the token. */
            $this->insertElement($token);

        /* A start tag token whose tag name is "optgroup" */
        } elseif($token['type'] === HTML5::STARTTAG &&
        $token['name'] === 'optgroup') {
            /* If the current node is an option element, act as if an end tag
            with the tag name "option" had been seen. */
            if(end($this->stack)->nodeName === 'option') {
                $this->inSelect(array(
                    'name' => 'option',
                    'type' => HTML5::ENDTAG
                ));
            }

            /* If the current node is an optgroup element, act as if an end tag
            with the tag name "optgroup" had been seen. */
            if(end($this->stack)->nodeName === 'optgroup') {
                $this->inSelect(array(
                    'name' => 'optgroup',
                    'type' => HTML5::ENDTAG
                ));
            }

            /* Insert an HTML element for the token. */
            $this->insertElement($token);

        /* An end tag token whose tag name is "optgroup" */
        } elseif($token['type'] === HTML5::ENDTAG &&
        $token['name'] === 'optgroup') {
            /* First, if the current node is an option element, and the node
            immediately before it in the stack of open elements is an optgroup
            element, then act as if an end tag with the tag name "option" had
            been seen. */
            $elements_in_stack = count($this->stack);

            if($this->stack[$elements_in_stack - 1]->nodeName === 'option' &&
            $this->stack[$elements_in_stack - 2]->nodeName === 'optgroup') {
                $this->inSelect(array(
                    'name' => 'option',
                    'type' => HTML5::ENDTAG
                ));
            }

            /* If the current node is an optgroup element, then pop that node
            from the stack of open elements. Otherwise, this is a parse error,
            ignore the token. */
            if($this->stack[$elements_in_stack - 1] === 'optgroup') {
                array_pop($this->stack);
            }

        /* An end tag token whose tag name is "option" */
        } elseif($token['type'] === HTML5::ENDTAG &&
        $token['name'] === 'option') {
            /* If the current node is an option element, then pop that node
            from the stack of open elements. Otherwise, this is a parse error,
            ignore the token. */
            if(end($this->stack)->nodeName === 'option') {
                array_pop($this->stack);
            }

        /* An end tag whose tag name is "select" */
        } elseif($token['type'] === HTML5::ENDTAG &&
        $token['name'] === 'select') {
            /* If the stack of open elements does not have an element in table
            scope with the same tag name as the token, this is a parse error.
            Ignore the token. (innerHTML case) */
            if(!$this->elementInScope($token['name'], true)) {
                // w/e

            /* Otherwise: */
            } else {
                /* Pop elements from the stack of open elements until a select
                element has been popped from the stack. */
                while(true) {
                    $current = end($this->stack)->nodeName;
                    array_pop($this->stack);

                    if($current === 'select') {
                        break;
                    }
                }

                /* Reset the insertion mode appropriately. */
                $this->resetInsertionMode();
            }

        /* A start tag whose tag name is "select" */
        } elseif($token['name'] === 'select' &&
        $token['type'] === HTML5::STARTTAG) {
            /* Parse error. Act as if the token had been an end tag with the
            tag name "select" instead. */
            $this->inSelect(array(
                'name' => 'select',
                'type' => HTML5::ENDTAG
            ));

        /* An end tag whose tag name is one of: "caption", "table", "tbody",
        "tfoot", "thead", "tr", "td", "th" */
        } elseif(in_array($token['name'], array('caption', 'table', 'tbody',
        'tfoot', 'thead', 'tr', 'td', 'th')) && $token['type'] === HTML5::ENDTAG) {
            /* Parse error. */
            // w/e

            /* If the stack of open elements has an element in table scope with
            the same tag name as that of the token, then act as if an end tag
            with the tag name "select" had been seen, and reprocess the token.
            Otherwise, ignore the token. */
            if($this->elementInScope($token['name'], true)) {
                $this->inSelect(array(
                    'name' => 'select',
                    'type' => HTML5::ENDTAG
                ));

                $this->mainPhase($token);
            }

        /* Anything else */
        } else {
            /* Parse error. Ignore the token. */
        }
    }

    private function afterBody($token)
    {
        /* Handle the token as follows: */

        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
        or U+0020 SPACE */
        if($token['type'] === HTML5::CHARACTR &&
        preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) {
            /* Process the token as it would be processed if the insertion mode
            was "in body". */
            $this->inBody($token);

        /* A comment token */
        } elseif($token['type'] === HTML5::COMMENT) {
            /* Append a Comment node to the first element in the stack of open
            elements (the html element), with the data attribute set to the
            data given in the comment token. */
            $comment = $this->dom->createComment($token['data']);
            $this->stack[0]->appendChild($comment);

        /* An end tag with the tag name "html" */
        } elseif($token['type'] === HTML5::ENDTAG && $token['name'] === 'html') {
            /* If the parser was originally created in order to handle the
            setting of an element's innerHTML attribute, this is a parse error;
            ignore the token. (The element will be an html element in this
            case.) (innerHTML case) */

            /* Otherwise, switch to the trailing end phase. */
            $this->phase = self::END_PHASE;

        /* Anything else */
        } else {
            /* Parse error. Set the insertion mode to "in body" and reprocess
            the token. */
            $this->mode = self::IN_BODY;
            return $this->inBody($token);
        }
    }

    private function inFrameset($token)
    {
        /* Handle the token as follows: */

        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
        U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */
        if($token['type'] === HTML5::CHARACTR &&
        preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) {
            /* Append the character to the current node. */
            $this->insertText($token['data']);

        /* A comment token */
        } elseif($token['type'] === HTML5::COMMENT) {
            /* Append a Comment node to the current node with the data
            attribute set to the data given in the comment token. */
            $this->insertComment($token['data']);

        /* A start tag with the tag name "frameset" */
        } elseif($token['name'] === 'frameset' &&
        $token['type'] === HTML5::STARTTAG) {
            $this->insertElement($token);

        /* An end tag with the tag name "frameset" */
        } elseif($token['name'] === 'frameset' &&
        $token['type'] === HTML5::ENDTAG) {
            /* If the current node is the root html element, then this is a
            parse error; ignore the token. (innerHTML case) */
            if(end($this->stack)->nodeName === 'html') {
                // Ignore

            } else {
                /* Otherwise, pop the current node from the stack of open
                elements. */
                array_pop($this->stack);

                /* If the parser was not originally created in order to handle
                the setting of an element's innerHTML attribute (innerHTML case),
                and the current node is no longer a frameset element, then change
                the insertion mode to "after frameset". */
                $this->mode = self::AFTR_FRAME;
            }

        /* A start tag with the tag name "frame" */
        } elseif($token['name'] === 'frame' &&
        $token['type'] === HTML5::STARTTAG) {
            /* Insert an HTML element for the token. */
            $this->insertElement($token);

            /* Immediately pop the current node off the stack of open elements. */
            array_pop($this->stack);

        /* A start tag with the tag name "noframes" */
        } elseif($token['name'] === 'noframes' &&
        $token['type'] === HTML5::STARTTAG) {
            /* Process the token as if the insertion mode had been "in body". */
            $this->inBody($token);

        /* Anything else */
        } else {
            /* Parse error. Ignore the token. */
        }
    }

    private function afterFrameset($token)
    {
        /* Handle the token as follows: */

        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
        U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */
        if($token['type'] === HTML5::CHARACTR &&
        preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) {
            /* Append the character to the current node. */
            $this->insertText($token['data']);

        /* A comment token */
        } elseif($token['type'] === HTML5::COMMENT) {
            /* Append a Comment node to the current node with the data
            attribute set to the data given in the comment token. */
            $this->insertComment($token['data']);

        /* An end tag with the tag name "html" */
        } elseif($token['name'] === 'html' &&
        $token['type'] === HTML5::ENDTAG) {
            /* Switch to the trailing end phase. */
            $this->phase = self::END_PHASE;

        /* A start tag with the tag name "noframes" */
        } elseif($token['name'] === 'noframes' &&
        $token['type'] === HTML5::STARTTAG) {
            /* Process the token as if the insertion mode had been "in body". */
            $this->inBody($token);

        /* Anything else */
        } else {
            /* Parse error. Ignore the token. */
        }
    }

    private function trailingEndPhase($token)
    {
        /* After the main phase, as each token is emitted from the tokenisation
        stage, it must be processed as described in this section. */

        /* A DOCTYPE token */
        if($token['type'] === HTML5::DOCTYPE) {
            // Parse error. Ignore the token.

        /* A comment token */
        } elseif($token['type'] === HTML5::COMMENT) {
            /* Append a Comment node to the Document object with the data
            attribute set to the data given in the comment token. */
            $comment = $this->dom->createComment($token['data']);
            $this->dom->appendChild($comment);

        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
        or U+0020 SPACE */
        } elseif($token['type'] === HTML5::CHARACTR &&
        preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) {
            /* Process the token as it would be processed in the main phase. */
            $this->mainPhase($token);

        /* A character token that is not one of U+0009 CHARACTER TABULATION,
        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
        or U+0020 SPACE. Or a start tag token. Or an end tag token. */
        } elseif(($token['type'] === HTML5::CHARACTR &&
        preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) ||
        $token['type'] === HTML5::STARTTAG || $token['type'] === HTML5::ENDTAG) {
            /* Parse error. Switch back to the main phase and reprocess the
            token. */
            $this->phase = self::MAIN_PHASE;
            return $this->mainPhase($token);

        /* An end-of-file token */
        } elseif($token['type'] === HTML5::EOF) {
            /* OMG DONE!! */
        }
    }

    private function insertElement($token, $append = true)
    {
        $el = $this->dom->createElement($token['name']);

        foreach($token['attr'] as $attr) {
            if(!$el->hasAttribute($attr['name'])) {
                $el->setAttribute($attr['name'], $attr['value']);
            }
        }

        $this->appendToRealParent($el);
        $this->stack[] = $el;

        return $el;
    }

    private function insertText($data)
    {
        $text = $this->dom->createTextNode($data);
        $this->appendToRealParent($text);
    }

    private function insertComment($data)
    {
        $comment = $this->dom->createComment($data);
        $this->appendToRealParent($comment);
    }

    private function appendToRealParent($node)
    {
        if($this->foster_parent === null) {
            end($this->stack)->appendChild($node);

        } elseif($this->foster_parent !== null) {
            /* If the foster parent element is the parent element of the
            last table element in the stack of open elements, then the new
            node must be inserted immediately before the last table element
            in the stack of open elements in the foster parent element;
            otherwise, the new node must be appended to the foster parent
            element. */
            for($n = count($this->stack) - 1; $n >= 0; $n--) {
                if($this->stack[$n]->nodeName === 'table' &&
                $this->stack[$n]->parentNode !== null) {
                    $table = $this->stack[$n];
                    break;
                }
            }

            if(isset($table) && $this->foster_parent->isSameNode($table->parentNode))
                $this->foster_parent->insertBefore($node, $table);
            else
                $this->foster_parent->appendChild($node);

            $this->foster_parent = null;
        }
    }

    private function elementInScope($el, $table = false)
    {
        if(is_array($el)) {
            foreach($el as $element) {
                if($this->elementInScope($element, $table)) {
                    return true;
                }
            }

            return false;
        }

        $leng = count($this->stack);

        for($n = 0; $n < $leng; $n++) {
            /* 1. Initialise node to be the current node (the bottommost node of
            the stack). */
            $node = $this->stack[$leng - 1 - $n];

            if($node->tagName === $el) {
                /* 2. If node is the target node, terminate in a match state. */
                return true;

            } elseif($node->tagName === 'table') {
                /* 3. Otherwise, if node is a table element, terminate in a failure
                state. */
                return false;

            } elseif($table === true && in_array($node->tagName, array('caption', 'td',
            'th', 'button', 'marquee', 'object'))) {
                /* 4. Otherwise, if the algorithm is the "has an element in scope"
                variant (rather than the "has an element in table scope" variant),
                and node is one of the following, terminate in a failure state. */
                return false;

            } elseif($node === $node->ownerDocument->documentElement) {
                /* 5. Otherwise, if node is an html element (root element), terminate
                in a failure state. (This can only happen if the node is the topmost
                node of the    stack of open elements, and prevents the next step from
                being invoked if there are no more elements in the stack.) */
                return false;
            }

            /* Otherwise, set node to the previous entry in the stack of open
            elements and return to step 2. (This will never fail, since the loop
            will always terminate in the previous step if the top of the stack
            is reached.) */
        }
    }

    private function reconstructActiveFormattingElements()
    {
        /* 1. If there are no entries in the list of active formatting elements,
        then there is nothing to reconstruct; stop this algorithm. */
        $formatting_elements = count($this->a_formatting);

        if($formatting_elements === 0) {
            return false;
        }

        /* 3. Let entry be the last (most recently added) element in the list
        of active formatting elements. */
        $entry = end($this->a_formatting);

        /* 2. If the last (most recently added) entry in the list of active
        formatting elements is a marker, or if it is an element that is in the
        stack of open elements, then there is nothing to reconstruct; stop this
        algorithm. */
        if($entry === self::MARKER || in_array($entry, $this->stack, true)) {
            return false;
        }

        for($a = $formatting_elements - 1; $a >= 0; true) {
            /* 4. If there are no entries before entry in the list of active
            formatting elements, then jump to step 8. */
            if($a === 0) {
                $step_seven = false;
                break;
            }

            /* 5. Let entry be the entry one earlier than entry in the list of
            active formatting elements. */
            $a--;
            $entry = $this->a_formatting[$a];

            /* 6. If entry is neither a marker nor an element that is also in
            thetack of open elements, go to step 4. */
            if($entry === self::MARKER || in_array($entry, $this->stack, true)) {
                break;
            }
        }

        while(true) {
            /* 7. Let entry be the element one later than entry in the list of
            active formatting elements. */
            if(isset($step_seven) && $step_seven === true) {
                $a++;
                $entry = $this->a_formatting[$a];
            }

            /* 8. Perform a shallow clone of the element entry to obtain clone. */
            $clone = $entry->cloneNode();

            /* 9. Append clone to the current node and push it onto the stack
            of open elements  so that it is the new current node. */
            end($this->stack)->appendChild($clone);
            $this->stack[] = $clone;

            /* 10. Replace the entry for entry in the list with an entry for
            clone. */
            $this->a_formatting[$a] = $clone;

            /* 11. If the entry for clone in the list of active formatting
            elements is not the last entry in the list, return to step 7. */
            if(end($this->a_formatting) !== $clone) {
                $step_seven = true;
            } else {
                break;
            }
        }
    }

    private function clearTheActiveFormattingElementsUpToTheLastMarker()
    {
        /* When the steps below require the UA to clear the list of active
        formatting elements up to the last marker, the UA must perform the
        following steps: */

        while(true) {
            /* 1. Let entry be the last (most recently added) entry in the list
            of active formatting elements. */
            $entry = end($this->a_formatting);

            /* 2. Remove entry from the list of active formatting elements. */
            array_pop($this->a_formatting);

            /* 3. If entry was a marker, then stop the algorithm at this point.
            The list has been cleared up to the last marker. */
            if($entry === self::MARKER) {
                break;
            }
        }
    }

    private function generateImpliedEndTags(array $exclude = array())
    {
        /* When the steps below require the UA to generate implied end tags,
        then, if the current node is a dd element, a dt element, an li element,
        a p element, a td element, a th  element, or a tr element, the UA must
        act as if an end tag with the respective tag name had been seen and
        then generate implied end tags again. */
        $node = end($this->stack);
        $elements = array_diff(array('dd', 'dt', 'li', 'p', 'td', 'th', 'tr'), $exclude);

        while(in_array(end($this->stack)->nodeName, $elements)) {
            array_pop($this->stack);
        }
    }

    private function getElementCategory($name)
    {
        if(in_array($name, $this->special))
            return self::SPECIAL;

        elseif(in_array($name, $this->scoping))
            return self::SCOPING;

        elseif(in_array($name, $this->formatting))
            return self::FORMATTING;

        else
            return self::PHRASING;
    }

    private function clearStackToTableContext($elements)
    {
        /* When the steps above require the UA to clear the stack back to a
        table context, it means that the UA must, while the current node is not
        a table element or an html element, pop elements from the stack of open
        elements. If this causes any elements to be popped from the stack, then
        this is a parse error. */
        while(true) {
            $node = end($this->stack)->nodeName;

            if(in_array($node, $elements)) {
                break;
            } else {
                array_pop($this->stack);
            }
        }
    }

    private function resetInsertionMode()
    {
        /* 1. Let last be false. */
        $last = false;
        $leng = count($this->stack);

        for($n = $leng - 1; $n >= 0; $n--) {
            /* 2. Let node be the last node in the stack of open elements. */
            $node = $this->stack[$n];

            /* 3. If node is the first node in the stack of open elements, then
            set last to true. If the element whose innerHTML  attribute is being
            set is neither a td  element nor a th element, then set node to the
            element whose innerHTML  attribute is being set. (innerHTML  case) */
            if($this->stack[0]->isSameNode($node)) {
                $last = true;
            }

            /* 4. If node is a select element, then switch the insertion mode to
            "in select" and abort these steps. (innerHTML case) */
            if($node->nodeName === 'select') {
                $this->mode = self::IN_SELECT;
                break;

            /* 5. If node is a td or th element, then switch the insertion mode
            to "in cell" and abort these steps. */
            } elseif($node->nodeName === 'td' || $node->nodeName === 'th') {
                $this->mode = self::IN_CELL;
                break;

            /* 6. If node is a tr element, then switch the insertion mode to
            "in    row" and abort these steps. */
            } elseif($node->nodeName === 'tr') {
                $this->mode = self::IN_ROW;
                break;

            /* 7. If node is a tbody, thead, or tfoot element, then switch the
            insertion mode to "in table body" and abort these steps. */
            } elseif(in_array($node->nodeName, array('tbody', 'thead', 'tfoot'))) {
                $this->mode = self::IN_TBODY;
                break;

            /* 8. If node is a caption element, then switch the insertion mode
            to "in caption" and abort these steps. */
            } elseif($node->nodeName === 'caption') {
                $this->mode = self::IN_CAPTION;
                break;

            /* 9. If node is a colgroup element, then switch the insertion mode
            to "in column group" and abort these steps. (innerHTML case) */
            } elseif($node->nodeName === 'colgroup') {
                $this->mode = self::IN_CGROUP;
                break;

            /* 10. If node is a table element, then switch the insertion mode
            to "in table" and abort these steps. */
            } elseif($node->nodeName === 'table') {
                $this->mode = self::IN_TABLE;
                break;

            /* 11. If node is a head element, then switch the insertion mode
            to "in body" ("in body"! not "in head"!) and abort these steps.
            (innerHTML case) */
            } elseif($node->nodeName === 'head') {
                $this->mode = self::IN_BODY;
                break;

            /* 12. If node is a body element, then switch the insertion mode to
            "in body" and abort these steps. */
            } elseif($node->nodeName === 'body') {
                $this->mode = self::IN_BODY;
                break;

            /* 13. If node is a frameset element, then switch the insertion
            mode to "in frameset" and abort these steps. (innerHTML case) */
            } elseif($node->nodeName === 'frameset') {
                $this->mode = self::IN_FRAME;
                break;

            /* 14. If node is an html element, then: if the head element
            pointer is null, switch the insertion mode to "before head",
            otherwise, switch the insertion mode to "after head". In either
            case, abort these steps. (innerHTML case) */
            } elseif($node->nodeName === 'html') {
                $this->mode = ($this->head_pointer === null)
                    ? self::BEFOR_HEAD
                    : self::AFTER_HEAD;

                break;

            /* 15. If last is true, then set the insertion mode to "in body"
            and    abort these steps. (innerHTML case) */
            } elseif($last) {
                $this->mode = self::IN_BODY;
                break;
            }
        }
    }

    private function closeCell()
    {
        /* If the stack of open elements has a td or th element in table scope,
        then act as if an end tag token with that tag name had been seen. */
        foreach(array('td', 'th') as $cell) {
            if($this->elementInScope($cell, true)) {
                $this->inCell(array(
                    'name' => $cell,
                    'type' => HTML5::ENDTAG
                ));

                break;
            }
        }
    }

    public function save()
    {
        return $this->dom;
    }
}
#!/usr/bin/php
<?php

chdir(dirname(__FILE__));
require_once 'common.php';
assertCli();

echo "Please do not run this script. It is here for historical purposes only.";
exit;

/**
 * @file
 * Removes leading includes from files.
 *
 * @note
 *      This does not remove inline includes; those must be handled manually.
 */

chdir(dirname(__FILE__) . '/../tests/HTMLPurifier');
$FS = new FSTools();

$files = $FS->globr('.', '*.php');
foreach ($files as $file) {
    if (substr_count(basename($file), '.') > 1) continue;
    $old_code = file_get_contents($file);
    $new_code = preg_replace("#^require_once .+[\n\r]*#m", '', $old_code);
    if ($old_code !== $new_code) {
        file_put_contents($file, $new_code);
    }
}

// vim: et sw=4 sts=4
{
    "name": "ezyang/htmlpurifier",
    "description": "Standards compliant HTML filter written in PHP",
    "type": "library",
    "keywords": ["html"],
    "homepage": "http://htmlpurifier.org/",
    "license": "LGPL",
    "authors": [
        {
            "name": "Edward Z. Yang",
            "email": "admin@htmlpurifier.org",
            "homepage": "http://ezyang.com"
        }
    ],
    "require": {
        "php": ">=5.2"
    },
    "require-dev": {
        "simpletest/simpletest": "^1.1"
    },
    "autoload": {
        "psr-0": { "HTMLPurifier": "library/" },
        "files": ["library/HTMLPurifier.composer.php"]
    }
}
<?php

// release script
// PHP 5.0 only

if (php_sapi_name() != 'cli') {
    echo 'Release script cannot be called from web-browser.';
    exit;
}

if (!isset($argv[1])) {
    echo
'php release.php [version]
    HTML Purifier release script
';
    exit;
}

$version = trim($argv[1]);

// Bump version numbers:

// ...in VERSION
file_put_contents('VERSION', $version);

// ...in NEWS
if ($is_dev = (strpos($version, 'dev') === false)) {
  $date = date('Y-m-d');
  $news_c = str_replace(
      $l = "$version, unknown release date",
      "$version, released $date",
      file_get_contents('NEWS'),
      $c
  );
  if (!$c) {
      echo 'Could not update NEWS, missing ' . $l . PHP_EOL;
      exit;
  } elseif ($c > 1) {
      echo 'More than one release declaration in NEWS replaced' . PHP_EOL;
      exit;
  }
  file_put_contents('NEWS', $news_c);
}

// ...in Doxyfile
$doxyfile_c = preg_replace(
    '/(?<=PROJECT_NUMBER {9}= )[^\s]+/m', // brittle
    $version,
    file_get_contents('Doxyfile'),
    1, $c
);
if (!$c) {
    echo 'Could not update Doxyfile, missing PROJECT_NUMBER.' . PHP_EOL;
    exit;
}
file_put_contents('Doxyfile', $doxyfile_c);

// ...in HTMLPurifier.php
$htmlpurifier_c = file_get_contents('library/HTMLPurifier.php');
$htmlpurifier_c = preg_replace(
    '/HTML Purifier .+? - /',
    "HTML Purifier $version - ",
    $htmlpurifier_c,
    1, $c
);
if (!$c) {
    echo 'Could not update HTMLPurifier.php, missing HTML Purifier [version] header.' . PHP_EOL;
    exit;
}
$htmlpurifier_c = preg_replace(
    '/public \$version = \'.+?\';/',
    "public \$version = '$version';",
    $htmlpurifier_c,
    1, $c
);
if (!$c) {
    echo 'Could not update HTMLPurifier.php, missing public $version.' . PHP_EOL;
    exit;
}
$htmlpurifier_c = preg_replace(
    '/const VERSION = \'.+?\';/',
    "const VERSION = '$version';",
    $htmlpurifier_c,
    1, $c
);
if (!$c) {
    echo 'Could not update HTMLPurifier.php, missing const $version.' . PHP_EOL;
    exit;
}
file_put_contents('library/HTMLPurifier.php', $htmlpurifier_c);

$config_c = file_get_contents('library/HTMLPurifier/Config.php');
$config_c = preg_replace(
    '/public \$version = \'.+?\';/',
    "public \$version = '$version';",
    $config_c,
    1, $c
);
if (!$c) {
    echo 'Could not update Config.php, missing public $version.' . PHP_EOL;
    exit;
}
file_put_contents('library/HTMLPurifier/Config.php', $config_c);

passthru('php maintenance/flush.php');

if ($is_dev) echo "Review changes, write something in WHATSNEW and FOCUS, and then commit with log 'Release $version.'" . PHP_EOL;
else echo "Numbers updated to dev, no other modifications necessary!";

// vim: et sw=4 sts=4
<?php

/**
 * Meta-class for HTML Purifier's extra class hierarchies, similar to
 * HTMLPurifier_Bootstrap.
 */
class HTMLPurifierExtras
{

    public static function autoload($class)
    {
        $path = HTMLPurifierExtras::getPath($class);
        if (!$path) return false;
        require $path;
        return true;
    }

    public static function getPath($class)
    {
        if (
            strncmp('FSTools', $class, 7) !== 0 &&
            strncmp('ConfigDoc', $class, 9) !== 0
        ) return false;
        // Custom implementations can go here
        // Standard implementation:
        return str_replace('_', '/', $class) . '.php';
    }

}

// vim: et sw=4 sts=4
<?php

/**
 * Filesystem tools not provided by default; can recursively create, copy
 * and delete folders. Some template methods are provided for extensibility.
 *
 * @note This class must be instantiated to be used, although it does
 *       not maintain state.
 */
class FSTools
{

    private static $singleton;

    /**
     * Returns a global instance of FSTools
     */
    public static function singleton()
    {
        if (empty(FSTools::$singleton)) FSTools::$singleton = new FSTools();
        return FSTools::$singleton;
    }

    /**
     * Sets our global singleton to something else; useful for overloading
     * functions.
     */
    public static function setSingleton($singleton)
    {
        FSTools::$singleton = $singleton;
    }

    /**
     * Recursively creates a directory
     * @param string $folder Name of folder to create
     * @note Adapted from the PHP manual comment 76612
     */
    public function mkdirr($folder)
    {
        $folders = preg_split("#[\\\\/]#", $folder);
        $base = '';
        for($i = 0, $c = count($folders); $i < $c; $i++) {
            if(empty($folders[$i])) {
                if (!$i) {
                    // special case for root level
                    $base .= DIRECTORY_SEPARATOR;
                }
                continue;
            }
            $base .= $folders[$i];
            if(!is_dir($base)){
                $this->mkdir($base);
            }
            $base .= DIRECTORY_SEPARATOR;
        }
    }

    /**
     * Copy a file, or recursively copy a folder and its contents; modified
     * so that copied files, if PHP, have includes removed
     * @note Adapted from http://aidanlister.com/repos/v/function.copyr.php
     */
    public function copyr($source, $dest)
    {
        // Simple copy for a file
        if (is_file($source)) {
            return $this->copy($source, $dest);
        }
        // Make destination directory
        if (!is_dir($dest)) {
            $this->mkdir($dest);
        }
        // Loop through the folder
        $dir = $this->dir($source);
        while ( false !== ($entry = $dir->read()) ) {
            // Skip pointers
            if ($entry == '.' || $entry == '..') {
                continue;
            }
            if (!$this->copyable($entry)) {
                continue;
            }
            // Deep copy directories
            if ($dest !== "$source/$entry") {
                $this->copyr("$source/$entry", "$dest/$entry");
            }
        }
        // Clean up
        $dir->close();
        return true;
    }

    /**
     * Overloadable function that tests a filename for copyability. By
     * default, everything should be copied; you can restrict things to
     * ignore hidden files, unreadable files, etc. This function
     * applies to copyr().
     */
    public function copyable($file)
    {
        return true;
    }

    /**
     * Delete a file, or a folder and its contents
     * @note Adapted from http://aidanlister.com/repos/v/function.rmdirr.php
     */
    public function rmdirr($dirname)
    {
        // Sanity check
        if (!$this->file_exists($dirname)) {
            return false;
        }

        // Simple delete for a file
        if ($this->is_file($dirname) || $this->is_link($dirname)) {
            return $this->unlink($dirname);
        }

        // Loop through the folder
        $dir = $this->dir($dirname);
        while (false !== $entry = $dir->read()) {
            // Skip pointers
            if ($entry == '.' || $entry == '..') {
                continue;
            }
            // Recurse
            $this->rmdirr($dirname . DIRECTORY_SEPARATOR . $entry);
        }

        // Clean up
        $dir->close();
        return $this->rmdir($dirname);
    }

    /**
     * Recursively globs a directory.
     */
    public function globr($dir, $pattern, $flags = NULL)
    {
        $files = $this->glob("$dir/$pattern", $flags);
        if ($files === false) $files = array();
        $sub_dirs = $this->glob("$dir/*", GLOB_ONLYDIR);
        if ($sub_dirs === false) $sub_dirs = array();
        foreach ($sub_dirs as $sub_dir) {
            $sub_files = $this->globr($sub_dir, $pattern, $flags);
            $files = array_merge($files, $sub_files);
        }
        return $files;
    }

    /**
     * Allows for PHP functions to be called and be stubbed.
     * @warning This function will not work for functions that need
     *      to pass references; manually define a stub function for those.
     */
    public function __call($name, $args)
    {
        return call_user_func_array($name, $args);
    }

}

// vim: et sw=4 sts=4
<?php

/**
 * @file
 * Convenience file that registers autoload handler for HTML Purifier.
 *
 * @warning
 *      This autoloader does not contain the compatibility code seen in
 *      HTMLPurifier_Bootstrap; the user is expected to make any necessary
 *      changes to use this library.
 */

if (function_exists('spl_autoload_register')) {
    spl_autoload_register(array('HTMLPurifierExtras', 'autoload'));
    if (function_exists('__autoload')) {
        // Be polite and ensure that userland autoload gets retained
        spl_autoload_register('__autoload');
    }
} elseif (!function_exists('__autoload')) {
    require dirname(__FILE__) . '/HTMLPurifierExtras.autoload-legacy.php';
}

// vim: et sw=4 sts=4
<?php

/**
 * @file
 * Legacy autoloader for systems lacking spl_autoload_register
 *
 * Must be separate to prevent deprecation warning on PHP 7.2
 */

function __autoload($class)
{
    return HTMLPurifierExtras::autoload($class);
}

// vim: et sw=4 sts=4
<?php

/**
 * Represents a file in the filesystem
 *
 * @warning Be sure to distinguish between get() and write() versus
 *      read() and put(), the former operates on the entire file, while
 *      the latter operates on a handle.
 */
class FSTools_File
{

    /** Filename of file this object represents */
    protected $name;

    /** Handle for the file */
    protected $handle = false;

    /** Instance of FSTools for interfacing with filesystem */
    protected $fs;

    /**
     * Filename of file you wish to instantiate.
     * @note This file need not exist
     */
    public function __construct($name, $fs = false)
    {
        $this->name = $name;
        $this->fs = $fs ? $fs : FSTools::singleton();
    }

    /** Returns the filename of the file. */
    public function getName() {return $this->name;}

    /** Returns directory of the file without trailing slash */
    public function getDirectory() {return $this->fs->dirname($this->name);}

    /**
     * Retrieves the contents of a file
     * @todo Throw an exception if file doesn't exist
     */
    public function get()
    {
        return $this->fs->file_get_contents($this->name);
    }

    /** Writes contents to a file, creates new file if necessary */
    public function write($contents)
    {
        return $this->fs->file_put_contents($this->name, $contents);
    }

    /** Deletes the file */
    public function delete()
    {
        return $this->fs->unlink($this->name);
    }

    /** Returns true if file exists and is a file. */
    public function exists()
    {
        return $this->fs->is_file($this->name);
    }

    /** Returns last file modification time */
    public function getMTime()
    {
        return $this->fs->filemtime($this->name);
    }

    /**
     * Chmod a file
     * @note We ignore errors because of some weird owner trickery due
     *       to SVN duality
     */
    public function chmod($octal_code)
    {
        return @$this->fs->chmod($this->name, $octal_code);
    }

    /** Opens file's handle */
    public function open($mode)
    {
        if ($this->handle) $this->close();
        $this->handle = $this->fs->fopen($this->name, $mode);
        return true;
    }

    /** Closes file's handle */
    public function close()
    {
        if (!$this->handle) return false;
        $status = $this->fs->fclose($this->handle);
        $this->handle = false;
        return $status;
    }

    /** Retrieves a line from an open file, with optional max length $length */
    public function getLine($length = null)
    {
        if (!$this->handle) $this->open('r');
        if ($length === null) return $this->fs->fgets($this->handle);
        else return $this->fs->fgets($this->handle, $length);
    }

    /** Retrieves a character from an open file */
    public function getChar()
    {
        if (!$this->handle) $this->open('r');
        return $this->fs->fgetc($this->handle);
    }

    /** Retrieves an $length bytes of data from an open data */
    public function read($length)
    {
        if (!$this->handle) $this->open('r');
        return $this->fs->fread($this->handle, $length);
    }

    /** Writes to an open file */
    public function put($string)
    {
        if (!$this->handle) $this->open('a');
        return $this->fs->fwrite($this->handle, $string);
    }

    /** Returns TRUE if the end of the file has been reached */
    public function eof()
    {
        if (!$this->handle) return true;
        return $this->fs->feof($this->handle);
    }

    public function __destruct()
    {
        if ($this->handle) $this->close();
    }

}

// vim: et sw=4 sts=4
<?php

/**
 * This is a stub include that automatically configures the include path.
 */

set_include_path(dirname(__FILE__) . PATH_SEPARATOR . get_include_path() );
require_once 'HTMLPurifierExtras.php';
require_once 'HTMLPurifierExtras.autoload.php';

// vim: et sw=4 sts=4
<?php

/**
 * Decorator/extender XSLT processor specifically for HTML documents.
 */
class ConfigDoc_HTMLXSLTProcessor
{

    /**
     * Instance of XSLTProcessor
     */
    protected $xsltProcessor;

    public function __construct($proc = false)
    {
        if ($proc === false) $proc = new XSLTProcessor();
        $this->xsltProcessor = $proc;
    }

    /**
     * @note Allows a string $xsl filename to be passed
     */
    public function importStylesheet($xsl)
    {
        if (is_string($xsl)) {
            $xsl_file = $xsl;
            $xsl = new DOMDocument();
            $xsl->load($xsl_file);
        }
        return $this->xsltProcessor->importStylesheet($xsl);
    }

    /**
     * Transforms an XML file into compatible XHTML based on the stylesheet
     * @param $xml XML DOM tree, or string filename
     * @return string HTML output
     * @todo Rename to transformToXHTML, as transformToHTML is misleading
     */
    public function transformToHTML($xml)
    {
        if (is_string($xml)) {
            $dom = new DOMDocument();
            $dom->load($xml);
        } else {
            $dom = $xml;
        }
        $out = $this->xsltProcessor->transformToXML($dom);

        // fudges for HTML backwards compatibility
        // assumes that document is XHTML
        $out = str_replace('/>', ' />', $out); // <br /> not <br/>
        $out = str_replace(' xmlns=""', '', $out); // rm unnecessary xmlns

        if (class_exists('Tidy')) {
            // cleanup output
            $config = array(
                'indent'        => true,
                'output-xhtml'  => true,
                'wrap'          => 80
            );
            $tidy = new Tidy;
            $tidy->parseString($out, $config, 'utf8');
            $tidy->cleanRepair();
            $out = (string) $tidy;
        }

        return $out;
    }

    /**
     * Bulk sets parameters for the XSL stylesheet
     * @param array $options Associative array of options to set
     */
    public function setParameters($options)
    {
        foreach ($options as $name => $value) {
            $this->xsltProcessor->setParameter('', $name, $value);
        }
    }

    /**
     * Forward any other calls to the XSLT processor
     */
    public function __call($name, $arguments)
    {
        call_user_func_array(array($this->xsltProcessor, $name), $arguments);
    }

}

// vim: et sw=4 sts=4
{
    "name": "guzzlehttp/streams",
    "description": "Provides a simple abstraction over streams of data",
    "homepage": "http://guzzlephp.org/",
    "keywords": ["stream", "guzzle"],
    "license": "MIT",
    "authors": [
        {
            "name": "Michael Dowling",
            "email": "mtdowling@gmail.com",
            "homepage": "https://github.com/mtdowling"
        }
    ],
    "require": {
        "php": ">=5.4.0"
    },
    "require-dev": {
        "phpunit/phpunit": "~4.0"
    },
    "autoload": {
        "psr-4": { "GuzzleHttp\\Stream\\": "src/" }
    },
    "extra": {
        "branch-alias": {
            "dev-master": "3.0-dev"
        }
    }
}
<?php
namespace GuzzleHttp\Stream;

/**
 * This interface is deprecated and should no longer be used. Just use
 * StreamInterface now that the getMetadata method has been added to
 * StreamInterface.
 *
 * @deprecated
 */
interface MetadataStreamInterface extends StreamInterface {}
<?php
namespace GuzzleHttp\Stream;

/**
 * Compose stream implementations based on a hash of functions.
 *
 * Allows for easy testing and extension of a provided stream without needing
 * to create a concrete class for a simple extension point.
 */
class FnStream implements StreamInterface
{
    /** @var array */
    private $methods;

    /** @var array Methods that must be implemented in the given array */
    private static $slots = ['__toString', 'close', 'detach', 'attach',
        'getSize', 'tell', 'eof', 'isSeekable', 'seek', 'isWritable', 'write',
        'isReadable', 'read', 'getContents', 'getMetadata'];

    /**
     * @param array $methods Hash of method name to a callable.
     */
    public function __construct(array $methods)
    {
        $this->methods = $methods;

        // Create the functions on the class
        foreach ($methods as $name => $fn) {
            $this->{'_fn_' . $name} = $fn;
        }
    }

    /**
     * Lazily determine which methods are not implemented.
     * @throws \BadMethodCallException
     */
    public function __get($name)
    {
        throw new \BadMethodCallException(str_replace('_fn_', '', $name)
            . '() is not implemented in the FnStream');
    }

    /**
     * The close method is called on the underlying stream only if possible.
     */
    public function __destruct()
    {
        if (isset($this->_fn_close)) {
            call_user_func($this->_fn_close);
        }
    }

    /**
     * Adds custom functionality to an underlying stream by intercepting
     * specific method calls.
     *
     * @param StreamInterface $stream  Stream to decorate
     * @param array           $methods Hash of method name to a closure
     *
     * @return FnStream
     */
    public static function decorate(StreamInterface $stream, array $methods)
    {
        // If any of the required methods were not provided, then simply
        // proxy to the decorated stream.
        foreach (array_diff(self::$slots, array_keys($methods)) as $diff) {
            $methods[$diff] = [$stream, $diff];
        }

        return new self($methods);
    }

    public function __toString()
    {
        return call_user_func($this->_fn___toString);
    }

    public function close()
    {
        return call_user_func($this->_fn_close);
    }

    public function detach()
    {
        return call_user_func($this->_fn_detach);
    }

    public function attach($stream)
    {
        return call_user_func($this->_fn_attach, $stream);
    }

    public function getSize()
    {
        return call_user_func($this->_fn_getSize);
    }

    public function tell()
    {
        return call_user_func($this->_fn_tell);
    }

    public function eof()
    {
        return call_user_func($this->_fn_eof);
    }

    public function isSeekable()
    {
        return call_user_func($this->_fn_isSeekable);
    }

    public function seek($offset, $whence = SEEK_SET)
    {
        return call_user_func($this->_fn_seek, $offset, $whence);
    }

    public function isWritable()
    {
        return call_user_func($this->_fn_isWritable);
    }

    public function write($string)
    {
        return call_user_func($this->_fn_write, $string);
    }

    public function isReadable()
    {
        return call_user_func($this->_fn_isReadable);
    }

    public function read($length)
    {
        return call_user_func($this->_fn_read, $length);
    }

    public function getContents()
    {
        return call_user_func($this->_fn_getContents);
    }

    public function getMetadata($key = null)
    {
        return call_user_func($this->_fn_getMetadata, $key);
    }
}
<?php
namespace GuzzleHttp\Stream;
use GuzzleHttp\Stream\Exception\CannotAttachException;

/**
 * Does not store any data written to it.
 */
class NullStream implements StreamInterface
{
    public function __toString()
    {
        return '';
    }

    public function getContents()
    {
        return '';
    }

    public function close() {}

    public function detach() {}

    public function attach($stream)
    {
        throw new CannotAttachException();
    }

    public function getSize()
    {
        return 0;
    }

    public function isReadable()
    {
        return true;
    }

    public function isWritable()
    {
        return true;
    }

    public function isSeekable()
    {
        return true;
    }

    public function eof()
    {
        return true;
    }

    public function tell()
    {
        return 0;
    }

    public function seek($offset, $whence = SEEK_SET)
    {
        return false;
    }

    public function read($length)
    {
        return false;
    }

    public function write($string)
    {
        return strlen($string);
    }

    public function getMetadata($key = null)
    {
        return $key ? null : [];
    }
}
<?php
namespace GuzzleHttp\Stream;

/**
 * Describes a stream instance.
 */
interface StreamInterface
{
    /**
     * Attempts to seek to the beginning of the stream and reads all data into
     * a string until the end of the stream is reached.
     *
     * Warning: This could attempt to load a large amount of data into memory.
     *
     * @return string
     */
    public function __toString();

    /**
     * Closes the stream and any underlying resources.
     */
    public function close();

    /**
     * Separates any underlying resources from the stream.
     *
     * After the underlying resource has been detached, the stream object is in
     * an unusable state. If you wish to use a Stream object as a PHP stream
     * but keep the Stream object in a consistent state, use
     * {@see GuzzleHttp\Stream\GuzzleStreamWrapper::getResource}.
     *
     * @return resource|null Returns the underlying PHP stream resource or null
     *                       if the Stream object did not utilize an underlying
     *                       stream resource.
     */
    public function detach();

    /**
     * Replaces the underlying stream resource with the provided stream.
     *
     * Use this method to replace the underlying stream with another; as an
     * example, in server-side code, if you decide to return a file, you
     * would replace the original content-oriented stream with the file
     * stream.
     *
     * Any internal state such as caching of cursor position should be reset
     * when attach() is called, as the stream has changed.
     *
     * @param resource $stream
     *
     * @return void
     */
    public function attach($stream);

    /**
     * Get the size of the stream if known
     *
     * @return int|null Returns the size in bytes if known, or null if unknown
     */
    public function getSize();

    /**
     * Returns the current position of the file read/write pointer
     *
     * @return int|bool Returns the position of the file pointer or false on error
     */
    public function tell();

    /**
     * Returns true if the stream is at the end of the stream.
     *
     * @return bool
     */
    public function eof();

    /**
     * Returns whether or not the stream is seekable
     *
     * @return bool
     */
    public function isSeekable();

    /**
     * Seek to a position in the stream
     *
     * @param int $offset Stream offset
     * @param int $whence Specifies how the cursor position will be calculated
     *                    based on the seek offset. Valid values are identical
     *                    to the built-in PHP $whence values for `fseek()`.
     *                    SEEK_SET: Set position equal to offset bytes
     *                    SEEK_CUR: Set position to current location plus offset
     *                    SEEK_END: Set position to end-of-stream plus offset
     *
     * @return bool Returns true on success or false on failure
     * @link   http://www.php.net/manual/en/function.fseek.php
     */
    public function seek($offset, $whence = SEEK_SET);

    /**
     * Returns whether or not the stream is writable
     *
     * @return bool
     */
    public function isWritable();

    /**
     * Write data to the stream
     *
     * @param string $string The string that is to be written.
     *
     * @return int|bool Returns the number of bytes written to the stream on
     *                  success returns false on failure (e.g., broken pipe,
     *                  writer needs to slow down, buffer is full, etc.)
     */
    public function write($string);

    /**
     * Returns whether or not the stream is readable
     *
     * @return bool
     */
    public function isReadable();

    /**
     * Read data from the stream
     *
     * @param int $length Read up to $length bytes from the object and return
     *                    them. Fewer than $length bytes may be returned if
     *                    underlying stream call returns fewer bytes.
     *
     * @return string     Returns the data read from the stream.
     */
    public function read($length);

    /**
     * Returns the remaining contents of the stream as a string.
     *
     * Note: this could potentially load a large amount of data into memory.
     *
     * @return string
     */
    public function getContents();

    /**
     * Get stream metadata as an associative array or retrieve a specific key.
     *
     * The keys returned are identical to the keys returned from PHP's
     * stream_get_meta_data() function.
     *
     * @param string $key Specific metadata to retrieve.
     *
     * @return array|mixed|null Returns an associative array if no key is
     *                          no key is provided. Returns a specific key
     *                          value if a key is provided and the value is
     *                          found, or null if the key is not found.
     * @see http://php.net/manual/en/function.stream-get-meta-data.php
     */
    public function getMetadata($key = null);
}
<?php
namespace GuzzleHttp\Stream;

use GuzzleHttp\Stream\Exception\SeekException;

/**
 * Stream decorator that can cache previously read bytes from a sequentially
 * read stream.
 */
class CachingStream implements StreamInterface
{
    use StreamDecoratorTrait;

    /** @var StreamInterface Stream being wrapped */
    private $remoteStream;

    /** @var int Number of bytes to skip reading due to a write on the buffer */
    private $skipReadBytes = 0;

    /**
     * We will treat the buffer object as the body of the stream
     *
     * @param StreamInterface $stream Stream to cache
     * @param StreamInterface $target Optionally specify where data is cached
     */
    public function __construct(
        StreamInterface $stream,
        StreamInterface $target = null
    ) {
        $this->remoteStream = $stream;
        $this->stream = $target ?: new Stream(fopen('php://temp', 'r+'));
    }

    public function getSize()
    {
        return max($this->stream->getSize(), $this->remoteStream->getSize());
    }

    /**
     * {@inheritdoc}
     * @throws SeekException When seeking with SEEK_END or when seeking
     *     past the total size of the buffer stream
     */
    public function seek($offset, $whence = SEEK_SET)
    {
        if ($whence == SEEK_SET) {
            $byte = $offset;
        } elseif ($whence == SEEK_CUR) {
            $byte = $offset + $this->tell();
        } else {
            return false;
        }

        // You cannot skip ahead past where you've read from the remote stream
        if ($byte > $this->stream->getSize()) {
            throw new SeekException(
                $this,
                $byte,
                sprintf('Cannot seek to byte %d when the buffered stream only'
                    . ' contains %d bytes', $byte, $this->stream->getSize())
            );
        }

        return $this->stream->seek($byte);
    }

    public function read($length)
    {
        // Perform a regular read on any previously read data from the buffer
        $data = $this->stream->read($length);
        $remaining = $length - strlen($data);

        // More data was requested so read from the remote stream
        if ($remaining) {
            // If data was written to the buffer in a position that would have
            // been filled from the remote stream, then we must skip bytes on
            // the remote stream to emulate overwriting bytes from that
            // position. This mimics the behavior of other PHP stream wrappers.
            $remoteData = $this->remoteStream->read(
                $remaining + $this->skipReadBytes
            );

            if ($this->skipReadBytes) {
                $len = strlen($remoteData);
                $remoteData = substr($remoteData, $this->skipReadBytes);
                $this->skipReadBytes = max(0, $this->skipReadBytes - $len);
            }

            $data .= $remoteData;
            $this->stream->write($remoteData);
        }

        return $data;
    }

    public function write($string)
    {
        // When appending to the end of the currently read stream, you'll want
        // to skip bytes from being read from the remote stream to emulate
        // other stream wrappers. Basically replacing bytes of data of a fixed
        // length.
        $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell();
        if ($overflow > 0) {
            $this->skipReadBytes += $overflow;
        }

        return $this->stream->write($string);
    }

    public function eof()
    {
        return $this->stream->eof() && $this->remoteStream->eof();
    }

    /**
     * Close both the remote stream and buffer stream
     */
    public function close()
    {
        $this->remoteStream->close() && $this->stream->close();
    }
}
<?php
namespace GuzzleHttp\Stream;

use GuzzleHttp\Stream\Exception\CannotAttachException;

/**
 * Provides a read only stream that pumps data from a PHP callable.
 *
 * When invoking the provided callable, the PumpStream will pass the amount of
 * data requested to read to the callable. The callable can choose to ignore
 * this value and return fewer or more bytes than requested. Any extra data
 * returned by the provided callable is buffered internally until drained using
 * the read() function of the PumpStream. The provided callable MUST return
 * false when there is no more data to read.
 */
class PumpStream implements StreamInterface
{
    /** @var callable */
    private $source;

    /** @var int */
    private $size;

    /** @var int */
    private $tellPos = 0;

    /** @var array */
    private $metadata;

    /** @var BufferStream */
    private $buffer;

    /**
     * @param callable $source Source of the stream data. The callable MAY
     *                         accept an integer argument used to control the
     *                         amount of data to return. The callable MUST
     *                         return a string when called, or false on error
     *                         or EOF.
     * @param array $options   Stream options:
     *                         - metadata: Hash of metadata to use with stream.
     *                         - size: Size of the stream, if known.
     */
    public function __construct(callable $source, array $options = [])
    {
        $this->source = $source;
        $this->size = isset($options['size']) ? $options['size'] : null;
        $this->metadata = isset($options['metadata']) ? $options['metadata'] : [];
        $this->buffer = new BufferStream();
    }

    public function __toString()
    {
        return Utils::copyToString($this);
    }

    public function close()
    {
        $this->detach();
    }

    public function detach()
    {
        $this->tellPos = false;
        $this->source = null;
    }

    public function attach($stream)
    {
        throw new CannotAttachException();
    }

    public function getSize()
    {
        return $this->size;
    }

    public function tell()
    {
        return $this->tellPos;
    }

    public function eof()
    {
        return !$this->source;
    }

    public function isSeekable()
    {
        return false;
    }

    public function seek($offset, $whence = SEEK_SET)
    {
        return false;
    }

    public function isWritable()
    {
        return false;
    }

    public function write($string)
    {
        return false;
    }

    public function isReadable()
    {
        return true;
    }

    public function read($length)
    {
        $data = $this->buffer->read($length);
        $readLen = strlen($data);
        $this->tellPos += $readLen;
        $remaining = $length - $readLen;

        if ($remaining) {
            $this->pump($remaining);
            $data .= $this->buffer->read($remaining);
            $this->tellPos += strlen($data) - $readLen;
        }

        return $data;
    }

    public function getContents()
    {
        $result = '';
        while (!$this->eof()) {
            $result .= $this->read(1000000);
        }

        return $result;
    }

    public function getMetadata($key = null)
    {
        if (!$key) {
            return $this->metadata;
        }

        return isset($this->metadata[$key]) ? $this->metadata[$key] : null;
    }

    private function pump($length)
    {
        if ($this->source) {
            do {
                $data = call_user_func($this->source, $length);
                if ($data === false || $data === null) {
                    $this->source = null;
                    return;
                }
                $this->buffer->write($data);
                $length -= strlen($data);
            } while ($length > 0);
        }
    }
}
<?php
namespace GuzzleHttp\Stream;

/**
 * Stream decorator that prevents a stream from being seeked
 */
class NoSeekStream implements StreamInterface
{
    use StreamDecoratorTrait;

    public function seek($offset, $whence = SEEK_SET)
    {
        return false;
    }

    public function isSeekable()
    {
        return false;
    }

    public function attach($stream)
    {
        $this->stream->attach($stream);
    }
}
<?php
namespace GuzzleHttp\Stream;

/**
 * Uses PHP's zlib.inflate filter to inflate deflate or gzipped content.
 *
 * This stream decorator skips the first 10 bytes of the given stream to remove
 * the gzip header, converts the provided stream to a PHP stream resource,
 * then appends the zlib.inflate filter. The stream is then converted back
 * to a Guzzle stream resource to be used as a Guzzle stream.
 *
 * @link http://tools.ietf.org/html/rfc1952
 * @link http://php.net/manual/en/filters.compression.php
 */
class InflateStream implements StreamInterface
{
    use StreamDecoratorTrait;

    public function __construct(StreamInterface $stream)
    {
        // Skip the first 10 bytes
        $stream = new LimitStream($stream, -1, 10);
        $resource = GuzzleStreamWrapper::getResource($stream);
        stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ);
        $this->stream = new Stream($resource);
    }
}
<?php
namespace GuzzleHttp\Stream;

/**
 * Converts Guzzle streams into PHP stream resources.
 */
class GuzzleStreamWrapper
{
    /** @var resource */
    public $context;

    /** @var StreamInterface */
    private $stream;

    /** @var string r, r+, or w */
    private $mode;

    /**
     * Returns a resource representing the stream.
     *
     * @param StreamInterface $stream The stream to get a resource for
     *
     * @return resource
     * @throws \InvalidArgumentException if stream is not readable or writable
     */
    public static function getResource(StreamInterface $stream)
    {
        self::register();

        if ($stream->isReadable()) {
            $mode = $stream->isWritable() ? 'r+' : 'r';
        } elseif ($stream->isWritable()) {
            $mode = 'w';
        } else {
            throw new \InvalidArgumentException('The stream must be readable, '
                . 'writable, or both.');
        }

        return fopen('guzzle://stream', $mode, null, stream_context_create([
            'guzzle' => ['stream' => $stream]
        ]));
    }

    /**
     * Registers the stream wrapper if needed
     */
    public static function register()
    {
        if (!in_array('guzzle', stream_get_wrappers())) {
            stream_wrapper_register('guzzle', __CLASS__);
        }
    }

    public function stream_open($path, $mode, $options, &$opened_path)
    {
        $options = stream_context_get_options($this->context);

        if (!isset($options['guzzle']['stream'])) {
            return false;
        }

        $this->mode = $mode;
        $this->stream = $options['guzzle']['stream'];

        return true;
    }

    public function stream_read($count)
    {
        return $this->stream->read($count);
    }

    public function stream_write($data)
    {
        return (int) $this->stream->write($data);
    }

    public function stream_tell()
    {
        return $this->stream->tell();
    }

    public function stream_eof()
    {
        return $this->stream->eof();
    }

    public function stream_seek($offset, $whence)
    {
        return $this->stream->seek($offset, $whence);
    }

    public function stream_stat()
    {
        static $modeMap = [
            'r'  => 33060,
            'r+' => 33206,
            'w'  => 33188
        ];

        return [
            'dev'     => 0,
            'ino'     => 0,
            'mode'    => $modeMap[$this->mode],
            'nlink'   => 0,
            'uid'     => 0,
            'gid'     => 0,
            'rdev'    => 0,
            'size'    => $this->stream->getSize() ?: 0,
            'atime'   => 0,
            'mtime'   => 0,
            'ctime'   => 0,
            'blksize' => 0,
            'blocks'  => 0
        ];
    }
}
<?php
namespace GuzzleHttp\Stream;

/**
 * PHP stream implementation
 */
class Stream implements StreamInterface
{
    private $stream;
    private $size;
    private $seekable;
    private $readable;
    private $writable;
    private $uri;
    private $customMetadata;

    /** @var array Hash of readable and writable stream types */
    private static $readWriteHash = [
        'read' => [
            'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
            'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
            'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
            'x+t' => true, 'c+t' => true, 'a+' => true
        ],
        'write' => [
            'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
            'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
            'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
            'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true
        ]
    ];

    /**
     * Create a new stream based on the input type.
     *
     * This factory accepts the same associative array of options as described
     * in the constructor.
     *
     * @param resource|string|StreamInterface $resource Entity body data
     * @param array                           $options  Additional options
     *
     * @return Stream
     * @throws \InvalidArgumentException if the $resource arg is not valid.
     */
    public static function factory($resource = '', array $options = [])
    {
        $type = gettype($resource);

        if ($type == 'string') {
            $stream = fopen('php://temp', 'r+');
            if ($resource !== '') {
                fwrite($stream, $resource);
                fseek($stream, 0);
            }
            return new self($stream, $options);
        }

        if ($type == 'resource') {
            return new self($resource, $options);
        }

        if ($resource instanceof StreamInterface) {
            return $resource;
        }

        if ($type == 'object' && method_exists($resource, '__toString')) {
            return self::factory((string) $resource, $options);
        }

        if (is_callable($resource)) {
            return new PumpStream($resource, $options);
        }

        if ($resource instanceof \Iterator) {
            return new PumpStream(function () use ($resource) {
                if (!$resource->valid()) {
                    return false;
                }
                $result = $resource->current();
                $resource->next();
                return $result;
            }, $options);
        }

        throw new \InvalidArgumentException('Invalid resource type: ' . $type);
    }

    /**
     * This constructor accepts an associative array of options.
     *
     * - size: (int) If a read stream would otherwise have an indeterminate
     *   size, but the size is known due to foreknownledge, then you can
     *   provide that size, in bytes.
     * - metadata: (array) Any additional metadata to return when the metadata
     *   of the stream is accessed.
     *
     * @param resource $stream  Stream resource to wrap.
     * @param array    $options Associative array of options.
     *
     * @throws \InvalidArgumentException if the stream is not a stream resource
     */
    public function __construct($stream, $options = [])
    {
        if (!is_resource($stream)) {
            throw new \InvalidArgumentException('Stream must be a resource');
        }

        if (isset($options['size'])) {
            $this->size = $options['size'];
        }

        $this->customMetadata = isset($options['metadata'])
            ? $options['metadata']
            : [];

        $this->attach($stream);
    }

    /**
     * Closes the stream when the destructed
     */
    public function __destruct()
    {
        $this->close();
    }

    public function __toString()
    {
        if (!$this->stream) {
            return '';
        }

        $this->seek(0);

        return (string) stream_get_contents($this->stream);
    }

    public function getContents()
    {
        return $this->stream ? stream_get_contents($this->stream) : '';
    }

    public function close()
    {
        if (is_resource($this->stream)) {
            fclose($this->stream);
        }

        $this->detach();
    }

    public function detach()
    {
        $result = $this->stream;
        $this->stream = $this->size = $this->uri = null;
        $this->readable = $this->writable = $this->seekable = false;

        return $result;
    }

    public function attach($stream)
    {
        $this->stream = $stream;
        $meta = stream_get_meta_data($this->stream);
        $this->seekable = $meta['seekable'];
        $this->readable = isset(self::$readWriteHash['read'][$meta['mode']]);
        $this->writable = isset(self::$readWriteHash['write'][$meta['mode']]);
        $this->uri = $this->getMetadata('uri');
    }

    public function getSize()
    {
        if ($this->size !== null) {
            return $this->size;
        }

        if (!$this->stream) {
            return null;
        }

        // Clear the stat cache if the stream has a URI
        if ($this->uri) {
            clearstatcache(true, $this->uri);
        }

        $stats = fstat($this->stream);
        if (isset($stats['size'])) {
            $this->size = $stats['size'];
            return $this->size;
        }

        return null;
    }

    public function isReadable()
    {
        return $this->readable;
    }

    public function isWritable()
    {
        return $this->writable;
    }

    public function isSeekable()
    {
        return $this->seekable;
    }

    public function eof()
    {
        return !$this->stream || feof($this->stream);
    }

    public function tell()
    {
        return $this->stream ? ftell($this->stream) : false;
    }

    public function setSize($size)
    {
        $this->size = $size;

        return $this;
    }

    public function seek($offset, $whence = SEEK_SET)
    {
        return $this->seekable
            ? fseek($this->stream, $offset, $whence) === 0
            : false;
    }

    public function read($length)
    {
        return $this->readable ? fread($this->stream, $length) : false;
    }

    public function write($string)
    {
        // We can't know the size after writing anything
        $this->size = null;

        return $this->writable ? fwrite($this->stream, $string) : false;
    }

    public function getMetadata($key = null)
    {
        if (!$this->stream) {
            return $key ? null : [];
        } elseif (!$key) {
            return $this->customMetadata + stream_get_meta_data($this->stream);
        } elseif (isset($this->customMetadata[$key])) {
            return $this->customMetadata[$key];
        }

        $meta = stream_get_meta_data($this->stream);

        return isset($meta[$key]) ? $meta[$key] : null;
    }
}
<?php
namespace GuzzleHttp\Stream;

/**
 * Represents an asynchronous read-only stream that supports a drain event and
 * pumping data from a source stream.
 *
 * The AsyncReadStream can be used as a completely asynchronous stream, meaning
 * the data you can read from the stream will immediately return only
 * the data that is currently buffered.
 *
 * AsyncReadStream can also be used in a "blocking" manner if a "pump" function
 * is provided. When a caller requests more bytes than are available in the
 * buffer, then the pump function is used to block until the requested number
 * of bytes are available or the remote source stream has errored, closed, or
 * timed-out. This behavior isn't strictly "blocking" because the pump function
 * can send other transfers while waiting on the desired buffer size to be
 * ready for reading (e.g., continue to tick an event loop).
 *
 * @unstable This class is subject to change.
 */
class AsyncReadStream implements StreamInterface
{
    use StreamDecoratorTrait;

    /** @var callable|null Fn used to notify writers the buffer has drained */
    private $drain;

    /** @var callable|null Fn used to block for more data */
    private $pump;

    /** @var int|null Highwater mark of the underlying buffer */
    private $hwm;

    /** @var bool Whether or not drain needs to be called at some point */
    private $needsDrain;

    /** @var int The expected size of the remote source */
    private $size;

    /**
     * In order to utilize high water marks to tell writers to slow down, the
     * provided stream must answer to the "hwm" stream metadata variable,
     * providing the high water mark. If no "hwm" metadata value is available,
     * then the "drain" functionality is not utilized.
     *
     * This class accepts an associative array of configuration options.
     *
     * - drain: (callable) Function to invoke when the stream has drained,
     *   meaning the buffer is now writable again because the size of the
     *   buffer is at an acceptable level (e.g., below the high water mark).
     *   The function accepts a single argument, the buffer stream object that
     *   has drained.
     * - pump: (callable) A function that accepts the number of bytes to read
     *   from the source stream. This function will block until all of the data
     *   that was requested has been read, EOF of the source stream, or the
     *   source stream is closed.
     * - size: (int) The expected size in bytes of the data that will be read
     *   (if known up-front).
     *
     * @param StreamInterface $buffer   Buffer that contains the data that has
     *                                  been read by the event loop.
     * @param array           $config   Associative array of options.
     *
     * @throws \InvalidArgumentException if the buffer is not readable and
     *                                   writable.
     */
    public function __construct(
        StreamInterface $buffer,
        array $config = []
    ) {
        if (!$buffer->isReadable() || !$buffer->isWritable()) {
            throw new \InvalidArgumentException(
                'Buffer must be readable and writable'
            );
        }

        if (isset($config['size'])) {
            $this->size = $config['size'];
        }

        static $callables = ['pump', 'drain'];
        foreach ($callables as $check) {
            if (isset($config[$check])) {
                if (!is_callable($config[$check])) {
                    throw new \InvalidArgumentException(
                        $check . ' must be callable'
                    );
                }
                $this->{$check} = $config[$check];
            }
        }

        $this->hwm = $buffer->getMetadata('hwm');

        // Cannot drain when there's no high water mark.
        if ($this->hwm === null) {
            $this->drain = null;
        }

        $this->stream = $buffer;
    }

    /**
     * Factory method used to create new async stream and an underlying buffer
     * if no buffer is provided.
     *
     * This function accepts the same options as AsyncReadStream::__construct,
     * but added the following key value pairs:
     *
     * - buffer: (StreamInterface) Buffer used to buffer data. If none is
     *   provided, a default buffer is created.
     * - hwm: (int) High water mark to use if a buffer is created on your
     *   behalf.
     * - max_buffer: (int) If provided, wraps the utilized buffer in a
     *   DroppingStream decorator to ensure that buffer does not exceed a given
     *   length. When exceeded, the stream will begin dropping data. Set the
     *   max_buffer to 0, to use a NullStream which does not store data.
     * - write: (callable) A function that is invoked when data is written
     *   to the underlying buffer. The function accepts the buffer as the first
     *   argument, and the data being written as the second. The function MUST
     *   return the number of bytes that were written or false to let writers
     *   know to slow down.
     * - drain: (callable) See constructor documentation.
     * - pump: (callable) See constructor documentation.
     *
     * @param array $options Associative array of options.
     *
     * @return array Returns an array containing the buffer used to buffer
     *               data, followed by the ready to use AsyncReadStream object.
     */
    public static function create(array $options = [])
    {
        $maxBuffer = isset($options['max_buffer'])
            ? $options['max_buffer']
            : null;

        if ($maxBuffer === 0) {
            $buffer = new NullStream();
        } elseif (isset($options['buffer'])) {
            $buffer = $options['buffer'];
        } else {
            $hwm = isset($options['hwm']) ? $options['hwm'] : 16384;
            $buffer = new BufferStream($hwm);
        }

        if ($maxBuffer > 0) {
            $buffer = new DroppingStream($buffer, $options['max_buffer']);
        }

        // Call the on_write callback if an on_write function was provided.
        if (isset($options['write'])) {
            $onWrite = $options['write'];
            $buffer = FnStream::decorate($buffer, [
                'write' => function ($string) use ($buffer, $onWrite) {
                    $result = $buffer->write($string);
                    $onWrite($buffer, $string);
                    return $result;
                }
            ]);
        }

        return [$buffer, new self($buffer, $options)];
    }

    public function getSize()
    {
        return $this->size;
    }

    public function isWritable()
    {
        return false;
    }

    public function write($string)
    {
        return false;
    }

    public function read($length)
    {
        if (!$this->needsDrain && $this->drain) {
            $this->needsDrain = $this->stream->getSize() >= $this->hwm;
        }

        $result = $this->stream->read($length);

        // If we need to drain, then drain when the buffer is empty.
        if ($this->needsDrain && $this->stream->getSize() === 0) {
            $this->needsDrain = false;
            $drainFn = $this->drain;
            $drainFn($this->stream);
        }

        $resultLen = strlen($result);

        // If a pump was provided, the buffer is still open, and not enough
        // data was given, then block until the data is provided.
        if ($this->pump && $resultLen < $length) {
            $pumpFn = $this->pump;
            $result .= $pumpFn($length - $resultLen);
        }

        return $result;
    }
}
<?php
namespace GuzzleHttp\Stream;

/**
 * Stream decorator that begins dropping data once the size of the underlying
 * stream becomes too full.
 */
class DroppingStream implements StreamInterface
{
    use StreamDecoratorTrait;

    private $maxLength;

    /**
     * @param StreamInterface $stream    Underlying stream to decorate.
     * @param int             $maxLength Maximum size before dropping data.
     */
    public function __construct(StreamInterface $stream, $maxLength)
    {
        $this->stream = $stream;
        $this->maxLength = $maxLength;
    }

    public function write($string)
    {
        $diff = $this->maxLength - $this->stream->getSize();

        // Begin returning false when the underlying stream is too large.
        if ($diff <= 0) {
            return false;
        }

        // Write the stream or a subset of the stream if needed.
        if (strlen($string) < $diff) {
            return $this->stream->write($string);
        }

        $this->stream->write(substr($string, 0, $diff));

        return false;
    }
}
<?php
namespace GuzzleHttp\Stream;

/**
 * Lazily reads or writes to a file that is opened only after an IO operation
 * take place on the stream.
 */
class LazyOpenStream implements StreamInterface
{
    use StreamDecoratorTrait;

    /** @var string File to open */
    private $filename;

    /** @var string $mode */
    private $mode;

    /**
     * @param string $filename File to lazily open
     * @param string $mode     fopen mode to use when opening the stream
     */
    public function __construct($filename, $mode)
    {
        $this->filename = $filename;
        $this->mode = $mode;
    }

    /**
     * Creates the underlying stream lazily when required.
     *
     * @return StreamInterface
     */
    protected function createStream()
    {
        return Stream::factory(Utils::open($this->filename, $this->mode));
    }
}
<?php
namespace GuzzleHttp\Stream;

use GuzzleHttp\Stream\Exception\CannotAttachException;

/**
 * Provides a buffer stream that can be written to to fill a buffer, and read
 * from to remove bytes from the buffer.
 *
 * This stream returns a "hwm" metadata value that tells upstream consumers
 * what the configured high water mark of the stream is, or the maximum
 * preferred size of the buffer.
 *
 * @package GuzzleHttp\Stream
 */
class BufferStream implements StreamInterface
{
    private $hwm;
    private $buffer = '';

    /**
     * @param int $hwm High water mark, representing the preferred maximum
     *                 buffer size. If the size of the buffer exceeds the high
     *                 water mark, then calls to write will continue to succeed
     *                 but will return false to inform writers to slow down
     *                 until the buffer has been drained by reading from it.
     */
    public function __construct($hwm = 16384)
    {
        $this->hwm = $hwm;
    }

    public function __toString()
    {
        return $this->getContents();
    }

    public function getContents()
    {
        $buffer = $this->buffer;
        $this->buffer = '';

        return $buffer;
    }

    public function close()
    {
        $this->buffer = '';
    }

    public function detach()
    {
        $this->close();
    }

    public function attach($stream)
    {
        throw new CannotAttachException();
    }

    public function getSize()
    {
        return strlen($this->buffer);
    }

    public function isReadable()
    {
        return true;
    }

    public function isWritable()
    {
        return true;
    }

    public function isSeekable()
    {
        return false;
    }

    public function seek($offset, $whence = SEEK_SET)
    {
        return false;
    }

    public function eof()
    {
        return strlen($this->buffer) === 0;
    }

    public function tell()
    {
        return false;
    }

    /**
     * Reads data from the buffer.
     */
    public function read($length)
    {
        $currentLength = strlen($this->buffer);

        if ($length >= $currentLength) {
            // No need to slice the buffer because we don't have enough data.
            $result = $this->buffer;
            $this->buffer = '';
        } else {
            // Slice up the result to provide a subset of the buffer.
            $result = substr($this->buffer, 0, $length);
            $this->buffer = substr($this->buffer, $length);
        }

        return $result;
    }

    /**
     * Writes data to the buffer.
     */
    public function write($string)
    {
        $this->buffer .= $string;

        if (strlen($this->buffer) >= $this->hwm) {
            return false;
        }

        return strlen($string);
    }

    public function getMetadata($key = null)
    {
        if ($key == 'hwm') {
            return $this->hwm;
        }

        return $key ? null : [];
    }
}
<?php
namespace GuzzleHttp\Stream;
use GuzzleHttp\Stream\Exception\CannotAttachException;

/**
 * Stream decorator trait
 * @property StreamInterface stream
 */
trait StreamDecoratorTrait
{
    /**
     * @param StreamInterface $stream Stream to decorate
     */
    public function __construct(StreamInterface $stream)
    {
        $this->stream = $stream;
    }

    /**
     * Magic method used to create a new stream if streams are not added in
     * the constructor of a decorator (e.g., LazyOpenStream).
     */
    public function __get($name)
    {
        if ($name == 'stream') {
            $this->stream = $this->createStream();
            return $this->stream;
        }

        throw new \UnexpectedValueException("$name not found on class");
    }

    public function __toString()
    {
        try {
            $this->seek(0);
            return $this->getContents();
        } catch (\Exception $e) {
            // Really, PHP? https://bugs.php.net/bug.php?id=53648
            trigger_error('StreamDecorator::__toString exception: '
                . (string) $e, E_USER_ERROR);
            return '';
        }
    }

    public function getContents()
    {
        return Utils::copyToString($this);
    }

    /**
     * Allow decorators to implement custom methods
     *
     * @param string $method Missing method name
     * @param array  $args   Method arguments
     *
     * @return mixed
     */
    public function __call($method, array $args)
    {
        $result = call_user_func_array(array($this->stream, $method), $args);

        // Always return the wrapped object if the result is a return $this
        return $result === $this->stream ? $this : $result;
    }

    public function close()
    {
        $this->stream->close();
    }

    public function getMetadata($key = null)
    {
        return $this->stream->getMetadata($key);
    }

    public function detach()
    {
        return $this->stream->detach();
    }

    public function attach($stream)
    {
        throw new CannotAttachException();
    }

    public function getSize()
    {
        return $this->stream->getSize();
    }

    public function eof()
    {
        return $this->stream->eof();
    }

    public function tell()
    {
        return $this->stream->tell();
    }

    public function isReadable()
    {
        return $this->stream->isReadable();
    }

    public function isWritable()
    {
        return $this->stream->isWritable();
    }

    public function isSeekable()
    {
        return $this->stream->isSeekable();
    }

    public function seek($offset, $whence = SEEK_SET)
    {
        return $this->stream->seek($offset, $whence);
    }

    public function read($length)
    {
        return $this->stream->read($length);
    }

    public function write($string)
    {
        return $this->stream->write($string);
    }

    /**
     * Implement in subclasses to dynamically create streams when requested.
     *
     * @return StreamInterface
     * @throws \BadMethodCallException
     */
    protected function createStream()
    {
        throw new \BadMethodCallException('createStream() not implemented in '
            . get_class($this));
    }
}
<?php
namespace GuzzleHttp\Stream;

use GuzzleHttp\Stream\Exception\SeekException;

/**
 * Static utility class because PHP's autoloaders don't support the concept
 * of namespaced function autoloading.
 */
class Utils
{
    /**
     * Safely opens a PHP stream resource using a filename.
     *
     * When fopen fails, PHP normally raises a warning. This function adds an
     * error handler that checks for errors and throws an exception instead.
     *
     * @param string $filename File to open
     * @param string $mode     Mode used to open the file
     *
     * @return resource
     * @throws \RuntimeException if the file cannot be opened
     */
    public static function open($filename, $mode)
    {
        $ex = null;
        set_error_handler(function () use ($filename, $mode, &$ex) {
            $ex = new \RuntimeException(sprintf(
                'Unable to open %s using mode %s: %s',
                $filename,
                $mode,
                func_get_args()[1]
            ));
        });

        $handle = fopen($filename, $mode);
        restore_error_handler();

        if ($ex) {
            /** @var $ex \RuntimeException */
            throw $ex;
        }

        return $handle;
    }

    /**
     * Copy the contents of a stream into a string until the given number of
     * bytes have been read.
     *
     * @param StreamInterface $stream Stream to read
     * @param int             $maxLen Maximum number of bytes to read. Pass -1
     *                                to read the entire stream.
     * @return string
     */
    public static function copyToString(StreamInterface $stream, $maxLen = -1)
    {
        $buffer = '';

        if ($maxLen === -1) {
            while (!$stream->eof()) {
                $buf = $stream->read(1048576);
                if ($buf === false) {
                    break;
                }
                $buffer .= $buf;
            }
            return $buffer;
        }

        $len = 0;
        while (!$stream->eof() && $len < $maxLen) {
            $buf = $stream->read($maxLen - $len);
            if ($buf === false) {
                break;
            }
            $buffer .= $buf;
            $len = strlen($buffer);
        }

        return $buffer;
    }

    /**
     * Copy the contents of a stream into another stream until the given number
     * of bytes have been read.
     *
     * @param StreamInterface $source Stream to read from
     * @param StreamInterface $dest   Stream to write to
     * @param int             $maxLen Maximum number of bytes to read. Pass -1
     *                                to read the entire stream.
     */
    public static function copyToStream(
        StreamInterface $source,
        StreamInterface $dest,
        $maxLen = -1
    ) {
        if ($maxLen === -1) {
            while (!$source->eof()) {
                if (!$dest->write($source->read(1048576))) {
                    break;
                }
            }
            return;
        }

        $bytes = 0;
        while (!$source->eof()) {
            $buf = $source->read($maxLen - $bytes);
            if (!($len = strlen($buf))) {
                break;
            }
            $bytes += $len;
            $dest->write($buf);
            if ($bytes == $maxLen) {
                break;
            }
        }
    }

    /**
     * Calculate a hash of a Stream
     *
     * @param StreamInterface $stream    Stream to calculate the hash for
     * @param string          $algo      Hash algorithm (e.g. md5, crc32, etc)
     * @param bool            $rawOutput Whether or not to use raw output
     *
     * @return string Returns the hash of the stream
     * @throws SeekException
     */
    public static function hash(
        StreamInterface $stream,
        $algo,
        $rawOutput = false
    ) {
        $pos = $stream->tell();

        if ($pos > 0 && !$stream->seek(0)) {
            throw new SeekException($stream);
        }

        $ctx = hash_init($algo);
        while (!$stream->eof()) {
            hash_update($ctx, $stream->read(1048576));
        }

        $out = hash_final($ctx, (bool) $rawOutput);
        $stream->seek($pos);

        return $out;
    }

    /**
     * Read a line from the stream up to the maximum allowed buffer length
     *
     * @param StreamInterface $stream    Stream to read from
     * @param int             $maxLength Maximum buffer length
     *
     * @return string|bool
     */
    public static function readline(StreamInterface $stream, $maxLength = null)
    {
        $buffer = '';
        $size = 0;

        while (!$stream->eof()) {
            if (false === ($byte = $stream->read(1))) {
                return $buffer;
            }
            $buffer .= $byte;
            // Break when a new line is found or the max length - 1 is reached
            if ($byte == PHP_EOL || ++$size == $maxLength - 1) {
                break;
            }
        }

        return $buffer;
    }

    /**
     * Alias of GuzzleHttp\Stream\Stream::factory.
     *
     * @param mixed $resource Resource to create
     * @param array $options  Associative array of stream options defined in
     *                        {@see \GuzzleHttp\Stream\Stream::__construct}
     *
     * @return StreamInterface
     *
     * @see GuzzleHttp\Stream\Stream::factory
     * @see GuzzleHttp\Stream\Stream::__construct
     */
    public static function create($resource, array $options = [])
    {
        return Stream::factory($resource, $options);
    }
}
<?php
namespace GuzzleHttp\Stream;

use GuzzleHttp\Stream\Exception\CannotAttachException;

/**
 * Reads from multiple streams, one after the other.
 *
 * This is a read-only stream decorator.
 */
class AppendStream implements StreamInterface
{
    /** @var StreamInterface[] Streams being decorated */
    private $streams = [];

    private $seekable = true;
    private $current = 0;
    private $pos = 0;
    private $detached = false;

    /**
     * @param StreamInterface[] $streams Streams to decorate. Each stream must
     *                                   be readable.
     */
    public function __construct(array $streams = [])
    {
        foreach ($streams as $stream) {
            $this->addStream($stream);
        }
    }

    public function __toString()
    {
        try {
            $this->seek(0);
            return $this->getContents();
        } catch (\Exception $e) {
            return '';
        }
    }

    /**
     * Add a stream to the AppendStream
     *
     * @param StreamInterface $stream Stream to append. Must be readable.
     *
     * @throws \InvalidArgumentException if the stream is not readable
     */
    public function addStream(StreamInterface $stream)
    {
        if (!$stream->isReadable()) {
            throw new \InvalidArgumentException('Each stream must be readable');
        }

        // The stream is only seekable if all streams are seekable
        if (!$stream->isSeekable()) {
            $this->seekable = false;
        }

        $this->streams[] = $stream;
    }

    public function getContents()
    {
        return Utils::copyToString($this);
    }

    /**
     * Closes each attached stream.
     *
     * {@inheritdoc}
     */
    public function close()
    {
        $this->pos = $this->current = 0;

        foreach ($this->streams as $stream) {
            $stream->close();
        }

        $this->streams = [];
    }

    /**
     * Detaches each attached stream
     *
     * {@inheritdoc}
     */
    public function detach()
    {
        $this->close();
        $this->detached = true;
    }

    public function attach($stream)
    {
        throw new CannotAttachException();
    }

    public function tell()
    {
        return $this->pos;
    }

    /**
     * Tries to calculate the size by adding the size of each stream.
     *
     * If any of the streams do not return a valid number, then the size of the
     * append stream cannot be determined and null is returned.
     *
     * {@inheritdoc}
     */
    public function getSize()
    {
        $size = 0;

        foreach ($this->streams as $stream) {
            $s = $stream->getSize();
            if ($s === null) {
                return null;
            }
            $size += $s;
        }

        return $size;
    }

    public function eof()
    {
        return !$this->streams ||
            ($this->current >= count($this->streams) - 1 &&
             $this->streams[$this->current]->eof());
    }

    /**
     * Attempts to seek to the given position. Only supports SEEK_SET.
     *
     * {@inheritdoc}
     */
    public function seek($offset, $whence = SEEK_SET)
    {
        if (!$this->seekable || $whence !== SEEK_SET) {
            return false;
        }

        $success = true;
        $this->pos = $this->current = 0;

        // Rewind each stream
        foreach ($this->streams as $stream) {
            if (!$stream->seek(0)) {
                $success = false;
            }
        }

        if (!$success) {
            return false;
        }

        // Seek to the actual position by reading from each stream
        while ($this->pos < $offset && !$this->eof()) {
            $this->read(min(8096, $offset - $this->pos));
        }

        return $this->pos == $offset;
    }

    /**
     * Reads from all of the appended streams until the length is met or EOF.
     *
     * {@inheritdoc}
     */
    public function read($length)
    {
        $buffer = '';
        $total = count($this->streams) - 1;
        $remaining = $length;

        while ($remaining > 0) {
            // Progress to the next stream if needed.
            if ($this->streams[$this->current]->eof()) {
                if ($this->current == $total) {
                    break;
                }
                $this->current++;
            }
            $buffer .= $this->streams[$this->current]->read($remaining);
            $remaining = $length - strlen($buffer);
        }

        $this->pos += strlen($buffer);

        return $buffer;
    }

    public function isReadable()
    {
        return true;
    }

    public function isWritable()
    {
        return false;
    }

    public function isSeekable()
    {
        return $this->seekable;
    }

    public function write($string)
    {
        return false;
    }

    public function getMetadata($key = null)
    {
        return $key ? null : [];
    }
}
<?php
namespace GuzzleHttp\Stream;

use GuzzleHttp\Stream\Exception\SeekException;

/**
 * Decorator used to return only a subset of a stream
 */
class LimitStream implements StreamInterface
{
    use StreamDecoratorTrait;

    /** @var int Offset to start reading from */
    private $offset;

    /** @var int Limit the number of bytes that can be read */
    private $limit;

    /**
     * @param StreamInterface $stream Stream to wrap
     * @param int             $limit  Total number of bytes to allow to be read
     *                                from the stream. Pass -1 for no limit.
     * @param int|null        $offset Position to seek to before reading (only
     *                                works on seekable streams).
     */
    public function __construct(
        StreamInterface $stream,
        $limit = -1,
        $offset = 0
    ) {
        $this->stream = $stream;
        $this->setLimit($limit);
        $this->setOffset($offset);
    }

    public function eof()
    {
        // Always return true if the underlying stream is EOF
        if ($this->stream->eof()) {
            return true;
        }

        // No limit and the underlying stream is not at EOF
        if ($this->limit == -1) {
            return false;
        }

        $tell = $this->stream->tell();
        if ($tell === false) {
            return false;
        }

        return $tell >= $this->offset + $this->limit;
    }

    /**
     * Returns the size of the limited subset of data
     * {@inheritdoc}
     */
    public function getSize()
    {
        if (null === ($length = $this->stream->getSize())) {
            return null;
        } elseif ($this->limit == -1) {
            return $length - $this->offset;
        } else {
            return min($this->limit, $length - $this->offset);
        }
    }

    /**
     * Allow for a bounded seek on the read limited stream
     * {@inheritdoc}
     */
    public function seek($offset, $whence = SEEK_SET)
    {
        if ($whence !== SEEK_SET || $offset < 0) {
            return false;
        }

        $offset += $this->offset;

        if ($this->limit !== -1) {
            if ($offset > $this->offset + $this->limit) {
                $offset = $this->offset + $this->limit;
            }
        }

        return $this->stream->seek($offset);
    }

    /**
     * Give a relative tell()
     * {@inheritdoc}
     */
    public function tell()
    {
        return $this->stream->tell() - $this->offset;
    }

    /**
     * Set the offset to start limiting from
     *
     * @param int $offset Offset to seek to and begin byte limiting from
     *
     * @return self
     * @throws SeekException
     */
    public function setOffset($offset)
    {
        $current = $this->stream->tell();

        if ($current !== $offset) {
            // If the stream cannot seek to the offset position, then read to it
            if (!$this->stream->seek($offset)) {
                if ($current > $offset) {
                    throw new SeekException($this, $offset);
                } else {
                    $this->stream->read($offset - $current);
                }
            }
        }

        $this->offset = $offset;

        return $this;
    }

    /**
     * Set the limit of bytes that the decorator allows to be read from the
     * stream.
     *
     * @param int $limit Number of bytes to allow to be read from the stream.
     *                   Use -1 for no limit.
     * @return self
     */
    public function setLimit($limit)
    {
        $this->limit = $limit;

        return $this;
    }

    public function read($length)
    {
        if ($this->limit == -1) {
            return $this->stream->read($length);
        }

        // Check if the current position is less than the total allowed
        // bytes + original offset
        $remaining = ($this->offset + $this->limit) - $this->stream->tell();
        if ($remaining > 0) {
            // Only return the amount of requested data, ensuring that the byte
            // limit is not exceeded
            return $this->stream->read(min($remaining, $length));
        } else {
            return false;
        }
    }
}
<?php
namespace GuzzleHttp\Stream\Exception;

use GuzzleHttp\Stream\StreamInterface;

/**
 * Exception thrown when a seek fails on a stream.
 */
class SeekException extends \RuntimeException
{
    private $stream;

    public function __construct(StreamInterface $stream, $pos = 0, $msg = '')
    {
        $this->stream = $stream;
        $msg = $msg ?: 'Could not seek the stream to position ' . $pos;
        parent::__construct($msg);
    }

    /**
     * @return StreamInterface
     */
    public function getStream()
    {
        return $this->stream;
    }
}
<?php
namespace GuzzleHttp\Stream\Exception;

class CannotAttachException extends \RuntimeException {}
{
    "name": "guzzlehttp/ringphp",
    "description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function.",
    "license": "MIT",
    "authors": [
        {
            "name": "Michael Dowling",
            "email": "mtdowling@gmail.com",
            "homepage": "https://github.com/mtdowling"
        }
    ],
    "require": {
        "php": ">=5.4.0",
        "guzzlehttp/streams": "~3.0",
        "react/promise": "~2.0"
    },
    "require-dev": {
        "ext-curl": "*",
        "phpunit/phpunit": "~4.0"
    },
    "suggest": {
        "ext-curl": "Guzzle will use specific adapters if cURL is present"
    },
    "autoload": {
        "psr-4": {
            "GuzzleHttp\\Ring\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "GuzzleHttp\\Tests\\Ring\\": "tests/"
        }
    },
    "scripts": {
        "test": "make test",
        "test-ci": "make coverage"
    },
    "extra": {
        "branch-alias": {
            "dev-master": "1.1-dev"
        }
    }
}
<?php
namespace GuzzleHttp\Ring\Exception;

/**
 * Occurs when the connection failed.
 */
class ConnectException extends RingException {}
<?php
namespace GuzzleHttp\Ring\Exception;

/**
 * Marker interface for cancelled exceptions.
 */
interface CancelledException {}
<?php
namespace GuzzleHttp\Ring\Exception;

class CancelledFutureAccessException extends RingException implements CancelledException {}
<?php
namespace GuzzleHttp\Ring\Exception;

class RingException extends \RuntimeException {};
<?php
namespace GuzzleHttp\Ring\Future;

/**
 * Represents a future array value that when dereferenced returns an array.
 */
class FutureArray implements FutureArrayInterface
{
    use MagicFutureTrait;

    public function offsetExists($offset)
    {
        return isset($this->_value[$offset]);
    }

    public function offsetGet($offset)
    {
        return $this->_value[$offset];
    }

    public function offsetSet($offset, $value)
    {
        $this->_value[$offset] = $value;
    }

    public function offsetUnset($offset)
    {
        unset($this->_value[$offset]);
    }

    public function count()
    {
        return count($this->_value);
    }

    public function getIterator()
    {
        return new \ArrayIterator($this->_value);
    }
}
<?php
namespace GuzzleHttp\Ring\Future;

/**
 * Represents a future value that responds to wait() to retrieve the promised
 * value, but can also return promises that are delivered the value when it is
 * available.
 */
class FutureValue implements FutureInterface
{
    use BaseFutureTrait;
}
<?php
namespace GuzzleHttp\Ring\Future;

use React\Promise\PromiseInterface;
use React\Promise\PromisorInterface;

/**
 * Represents the result of a computation that may not have completed yet.
 *
 * You can use the future in a blocking manner using the wait() function, or
 * you can use a promise from the future to receive the result when the future
 * has been resolved.
 *
 * When the future is dereferenced using wait(), the result of the computation
 * is cached and returned for subsequent calls to wait(). If the result of the
 * computation has not yet completed when wait() is called, the call to wait()
 * will block until the future has completed.
 */
interface FutureInterface extends PromiseInterface, PromisorInterface
{
    /**
     * Returns the result of the future either from cache or by blocking until
     * it is complete.
     *
     * This method must block until the future has a result or is cancelled.
     * Throwing an exception in the wait() method will mark the future as
     * realized and will throw the exception each time wait() is called.
     * Throwing an instance of GuzzleHttp\Ring\CancelledException will mark
     * the future as realized, will not throw immediately, but will throw the
     * exception if the future's wait() method is called again.
     *
     * @return mixed
     */
    public function wait();

    /**
     * Cancels the future, if possible.
     */
    public function cancel();
}
<?php
namespace GuzzleHttp\Ring\Future;

use GuzzleHttp\Ring\Exception\CancelledFutureAccessException;
use GuzzleHttp\Ring\Exception\RingException;
use React\Promise\PromiseInterface;

/**
 * Implements common future functionality built on top of promises.
 */
trait BaseFutureTrait
{
    /** @var callable */
    private $waitfn;

    /** @var callable */
    private $cancelfn;

    /** @var PromiseInterface */
    private $wrappedPromise;

    /** @var \Exception Error encountered. */
    private $error;

    /** @var mixed Result of the future */
    private $result;

    private $isRealized = false;

    /**
     * @param PromiseInterface $promise Promise to shadow with the future.
     * @param callable         $wait    Function that blocks until the deferred
     *                                  computation has been resolved. This
     *                                  function MUST resolve the deferred value
     *                                  associated with the supplied promise.
     * @param callable         $cancel  If possible and reasonable, provide a
     *                                  function that can be used to cancel the
     *                                  future from completing.
     */
    public function __construct(
        PromiseInterface $promise,
        callable $wait = null,
        callable $cancel = null
    ) {
        $this->wrappedPromise = $promise;
        $this->waitfn = $wait;
        $this->cancelfn = $cancel;
    }

    public function wait()
    {
        if (!$this->isRealized) {
            $this->addShadow();
            if (!$this->isRealized && $this->waitfn) {
                $this->invokeWait();
            }
            if (!$this->isRealized) {
                $this->error = new RingException('Waiting did not resolve future');
            }
        }

        if ($this->error) {
            throw $this->error;
        }

        return $this->result;
    }

    public function promise()
    {
        return $this->wrappedPromise;
    }

    public function then(
        callable $onFulfilled = null,
        callable $onRejected = null,
        callable $onProgress = null
    ) {
        return $this->wrappedPromise->then($onFulfilled, $onRejected, $onProgress);
    }

    public function cancel()
    {
        if (!$this->isRealized) {
            $cancelfn = $this->cancelfn;
            $this->waitfn = $this->cancelfn = null;
            $this->isRealized = true;
            $this->error = new CancelledFutureAccessException();
            if ($cancelfn) {
                $cancelfn($this);
            }
        }
    }

    private function addShadow()
    {
        // Get the result and error when the promise is resolved. Note that
        // calling this function might trigger the resolution immediately.
        $this->wrappedPromise->then(
            function ($value) {
                $this->isRealized = true;
                $this->result = $value;
                $this->waitfn = $this->cancelfn = null;
            },
            function ($error) {
                $this->isRealized = true;
                $this->error = $error;
                $this->waitfn = $this->cancelfn = null;
            }
        );
    }

    private function invokeWait()
    {
        try {
            $wait = $this->waitfn;
            $this->waitfn = null;
            $wait();
        } catch (\Exception $e) {
            // Defer can throw to reject.
            $this->error = $e;
            $this->isRealized = true;
        }
    }
}
<?php
namespace GuzzleHttp\Ring\Future;

/**
 * Future that provides array-like access.
 */
interface FutureArrayInterface extends
    FutureInterface,
    \ArrayAccess,
    \Countable,
    \IteratorAggregate {};
<?php
namespace GuzzleHttp\Ring\Future;

use React\Promise\FulfilledPromise;
use React\Promise\RejectedPromise;

/**
 * Represents a future value that has been resolved or rejected.
 */
class CompletedFutureValue implements FutureInterface
{
    protected $result;
    protected $error;

    private $cachedPromise;

    /**
     * @param mixed      $result Resolved result
     * @param \Exception $e      Error. Pass a GuzzleHttp\Ring\Exception\CancelledFutureAccessException
     *                           to mark the future as cancelled.
     */
    public function __construct($result, \Exception $e = null)
    {
        $this->result = $result;
        $this->error = $e;
    }

    public function wait()
    {
        if ($this->error) {
            throw $this->error;
        }

        return $this->result;
    }

    public function cancel() {}

    public function promise()
    {
        if (!$this->cachedPromise) {
            $this->cachedPromise = $this->error
                ? new RejectedPromise($this->error)
                : new FulfilledPromise($this->result);
        }

        return $this->cachedPromise;
    }

    public function then(
        callable $onFulfilled = null,
        callable $onRejected = null,
        callable $onProgress = null
    ) {
        return $this->promise()->then($onFulfilled, $onRejected, $onProgress);
    }
}
<?php
namespace GuzzleHttp\Ring\Future;

/**
 * Represents a future array that has been completed successfully.
 */
class CompletedFutureArray extends CompletedFutureValue implements FutureArrayInterface
{
    public function __construct(array $result)
    {
        parent::__construct($result);
    }

    public function offsetExists($offset)
    {
        return isset($this->result[$offset]);
    }

    public function offsetGet($offset)
    {
        return $this->result[$offset];
    }

    public function offsetSet($offset, $value)
    {
        $this->result[$offset] = $value;
    }

    public function offsetUnset($offset)
    {
        unset($this->result[$offset]);
    }

    public function count()
    {
        return count($this->result);
    }

    public function getIterator()
    {
        return new \ArrayIterator($this->result);
    }
}
<?php
namespace GuzzleHttp\Ring\Future;

/**
 * Implements common future functionality that is triggered when the result
 * property is accessed via a magic __get method.
 *
 * @property mixed $_value Actual data used by the future. Accessing this
 *     property will cause the future to block if needed.
 */
trait MagicFutureTrait
{
    use BaseFutureTrait;

    /**
     * This function handles retrieving the dereferenced result when requested.
     *
     * @param string $name Should always be "data" or an exception is thrown.
     *
     * @return mixed Returns the dereferenced data.
     * @throws \RuntimeException
     * @throws \GuzzleHttp\Ring\Exception\CancelledException
     */
    public function __get($name)
    {
        if ($name !== '_value') {
            throw new \RuntimeException("Class has no {$name} property");
        }

        return $this->_value = $this->wait();
    }
}
<?php
namespace GuzzleHttp\Ring\Client;

use GuzzleHttp\Ring\Core;
use GuzzleHttp\Ring\Exception\ConnectException;
use GuzzleHttp\Ring\Exception\RingException;
use GuzzleHttp\Stream\LazyOpenStream;
use GuzzleHttp\Stream\StreamInterface;

/**
 * Creates curl resources from a request
 */
class CurlFactory
{
    /**
     * Creates a cURL handle, header resource, and body resource based on a
     * transaction.
     *
     * @param array         $request Request hash
     * @param null|resource $handle  Optionally provide a curl handle to modify
     *
     * @return array Returns an array of the curl handle, headers array, and
     *               response body handle.
     * @throws \RuntimeException when an option cannot be applied
     */
    public function __invoke(array $request, $handle = null)
    {
        $headers = [];
        $options = $this->getDefaultOptions($request, $headers);
        $this->applyMethod($request, $options);

        if (isset($request['client'])) {
            $this->applyHandlerOptions($request, $options);
        }

        $this->applyHeaders($request, $options);
        unset($options['_headers']);

        // Add handler options from the request's configuration options
        if (isset($request['client']['curl'])) {
            $options = $this->applyCustomCurlOptions(
                $request['client']['curl'],
                $options
            );
        }

        if (!$handle) {
            $handle = curl_init();
        }

        $body = $this->getOutputBody($request, $options);
        curl_setopt_array($handle, $options);

        return [$handle, &$headers, $body];
    }

    /**
     * Creates a response hash from a cURL result.
     *
     * @param callable $handler  Handler that was used.
     * @param array    $request  Request that sent.
     * @param array    $response Response hash to update.
     * @param array    $headers  Headers received during transfer.
     * @param resource $body     Body fopen response.
     *
     * @return array
     */
    public static function createResponse(
        callable $handler,
        array $request,
        array $response,
        array $headers,
        $body
    ) {
        if (isset($response['transfer_stats']['url'])) {
            $response['effective_url'] = $response['transfer_stats']['url'];
        }

        if (!empty($headers)) {
            $startLine = explode(' ', array_shift($headers), 3);
            $headerList = Core::headersFromLines($headers);
            $response['headers'] = $headerList;
            $response['version'] = isset($startLine[0]) ? substr($startLine[0], 5) : null;
            $response['status'] = isset($startLine[1]) ? (int) $startLine[1] : null;
            $response['reason'] = isset($startLine[2]) ? $startLine[2] : null;
            $response['body'] = $body;
            Core::rewindBody($response);
        }

        return !empty($response['curl']['errno']) || !isset($response['status'])
            ? self::createErrorResponse($handler, $request, $response)
            : $response;
    }

    private static function createErrorResponse(
        callable $handler,
        array $request,
        array $response
    ) {
        static $connectionErrors = [
            CURLE_OPERATION_TIMEOUTED  => true,
            CURLE_COULDNT_RESOLVE_HOST => true,
            CURLE_COULDNT_CONNECT      => true,
            CURLE_SSL_CONNECT_ERROR    => true,
            CURLE_GOT_NOTHING          => true,
        ];

        // Retry when nothing is present or when curl failed to rewind.
        if (!isset($response['err_message'])
            && (empty($response['curl']['errno'])
                || $response['curl']['errno'] == 65)
        ) {
            return self::retryFailedRewind($handler, $request, $response);
        }

        $message = isset($response['err_message'])
            ? $response['err_message']
            : sprintf('cURL error %s: %s',
                $response['curl']['errno'],
                isset($response['curl']['error'])
                    ? $response['curl']['error']
                    : 'See http://curl.haxx.se/libcurl/c/libcurl-errors.html');

        $error = isset($response['curl']['errno'])
            && isset($connectionErrors[$response['curl']['errno']])
            ? new ConnectException($message)
            : new RingException($message);

        return $response + [
            'status'  => null,
            'reason'  => null,
            'body'    => null,
            'headers' => [],
            'error'   => $error,
        ];
    }

    private function getOutputBody(array $request, array &$options)
    {
        // Determine where the body of the response (if any) will be streamed.
        if (isset($options[CURLOPT_WRITEFUNCTION])) {
            return $request['client']['save_to'];
        }

        if (isset($options[CURLOPT_FILE])) {
            return $options[CURLOPT_FILE];
        }

        if ($request['http_method'] != 'HEAD') {
            // Create a default body if one was not provided
            return $options[CURLOPT_FILE] = fopen('php://temp', 'w+');
        }

        return null;
    }

    private function getDefaultOptions(array $request, array &$headers)
    {
        $url = Core::url($request);
        $startingResponse = false;

        $options = [
            '_headers'             => $request['headers'],
            CURLOPT_CUSTOMREQUEST  => $request['http_method'],
            CURLOPT_URL            => $url,
            CURLOPT_RETURNTRANSFER => false,
            CURLOPT_HEADER         => false,
            CURLOPT_CONNECTTIMEOUT => 150,
            CURLOPT_HEADERFUNCTION => function ($ch, $h) use (&$headers, &$startingResponse) {
                $value = trim($h);
                if ($value === '') {
                    $startingResponse = true;
                } elseif ($startingResponse) {
                    $startingResponse = false;
                    $headers = [$value];
                } else {
                    $headers[] = $value;
                }
                return strlen($h);
            },
        ];

        if (isset($request['version'])) {
            if ($request['version'] == 2.0) {
                $options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0;
            } else if ($request['version'] == 1.1) {
                $options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1;
            } else {
                $options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
            }
        }

        if (defined('CURLOPT_PROTOCOLS')) {
            $options[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
        }

        return $options;
    }

    private function applyMethod(array $request, array &$options)
    {
        if (isset($request['body'])) {
            $this->applyBody($request, $options);
            return;
        }

        switch ($request['http_method']) {
            case 'PUT':
            case 'POST':
                // See http://tools.ietf.org/html/rfc7230#section-3.3.2
                if (!Core::hasHeader($request, 'Content-Length')) {
                    $options[CURLOPT_HTTPHEADER][] = 'Content-Length: 0';
                }
                break;
            case 'HEAD':
                $options[CURLOPT_NOBODY] = true;
                unset(
                    $options[CURLOPT_WRITEFUNCTION],
                    $options[CURLOPT_READFUNCTION],
                    $options[CURLOPT_FILE],
                    $options[CURLOPT_INFILE]
                );
        }
    }

    private function applyBody(array $request, array &$options)
    {
        $contentLength = Core::firstHeader($request, 'Content-Length');
        $size = $contentLength !== null ? (int) $contentLength : null;

        // Send the body as a string if the size is less than 1MB OR if the
        // [client][curl][body_as_string] request value is set.
        if (($size !== null && $size < 1000000) ||
            isset($request['client']['curl']['body_as_string']) ||
            is_string($request['body'])
        ) {
            $options[CURLOPT_POSTFIELDS] = Core::body($request);
            // Don't duplicate the Content-Length header
            $this->removeHeader('Content-Length', $options);
            $this->removeHeader('Transfer-Encoding', $options);
        } else {
            $options[CURLOPT_UPLOAD] = true;
            if ($size !== null) {
                // Let cURL handle setting the Content-Length header
                $options[CURLOPT_INFILESIZE] = $size;
                $this->removeHeader('Content-Length', $options);
            }
            $this->addStreamingBody($request, $options);
        }

        // If the Expect header is not present, prevent curl from adding it
        if (!Core::hasHeader($request, 'Expect')) {
            $options[CURLOPT_HTTPHEADER][] = 'Expect:';
        }

        // cURL sometimes adds a content-type by default. Prevent this.
        if (!Core::hasHeader($request, 'Content-Type')) {
            $options[CURLOPT_HTTPHEADER][] = 'Content-Type:';
        }
    }

    private function addStreamingBody(array $request, array &$options)
    {
        $body = $request['body'];

        if ($body instanceof StreamInterface) {
            $options[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) {
                return (string) $body->read($length);
            };
            if (!isset($options[CURLOPT_INFILESIZE])) {
                if ($size = $body->getSize()) {
                    $options[CURLOPT_INFILESIZE] = $size;
                }
            }
        } elseif (is_resource($body)) {
            $options[CURLOPT_INFILE] = $body;
        } elseif ($body instanceof \Iterator) {
            $buf = '';
            $options[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body, &$buf) {
                if ($body->valid()) {
                    $buf .= $body->current();
                    $body->next();
                }
                $result = (string) substr($buf, 0, $length);
                $buf = substr($buf, $length);
                return $result;
            };
        } else {
            throw new \InvalidArgumentException('Invalid request body provided');
        }
    }

    private function applyHeaders(array $request, array &$options)
    {
        foreach ($options['_headers'] as $name => $values) {
            foreach ($values as $value) {
                $options[CURLOPT_HTTPHEADER][] = "$name: $value";
            }
        }

        // Remove the Accept header if one was not set
        if (!Core::hasHeader($request, 'Accept')) {
            $options[CURLOPT_HTTPHEADER][] = 'Accept:';
        }
    }

    /**
     * Takes an array of curl options specified in the 'curl' option of a
     * request's configuration array and maps them to CURLOPT_* options.
     *
     * This method is only called when a  request has a 'curl' config setting.
     *
     * @param array $config  Configuration array of custom curl option
     * @param array $options Array of existing curl options
     *
     * @return array Returns a new array of curl options
     */
    private function applyCustomCurlOptions(array $config, array $options)
    {
        $curlOptions = [];
        foreach ($config as $key => $value) {
            if (is_int($key)) {
                $curlOptions[$key] = $value;
            }
        }

        return $curlOptions + $options;
    }

    /**
     * Remove a header from the options array.
     *
     * @param string $name    Case-insensitive header to remove
     * @param array  $options Array of options to modify
     */
    private function removeHeader($name, array &$options)
    {
        foreach (array_keys($options['_headers']) as $key) {
            if (!strcasecmp($key, $name)) {
                unset($options['_headers'][$key]);
                return;
            }
        }
    }

    /**
     * Applies an array of request client options to a the options array.
     *
     * This method uses a large switch rather than double-dispatch to save on
     * high overhead of calling functions in PHP.
     */
    private function applyHandlerOptions(array $request, array &$options)
    {
        foreach ($request['client'] as $key => $value) {
            switch ($key) {
            // Violating PSR-4 to provide more room.
            case 'verify':

                if ($value === false) {
                    unset($options[CURLOPT_CAINFO]);
                    $options[CURLOPT_SSL_VERIFYHOST] = 0;
                    $options[CURLOPT_SSL_VERIFYPEER] = false;
                    continue 2;
                }

                $options[CURLOPT_SSL_VERIFYHOST] = 2;
                $options[CURLOPT_SSL_VERIFYPEER] = true;

                if (is_string($value)) {
                    $options[CURLOPT_CAINFO] = $value;
                    if (!file_exists($value)) {
                        throw new \InvalidArgumentException(
                            "SSL CA bundle not found: $value"
                        );
                    }
                }
                break;

            case 'decode_content':

                if ($value === false) {
                    continue 2;
                }

                $accept = Core::firstHeader($request, 'Accept-Encoding');
                if ($accept) {
                    $options[CURLOPT_ENCODING] = $accept;
                } else {
                    $options[CURLOPT_ENCODING] = '';
                    // Don't let curl send the header over the wire
                    $options[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:';
                }
                break;

            case 'save_to':

                if (is_string($value)) {
                    if (!is_dir(dirname($value))) {
                        throw new \RuntimeException(sprintf(
                            'Directory %s does not exist for save_to value of %s',
                            dirname($value),
                            $value
                        ));
                    }
                    $value = new LazyOpenStream($value, 'w+');
                }

                if ($value instanceof StreamInterface) {
                    $options[CURLOPT_WRITEFUNCTION] =
                        function ($ch, $write) use ($value) {
                            return $value->write($write);
                        };
                } elseif (is_resource($value)) {
                    $options[CURLOPT_FILE] = $value;
                } else {
                    throw new \InvalidArgumentException('save_to must be a '
                        . 'GuzzleHttp\Stream\StreamInterface or resource');
                }
                break;

            case 'timeout':

                if (defined('CURLOPT_TIMEOUT_MS')) {
                    $options[CURLOPT_TIMEOUT_MS] = $value * 1000;
                } else {
                    $options[CURLOPT_TIMEOUT] = $value;
                }
                break;

            case 'connect_timeout':

                if (defined('CURLOPT_CONNECTTIMEOUT_MS')) {
                    $options[CURLOPT_CONNECTTIMEOUT_MS] = $value * 1000;
                } else {
                    $options[CURLOPT_CONNECTTIMEOUT] = $value;
                }
                break;

            case 'proxy':

                if (!is_array($value)) {
                    $options[CURLOPT_PROXY] = $value;
                } elseif (isset($request['scheme'])) {
                    $scheme = $request['scheme'];
                    if (isset($value[$scheme])) {
                        $options[CURLOPT_PROXY] = $value[$scheme];
                    }
                }
                break;

            case 'cert':

                if (is_array($value)) {
                    $options[CURLOPT_SSLCERTPASSWD] = $value[1];
                    $value = $value[0];
                }

                if (!file_exists($value)) {
                    throw new \InvalidArgumentException(
                        "SSL certificate not found: {$value}"
                    );
                }

                $options[CURLOPT_SSLCERT] = $value;
                break;

            case 'ssl_key':

                if (is_array($value)) {
                    $options[CURLOPT_SSLKEYPASSWD] = $value[1];
                    $value = $value[0];
                }

                if (!file_exists($value)) {
                    throw new \InvalidArgumentException(
                        "SSL private key not found: {$value}"
                    );
                }

                $options[CURLOPT_SSLKEY] = $value;
                break;

            case 'progress':

                if (!is_callable($value)) {
                    throw new \InvalidArgumentException(
                        'progress client option must be callable'
                    );
                }

                $options[CURLOPT_NOPROGRESS] = false;
                $options[CURLOPT_PROGRESSFUNCTION] =
                    function () use ($value) {
                        $args = func_get_args();
                        // PHP 5.5 pushed the handle onto the start of the args
                        if (is_resource($args[0])) {
                            array_shift($args);
                        }
                        call_user_func_array($value, $args);
                    };
                break;

            case 'debug':

                if ($value) {
                    $options[CURLOPT_STDERR] = Core::getDebugResource($value);
                    $options[CURLOPT_VERBOSE] = true;
                }
                break;
            }
        }
    }

    /**
     * This function ensures that a response was set on a transaction. If one
     * was not set, then the request is retried if possible. This error
     * typically means you are sending a payload, curl encountered a
     * "Connection died, retrying a fresh connect" error, tried to rewind the
     * stream, and then encountered a "necessary data rewind wasn't possible"
     * error, causing the request to be sent through curl_multi_info_read()
     * without an error status.
     */
    private static function retryFailedRewind(
        callable $handler,
        array $request,
        array $response
    ) {
        // If there is no body, then there is some other kind of issue. This
        // is weird and should probably never happen.
        if (!isset($request['body'])) {
            $response['err_message'] = 'No response was received for a request '
                . 'with no body. This could mean that you are saturating your '
                . 'network.';
            return self::createErrorResponse($handler, $request, $response);
        }

        if (!Core::rewindBody($request)) {
            $response['err_message'] = 'The connection unexpectedly failed '
                . 'without providing an error. The request would have been '
                . 'retried, but attempting to rewind the request body failed.';
            return self::createErrorResponse($handler, $request, $response);
        }

        // Retry no more than 3 times before giving up.
        if (!isset($request['curl']['retries'])) {
            $request['curl']['retries'] = 1;
        } elseif ($request['curl']['retries'] == 2) {
            $response['err_message'] = 'The cURL request was retried 3 times '
                . 'and did no succeed. cURL was unable to rewind the body of '
                . 'the request and subsequent retries resulted in the same '
                . 'error. Turn on the debug option to see what went wrong. '
                . 'See https://bugs.php.net/bug.php?id=47204 for more information.';
            return self::createErrorResponse($handler, $request, $response);
        } else {
            $request['curl']['retries']++;
        }

        return $handler($request);
    }
}
<?php
namespace GuzzleHttp\Ring\Client;

use GuzzleHttp\Ring\Future\FutureArray;
use React\Promise\Deferred;

/**
 * Returns an asynchronous response using curl_multi_* functions.
 *
 * This handler supports future responses and the "delay" request client
 * option that can be used to delay before sending a request.
 *
 * When using the CurlMultiHandler, custom curl options can be specified as an
 * associative array of curl option constants mapping to values in the
 * **curl** key of the "client" key of the request.
 *
 * @property resource $_mh Internal use only. Lazy loaded multi-handle.
 */
class CurlMultiHandler
{
    /** @var callable */
    private $factory;
    private $selectTimeout;
    private $active;
    private $handles = [];
    private $delays = [];
    private $maxHandles;

    /**
     * This handler accepts the following options:
     *
     * - mh: An optional curl_multi resource
     * - handle_factory: An optional callable used to generate curl handle
     *   resources. the callable accepts a request hash and returns an array
     *   of the handle, headers file resource, and the body resource.
     * - select_timeout: Optional timeout (in seconds) to block before timing
     *   out while selecting curl handles. Defaults to 1 second.
     * - max_handles: Optional integer representing the maximum number of
     *   open requests. When this number is reached, the queued futures are
     *   flushed.
     *
     * @param array $options
     */
    public function __construct(array $options = [])
    {
        if (isset($options['mh'])) {
            $this->_mh = $options['mh'];
        }
        $this->factory = isset($options['handle_factory'])
            ? $options['handle_factory'] : new CurlFactory();
        $this->selectTimeout = isset($options['select_timeout'])
            ? $options['select_timeout'] : 1;
        $this->maxHandles = isset($options['max_handles'])
            ? $options['max_handles'] : 100;
    }

    public function __get($name)
    {
        if ($name === '_mh') {
            return $this->_mh = curl_multi_init();
        }

        throw new \BadMethodCallException();
    }

    public function __destruct()
    {
        // Finish any open connections before terminating the script.
        if ($this->handles) {
            $this->execute();
        }

        if (isset($this->_mh)) {
            curl_multi_close($this->_mh);
            unset($this->_mh);
        }
    }

    public function __invoke(array $request)
    {
        $factory = $this->factory;
        $result = $factory($request);
        $entry = [
            'request'  => $request,
            'response' => [],
            'handle'   => $result[0],
            'headers'  => &$result[1],
            'body'     => $result[2],
            'deferred' => new Deferred(),
        ];

        $id = (int) $result[0];

        $future = new FutureArray(
            $entry['deferred']->promise(),
            [$this, 'execute'],
            function () use ($id) {
                return $this->cancel($id);
            }
        );

        $this->addRequest($entry);

        // Transfer outstanding requests if there are too many open handles.
        if (count($this->handles) >= $this->maxHandles) {
            $this->execute();
        }

        return $future;
    }

    /**
     * Runs until all outstanding connections have completed.
     */
    public function execute()
    {
        do {

            if ($this->active &&
                curl_multi_select($this->_mh, $this->selectTimeout) === -1
            ) {
                // Perform a usleep if a select returns -1.
                // See: https://bugs.php.net/bug.php?id=61141
                usleep(250);
            }

            // Add any delayed futures if needed.
            if ($this->delays) {
                $this->addDelays();
            }

            do {
                $mrc = curl_multi_exec($this->_mh, $this->active);
            } while ($mrc === CURLM_CALL_MULTI_PERFORM);

            $this->processMessages();

            // If there are delays but no transfers, then sleep for a bit.
            if (!$this->active && $this->delays) {
                usleep(500);
            }

        } while ($this->active || $this->handles);
    }

    private function addRequest(array &$entry)
    {
        $id = (int) $entry['handle'];
        $this->handles[$id] = $entry;

        // If the request is a delay, then add the reques to the curl multi
        // pool only after the specified delay.
        if (isset($entry['request']['client']['delay'])) {
            $this->delays[$id] = microtime(true) + ($entry['request']['client']['delay'] / 1000);
        } elseif (empty($entry['request']['future'])) {
            curl_multi_add_handle($this->_mh, $entry['handle']);
        } else {
            curl_multi_add_handle($this->_mh, $entry['handle']);
            // "lazy" futures are only sent once the pool has many requests.
            if ($entry['request']['future'] !== 'lazy') {
                do {
                    $mrc = curl_multi_exec($this->_mh, $this->active);
                } while ($mrc === CURLM_CALL_MULTI_PERFORM);
                $this->processMessages();
            }
        }
    }

    private function removeProcessed($id)
    {
        if (isset($this->handles[$id])) {
            curl_multi_remove_handle(
                $this->_mh,
                $this->handles[$id]['handle']
            );
            curl_close($this->handles[$id]['handle']);
            unset($this->handles[$id], $this->delays[$id]);
        }
    }

    /**
     * Cancels a handle from sending and removes references to it.
     *
     * @param int $id Handle ID to cancel and remove.
     *
     * @return bool True on success, false on failure.
     */
    private function cancel($id)
    {
        // Cannot cancel if it has been processed.
        if (!isset($this->handles[$id])) {
            return false;
        }

        $handle = $this->handles[$id]['handle'];
        unset($this->delays[$id], $this->handles[$id]);
        curl_multi_remove_handle($this->_mh, $handle);
        curl_close($handle);

        return true;
    }

    private function addDelays()
    {
        $currentTime = microtime(true);

        foreach ($this->delays as $id => $delay) {
            if ($currentTime >= $delay) {
                unset($this->delays[$id]);
                curl_multi_add_handle(
                    $this->_mh,
                    $this->handles[$id]['handle']
                );
            }
        }
    }

    private function processMessages()
    {
        while ($done = curl_multi_info_read($this->_mh)) {
            $id = (int) $done['handle'];

            if (!isset($this->handles[$id])) {
                // Probably was cancelled.
                continue;
            }

            $entry = $this->handles[$id];
            $entry['response']['transfer_stats'] = curl_getinfo($done['handle']);

            if ($done['result'] !== CURLM_OK) {
                $entry['response']['curl']['errno'] = $done['result'];
                $entry['response']['curl']['error'] = curl_error($done['handle']);
            }

            $result = CurlFactory::createResponse(
                $this,
                $entry['request'],
                $entry['response'],
                $entry['headers'],
                $entry['body']
            );

            $this->removeProcessed($id);
            $entry['deferred']->resolve($result);
        }
    }
}
<?php
namespace GuzzleHttp\Ring\Client;

/**
 * Client specific utility functions.
 */
class ClientUtils
{
    /**
     * Returns the default cacert bundle for the current system.
     *
     * First, the openssl.cafile and curl.cainfo php.ini settings are checked.
     * If those settings are not configured, then the common locations for
     * bundles found on Red Hat, CentOS, Fedora, Ubuntu, Debian, FreeBSD, OS X
     * and Windows are checked. If any of these file locations are found on
     * disk, they will be utilized.
     *
     * Note: the result of this function is cached for subsequent calls.
     *
     * @return string
     * @throws \RuntimeException if no bundle can be found.
     */
    public static function getDefaultCaBundle()
    {
        static $cached = null;
        static $cafiles = [
            // Red Hat, CentOS, Fedora (provided by the ca-certificates package)
            '/etc/pki/tls/certs/ca-bundle.crt',
            // Ubuntu, Debian (provided by the ca-certificates package)
            '/etc/ssl/certs/ca-certificates.crt',
            // FreeBSD (provided by the ca_root_nss package)
            '/usr/local/share/certs/ca-root-nss.crt',
            // OS X provided by homebrew (using the default path)
            '/usr/local/etc/openssl/cert.pem',
            // Windows?
            'C:\\windows\\system32\\curl-ca-bundle.crt',
            'C:\\windows\\curl-ca-bundle.crt',
        ];

        if ($cached) {
            return $cached;
        }

        if ($ca = ini_get('openssl.cafile')) {
            return $cached = $ca;
        }

        if ($ca = ini_get('curl.cainfo')) {
            return $cached = $ca;
        }

        foreach ($cafiles as $filename) {
            if (file_exists($filename)) {
                return $cached = $filename;
            }
        }

        throw new \RuntimeException(self::CA_ERR);
    }

    const CA_ERR = "
No system CA bundle could be found in any of the the common system locations.
PHP versions earlier than 5.6 are not properly configured to use the system's
CA bundle by default. In order to verify peer certificates, you will need to
supply the path on disk to a certificate bundle to the 'verify' request
option: http://docs.guzzlephp.org/en/5.3/clients.html#verify. If you do not
need a specific certificate bundle, then Mozilla provides a commonly used CA
bundle which can be downloaded here (provided by the maintainer of cURL):
https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt. Once
you have a CA bundle available on disk, you can set the 'openssl.cafile' PHP
ini setting to point to the path to the file, allowing you to omit the 'verify'
request option. See http://curl.haxx.se/docs/sslcerts.html for more
information.";
}
<?php
namespace GuzzleHttp\Ring\Client;

use GuzzleHttp\Ring\Future\CompletedFutureArray;
use GuzzleHttp\Ring\Core;

/**
 * HTTP handler that uses cURL easy handles as a transport layer.
 *
 * Requires PHP 5.5+
 *
 * When using the CurlHandler, custom curl options can be specified as an
 * associative array of curl option constants mapping to values in the
 * **curl** key of the "client" key of the request.
 */
class CurlHandler
{
    /** @var callable */
    private $factory;

    /** @var array Array of curl easy handles */
    private $handles = [];

    /** @var array Array of owned curl easy handles */
    private $ownedHandles = [];

    /** @var int Total number of idle handles to keep in cache */
    private $maxHandles;

    /**
     * Accepts an associative array of options:
     *
     * - factory: Optional callable factory used to create cURL handles.
     *   The callable is passed a request hash when invoked, and returns an
     *   array of the curl handle, headers resource, and body resource.
     * - max_handles: Maximum number of idle handles (defaults to 5).
     *
     * @param array $options Array of options to use with the handler
     */
    public function __construct(array $options = [])
    {
        $this->handles = $this->ownedHandles = [];
        $this->factory = isset($options['handle_factory'])
            ? $options['handle_factory']
            : new CurlFactory();
        $this->maxHandles = isset($options['max_handles'])
            ? $options['max_handles']
            : 5;
    }

    public function __destruct()
    {
        foreach ($this->handles as $handle) {
            if (is_resource($handle)) {
                curl_close($handle);
            }
        }
    }

    /**
     * @param array $request
     *
     * @return CompletedFutureArray
     */
    public function __invoke(array $request)
    {
        return new CompletedFutureArray(
            $this->_invokeAsArray($request)
        );
    }

    /**
     * @internal
     *
     * @param array $request
     *
     * @return array
     */
    public function _invokeAsArray(array $request)
    {
        $factory = $this->factory;

        // Ensure headers are by reference. They're updated elsewhere.
        $result = $factory($request, $this->checkoutEasyHandle());
        $h = $result[0];
        $hd =& $result[1];
        $bd = $result[2];
        Core::doSleep($request);
        curl_exec($h);
        $response = ['transfer_stats' => curl_getinfo($h)];
        $response['curl']['error'] = curl_error($h);
        $response['curl']['errno'] = curl_errno($h);
        $response['transfer_stats'] = array_merge($response['transfer_stats'], $response['curl']);
        $this->releaseEasyHandle($h);

        return CurlFactory::createResponse([$this, '_invokeAsArray'], $request, $response, $hd, $bd);
    }

    private function checkoutEasyHandle()
    {
        // Find an unused handle in the cache
        if (false !== ($key = array_search(false, $this->ownedHandles, true))) {
            $this->ownedHandles[$key] = true;
            return $this->handles[$key];
        }

        // Add a new handle
        $handle = curl_init();
        $id = (int) $handle;
        $this->handles[$id] = $handle;
        $this->ownedHandles[$id] = true;

        return $handle;
    }

    private function releaseEasyHandle($handle)
    {
        $id = (int) $handle;
        if (count($this->ownedHandles) > $this->maxHandles) {
            curl_close($this->handles[$id]);
            unset($this->handles[$id], $this->ownedHandles[$id]);
        } else {
            // curl_reset doesn't clear these out for some reason
            static $unsetValues = [
                CURLOPT_HEADERFUNCTION   => null,
                CURLOPT_WRITEFUNCTION    => null,
                CURLOPT_READFUNCTION     => null,
                CURLOPT_PROGRESSFUNCTION => null,
            ];
            curl_setopt_array($handle, $unsetValues);
            curl_reset($handle);
            $this->ownedHandles[$id] = false;
        }
    }
}
<?php
namespace GuzzleHttp\Ring\Client;

use GuzzleHttp\Ring\Core;
use GuzzleHttp\Ring\Future\CompletedFutureArray;
use GuzzleHttp\Ring\Future\FutureArrayInterface;

/**
 * Ring handler that returns a canned response or evaluated function result.
 */
class MockHandler
{
    /** @var callable|array|FutureArrayInterface */
    private $result;

    /**
     * Provide an array or future to always return the same value. Provide a
     * callable that accepts a request object and returns an array or future
     * to dynamically create a response.
     *
     * @param array|FutureArrayInterface|callable $result Mock return value.
     */
    public function __construct($result)
    {
        $this->result = $result;
    }

    public function __invoke(array $request)
    {
        Core::doSleep($request);
        $response = is_callable($this->result)
            ? call_user_func($this->result, $request)
            : $this->result;

        if (is_array($response)) {
            $response = new CompletedFutureArray($response + [
                'status'        => null,
                'body'          => null,
                'headers'       => [],
                'reason'        => null,
                'effective_url' => null,
            ]);
        } elseif (!$response instanceof FutureArrayInterface) {
            throw new \InvalidArgumentException(
                'Response must be an array or FutureArrayInterface. Found '
                . Core::describeType($request)
            );
        }

        return $response;
    }
}
<?php
namespace GuzzleHttp\Ring\Client;

use GuzzleHttp\Ring\Core;
use GuzzleHttp\Ring\Exception\ConnectException;
use GuzzleHttp\Ring\Exception\RingException;
use GuzzleHttp\Ring\Future\CompletedFutureArray;
use GuzzleHttp\Stream\InflateStream;
use GuzzleHttp\Stream\StreamInterface;
use GuzzleHttp\Stream\Stream;
use GuzzleHttp\Stream\Utils;

/**
 * RingPHP client handler that uses PHP's HTTP stream wrapper.
 */
class StreamHandler
{
    private $options;
    private $lastHeaders;

    public function __construct(array $options = [])
    {
        $this->options = $options;
    }

    public function __invoke(array $request)
    {
        $url = Core::url($request);
        Core::doSleep($request);

        try {
            // Does not support the expect header.
            $request = Core::removeHeader($request, 'Expect');
            $stream = $this->createStream($url, $request);
            return $this->createResponse($request, $url, $stream);
        } catch (RingException $e) {
            return $this->createErrorResponse($url, $e);
        }
    }

    private function createResponse(array $request, $url, $stream)
    {
        $hdrs = $this->lastHeaders;
        $this->lastHeaders = null;
        $parts = explode(' ', array_shift($hdrs), 3);
        $response = [
            'version'        => substr($parts[0], 5),
            'status'         => $parts[1],
            'reason'         => isset($parts[2]) ? $parts[2] : null,
            'headers'        => Core::headersFromLines($hdrs),
            'effective_url'  => $url,
        ];

        $stream = $this->checkDecode($request, $response, $stream);

        // If not streaming, then drain the response into a stream.
        if (empty($request['client']['stream'])) {
            $dest = isset($request['client']['save_to'])
                ? $request['client']['save_to']
                : fopen('php://temp', 'r+');
            $stream = $this->drain($stream, $dest);
        }

        $response['body'] = $stream;

        return new CompletedFutureArray($response);
    }

    private function checkDecode(array $request, array $response, $stream)
    {
        // Automatically decode responses when instructed.
        if (!empty($request['client']['decode_content'])) {
            switch (Core::firstHeader($response, 'Content-Encoding', true)) {
                case 'gzip':
                case 'deflate':
                    $stream = new InflateStream(Stream::factory($stream));
                    break;
            }
        }

        return $stream;
    }

    /**
     * Drains the stream into the "save_to" client option.
     *
     * @param resource                        $stream
     * @param string|resource|StreamInterface $dest
     *
     * @return Stream
     * @throws \RuntimeException when the save_to option is invalid.
     */
    private function drain($stream, $dest)
    {
        if (is_resource($stream)) {
            if (!is_resource($dest)) {
                $stream = Stream::factory($stream);
            } else {
                stream_copy_to_stream($stream, $dest);
                fclose($stream);
                rewind($dest);
                return $dest;
            }
        }

        // Stream the response into the destination stream
        $dest = is_string($dest)
            ? new Stream(Utils::open($dest, 'r+'))
            : Stream::factory($dest);

        Utils::copyToStream($stream, $dest);
        $dest->seek(0);
        $stream->close();

        return $dest;
    }

    /**
     * Creates an error response for the given stream.
     *
     * @param string        $url
     * @param RingException $e
     *
     * @return array
     */
    private function createErrorResponse($url, RingException $e)
    {
        // Determine if the error was a networking error.
        $message = $e->getMessage();

        // This list can probably get more comprehensive.
        if (strpos($message, 'getaddrinfo') // DNS lookup failed
            || strpos($message, 'Connection refused')
        ) {
            $e = new ConnectException($e->getMessage(), 0, $e);
        }

        return new CompletedFutureArray([
            'status'        => null,
            'body'          => null,
            'headers'       => [],
            'effective_url' => $url,
            'error'         => $e
        ]);
    }

    /**
     * Create a resource and check to ensure it was created successfully
     *
     * @param callable $callback Callable that returns stream resource
     *
     * @return resource
     * @throws \RuntimeException on error
     */
    private function createResource(callable $callback)
    {
        $errors = null;
        set_error_handler(function ($_, $msg, $file, $line) use (&$errors) {
            $errors[] = [
                'message' => $msg,
                'file'    => $file,
                'line'    => $line
            ];
            return true;
        });

        $resource = $callback();
        restore_error_handler();

        if (!$resource) {
            $message = 'Error creating resource: ';
            foreach ($errors as $err) {
                foreach ($err as $key => $value) {
                    $message .= "[$key] $value" . PHP_EOL;
                }
            }
            throw new RingException(trim($message));
        }

        return $resource;
    }

    private function createStream($url, array $request)
    {
        static $methods;
        if (!$methods) {
            $methods = array_flip(get_class_methods(__CLASS__));
        }

        // HTTP/1.1 streams using the PHP stream wrapper require a
        // Connection: close header
        if ((!isset($request['version']) || $request['version'] == '1.1')
            && !Core::hasHeader($request, 'Connection')
        ) {
            $request['headers']['Connection'] = ['close'];
        }

        // Ensure SSL is verified by default
        if (!isset($request['client']['verify'])) {
            $request['client']['verify'] = true;
        }

        $params = [];
        $options = $this->getDefaultOptions($request);

        if (isset($request['client'])) {
            foreach ($request['client'] as $key => $value) {
                $method = "add_{$key}";
                if (isset($methods[$method])) {
                    $this->{$method}($request, $options, $value, $params);
                }
            }
        }

        return $this->createStreamResource(
            $url,
            $request,
            $options,
            $this->createContext($request, $options, $params)
        );
    }

    private function getDefaultOptions(array $request)
    {
        $headers = "";
        foreach ($request['headers'] as $name => $value) {
            foreach ((array) $value as $val) {
                $headers .= "$name: $val\r\n";
            }
        }

        $context = [
            'http' => [
                'method'           => $request['http_method'],
                'header'           => $headers,
                'protocol_version' => isset($request['version']) ? $request['version'] : 1.1,
                'ignore_errors'    => true,
                'follow_location'  => 0,
            ],
        ];

        $body = Core::body($request);
        if (isset($body)) {
            $context['http']['content'] = $body;
            // Prevent the HTTP handler from adding a Content-Type header.
            if (!Core::hasHeader($request, 'Content-Type')) {
                $context['http']['header'] .= "Content-Type:\r\n";
            }
        }

        $context['http']['header'] = rtrim($context['http']['header']);

        return $context;
    }

    private function add_proxy(array $request, &$options, $value, &$params)
    {
        if (!is_array($value)) {
            $options['http']['proxy'] = $value;
        } else {
            $scheme = isset($request['scheme']) ? $request['scheme'] : 'http';
            if (isset($value[$scheme])) {
                $options['http']['proxy'] = $value[$scheme];
            }
        }
    }

    private function add_timeout(array $request, &$options, $value, &$params)
    {
        $options['http']['timeout'] = $value;
    }

    private function add_verify(array $request, &$options, $value, &$params)
    {
        if ($value === true) {
            // PHP 5.6 or greater will find the system cert by default. When
            // < 5.6, use the Guzzle bundled cacert.
            if (PHP_VERSION_ID < 50600) {
                $options['ssl']['cafile'] = ClientUtils::getDefaultCaBundle();
            }
        } elseif (is_string($value)) {
            $options['ssl']['cafile'] = $value;
            if (!file_exists($value)) {
                throw new RingException("SSL CA bundle not found: $value");
            }
        } elseif ($value === false) {
            $options['ssl']['verify_peer'] = false;
            $options['ssl']['allow_self_signed'] = true;
            return;
        } else {
            throw new RingException('Invalid verify request option');
        }

        $options['ssl']['verify_peer'] = true;
        $options['ssl']['allow_self_signed'] = false;
    }

    private function add_cert(array $request, &$options, $value, &$params)
    {
        if (is_array($value)) {
            $options['ssl']['passphrase'] = $value[1];
            $value = $value[0];
        }

        if (!file_exists($value)) {
            throw new RingException("SSL certificate not found: {$value}");
        }

        $options['ssl']['local_cert'] = $value;
    }

    private function add_progress(array $request, &$options, $value, &$params)
    {
        $fn = function ($code, $_1, $_2, $_3, $transferred, $total) use ($value) {
            if ($code == STREAM_NOTIFY_PROGRESS) {
                $value($total, $transferred, null, null);
            }
        };

        // Wrap the existing function if needed.
        $params['notification'] = isset($params['notification'])
            ? Core::callArray([$params['notification'], $fn])
            : $fn;
    }

    private function add_debug(array $request, &$options, $value, &$params)
    {
        if ($value === false) {
            return;
        }

        static $map = [
            STREAM_NOTIFY_CONNECT       => 'CONNECT',
            STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED',
            STREAM_NOTIFY_AUTH_RESULT   => 'AUTH_RESULT',
            STREAM_NOTIFY_MIME_TYPE_IS  => 'MIME_TYPE_IS',
            STREAM_NOTIFY_FILE_SIZE_IS  => 'FILE_SIZE_IS',
            STREAM_NOTIFY_REDIRECTED    => 'REDIRECTED',
            STREAM_NOTIFY_PROGRESS      => 'PROGRESS',
            STREAM_NOTIFY_FAILURE       => 'FAILURE',
            STREAM_NOTIFY_COMPLETED     => 'COMPLETED',
            STREAM_NOTIFY_RESOLVE       => 'RESOLVE',
        ];

        static $args = ['severity', 'message', 'message_code',
            'bytes_transferred', 'bytes_max'];

        $value = Core::getDebugResource($value);
        $ident = $request['http_method'] . ' ' . Core::url($request);
        $fn = function () use ($ident, $value, $map, $args) {
            $passed = func_get_args();
            $code = array_shift($passed);
            fprintf($value, '<%s> [%s] ', $ident, $map[$code]);
            foreach (array_filter($passed) as $i => $v) {
                fwrite($value, $args[$i] . ': "' . $v . '" ');
            }
            fwrite($value, "\n");
        };

        // Wrap the existing function if needed.
        $params['notification'] = isset($params['notification'])
            ? Core::callArray([$params['notification'], $fn])
            : $fn;
    }

    private function applyCustomOptions(array $request, array &$options)
    {
        if (!isset($request['client']['stream_context'])) {
            return;
        }

        if (!is_array($request['client']['stream_context'])) {
            throw new RingException('stream_context must be an array');
        }

        $options = array_replace_recursive(
            $options,
            $request['client']['stream_context']
        );
    }

    private function createContext(array $request, array $options, array $params)
    {
        $this->applyCustomOptions($request, $options);
        return $this->createResource(
            function () use ($request, $options, $params) {
                return stream_context_create($options, $params);
            },
            $request,
            $options
        );
    }

    private function createStreamResource(
        $url,
        array $request,
        array $options,
        $context
    ) {
        return $this->createResource(
            function () use ($url, $context) {
                if (false === strpos($url, 'http')) {
                    trigger_error("URL is invalid: {$url}", E_USER_WARNING);
                    return null;
                }
                $resource = fopen($url, 'r', null, $context);
                $this->lastHeaders = $http_response_header;
                return $resource;
            },
            $request,
            $options
        );
    }
}
<?php
namespace GuzzleHttp\Ring\Client;

/**
 * Provides basic middleware wrappers.
 *
 * If a middleware is more complex than a few lines of code, then it should
 * be implemented in a class rather than a static method.
 */
class Middleware
{
    /**
     * Sends future requests to a future compatible handler while sending all
     * other requests to a default handler.
     *
     * When the "future" option is not provided on a request, any future responses
     * are automatically converted to synchronous responses and block.
     *
     * @param callable $default Handler used for non-streaming responses
     * @param callable $future  Handler used for future responses
     *
     * @return callable Returns the composed handler.
     */
    public static function wrapFuture(
        callable $default,
        callable $future
    ) {
        return function (array $request) use ($default, $future) {
            return empty($request['client']['future'])
                ? $default($request)
                : $future($request);
        };
    }

    /**
     * Sends streaming requests to a streaming compatible handler while sendin
     * all other requests to a default handler.
     *
     * This, for example, could be useful for taking advantage of the
     * performance benefits of curl while still supporting true streaming
     * through the StreamHandler.
     *
     * @param callable $default   Handler used for non-streaming responses
     * @param callable $streaming Handler used for streaming responses
     *
     * @return callable Returns the composed handler.
     */
    public static function wrapStreaming(
        callable $default,
        callable $streaming
    ) {
        return function (array $request) use ($default, $streaming) {
            return empty($request['client']['stream'])
                ? $default($request)
                : $streaming($request);
        };
    }
}
<?php
namespace GuzzleHttp\Ring;

use GuzzleHttp\Stream\StreamInterface;
use GuzzleHttp\Ring\Future\FutureArrayInterface;
use GuzzleHttp\Ring\Future\FutureArray;

/**
 * Provides core functionality of Ring handlers and middleware.
 */
class Core
{
    /**
     * Returns a function that calls all of the provided functions, in order,
     * passing the arguments provided to the composed function to each function.
     *
     * @param callable[] $functions Array of functions to proxy to.
     *
     * @return callable
     */
    public static function callArray(array $functions)
    {
        return function () use ($functions) {
            $args = func_get_args();
            foreach ($functions as $fn) {
                call_user_func_array($fn, $args);
            }
        };
    }

    /**
     * Gets an array of header line values from a message for a specific header
     *
     * This method searches through the "headers" key of a message for a header
     * using a case-insensitive search.
     *
     * @param array  $message Request or response hash.
     * @param string $header  Header to retrieve
     *
     * @return array
     */
    public static function headerLines($message, $header)
    {
        $result = [];

        if (!empty($message['headers'])) {
            foreach ($message['headers'] as $name => $value) {
                if (!strcasecmp($name, $header)) {
                    $result = array_merge($result, $value);
                }
            }
        }

        return $result;
    }

    /**
     * Gets a header value from a message as a string or null
     *
     * This method searches through the "headers" key of a message for a header
     * using a case-insensitive search. The lines of the header are imploded
     * using commas into a single string return value.
     *
     * @param array  $message Request or response hash.
     * @param string $header  Header to retrieve
     *
     * @return string|null Returns the header string if found, or null if not.
     */
    public static function header($message, $header)
    {
        $match = self::headerLines($message, $header);
        return $match ? implode(', ', $match) : null;
    }

    /**
     * Returns the first header value from a message as a string or null. If
     * a header line contains multiple values separated by a comma, then this
     * function will return the first value in the list.
     *
     * @param array  $message Request or response hash.
     * @param string $header  Header to retrieve
     *
     * @return string|null Returns the value as a string if found.
     */
    public static function firstHeader($message, $header)
    {
        if (!empty($message['headers'])) {
            foreach ($message['headers'] as $name => $value) {
                if (!strcasecmp($name, $header)) {
                    // Return the match itself if it is a single value.
                    $pos = strpos($value[0], ',');
                    return $pos ? substr($value[0], 0, $pos) : $value[0];
                }
            }
        }

        return null;
    }

    /**
     * Returns true if a message has the provided case-insensitive header.
     *
     * @param array  $message Request or response hash.
     * @param string $header  Header to check
     *
     * @return bool
     */
    public static function hasHeader($message, $header)
    {
        if (!empty($message['headers'])) {
            foreach ($message['headers'] as $name => $value) {
                if (!strcasecmp($name, $header)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Parses an array of header lines into an associative array of headers.
     *
     * @param array $lines Header lines array of strings in the following
     *                     format: "Name: Value"
     * @return array
     */
    public static function headersFromLines($lines)
    {
        $headers = [];

        foreach ($lines as $line) {
            $parts = explode(':', $line, 2);
            $headers[trim($parts[0])][] = isset($parts[1])
                ? trim($parts[1])
                : null;
        }

        return $headers;
    }

    /**
     * Removes a header from a message using a case-insensitive comparison.
     *
     * @param array  $message Message that contains 'headers'
     * @param string $header  Header to remove
     *
     * @return array
     */
    public static function removeHeader(array $message, $header)
    {
        if (isset($message['headers'])) {
            foreach (array_keys($message['headers']) as $key) {
                if (!strcasecmp($header, $key)) {
                    unset($message['headers'][$key]);
                }
            }
        }

        return $message;
    }

    /**
     * Replaces any existing case insensitive headers with the given value.
     *
     * @param array  $message Message that contains 'headers'
     * @param string $header  Header to set.
     * @param array  $value   Value to set.
     *
     * @return array
     */
    public static function setHeader(array $message, $header, array $value)
    {
        $message = self::removeHeader($message, $header);
        $message['headers'][$header] = $value;

        return $message;
    }

    /**
     * Creates a URL string from a request.
     *
     * If the "url" key is present on the request, it is returned, otherwise
     * the url is built up based on the scheme, host, uri, and query_string
     * request values.
     *
     * @param array $request Request to get the URL from
     *
     * @return string Returns the request URL as a string.
     * @throws \InvalidArgumentException if no Host header is present.
     */
    public static function url(array $request)
    {
        if (isset($request['url'])) {
            return $request['url'];
        }

        $uri = (isset($request['scheme'])
                ? $request['scheme'] : 'http') . '://';

        if ($host = self::header($request, 'host')) {
            $uri .= $host;
        } else {
            throw new \InvalidArgumentException('No Host header was provided');
        }

        if (isset($request['uri'])) {
            $uri .= $request['uri'];
        }

        if (isset($request['query_string'])) {
            $uri .= '?' . $request['query_string'];
        }

        return $uri;
    }

    /**
     * Reads the body of a message into a string.
     *
     * @param array|FutureArrayInterface $message Array containing a "body" key
     *
     * @return null|string Returns the body as a string or null if not set.
     * @throws \InvalidArgumentException if a request body is invalid.
     */
    public static function body($message)
    {
        if (!isset($message['body'])) {
            return null;
        }

        if ($message['body'] instanceof StreamInterface) {
            return (string) $message['body'];
        }

        switch (gettype($message['body'])) {
            case 'string':
                return $message['body'];
            case 'resource':
                return stream_get_contents($message['body']);
            case 'object':
                if ($message['body'] instanceof \Iterator) {
                    return implode('', iterator_to_array($message['body']));
                } elseif (method_exists($message['body'], '__toString')) {
                    return (string) $message['body'];
                }
            default:
                throw new \InvalidArgumentException('Invalid request body: '
                    . self::describeType($message['body']));
        }
    }

    /**
     * Rewind the body of the provided message if possible.
     *
     * @param array $message Message that contains a 'body' field.
     *
     * @return bool Returns true on success, false on failure
     */
    public static function rewindBody($message)
    {
        if ($message['body'] instanceof StreamInterface) {
            return $message['body']->seek(0);
        }

        if ($message['body'] instanceof \Generator) {
            return false;
        }

        if ($message['body'] instanceof \Iterator) {
            $message['body']->rewind();
            return true;
        }

        if (is_resource($message['body'])) {
            return rewind($message['body']);
        }

        return is_string($message['body'])
            || (is_object($message['body'])
                && method_exists($message['body'], '__toString'));
    }

    /**
     * Debug function used to describe the provided value type and class.
     *
     * @param mixed $input
     *
     * @return string Returns a string containing the type of the variable and
     *                if a class is provided, the class name.
     */
    public static function describeType($input)
    {
        switch (gettype($input)) {
            case 'object':
                return 'object(' . get_class($input) . ')';
            case 'array':
                return 'array(' . count($input) . ')';
            default:
                ob_start();
                var_dump($input);
                // normalize float vs double
                return str_replace('double(', 'float(', rtrim(ob_get_clean()));
        }
    }

    /**
     * Sleep for the specified amount of time specified in the request's
     * ['client']['delay'] option if present.
     *
     * This function should only be used when a non-blocking sleep is not
     * possible.
     *
     * @param array $request Request to sleep
     */
    public static function doSleep(array $request)
    {
        if (isset($request['client']['delay'])) {
            usleep($request['client']['delay'] * 1000);
        }
    }

    /**
     * Returns a proxied future that modifies the dereferenced value of another
     * future using a promise.
     *
     * @param FutureArrayInterface $future      Future to wrap with a new future
     * @param callable    $onFulfilled Invoked when the future fulfilled
     * @param callable    $onRejected  Invoked when the future rejected
     * @param callable    $onProgress  Invoked when the future progresses
     *
     * @return FutureArray
     */
    public static function proxy(
        FutureArrayInterface $future,
        callable $onFulfilled = null,
        callable $onRejected = null,
        callable $onProgress = null
    ) {
        return new FutureArray(
            $future->then($onFulfilled, $onRejected, $onProgress),
            [$future, 'wait'],
            [$future, 'cancel']
        );
    }

    /**
     * Returns a debug stream based on the provided variable.
     *
     * @param mixed $value Optional value
     *
     * @return resource
     */
    public static function getDebugResource($value = null)
    {
        if (is_resource($value)) {
            return $value;
        } elseif (defined('STDOUT')) {
            return STDOUT;
        } else {
            return fopen('php://output', 'w');
        }
    }
}
<?php

// autoload_namespaces.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'Prophecy\\' => array($vendorDir . '/phpspec/prophecy/src'),
    'HTMLPurifier' => array($vendorDir . '/ezyang/htmlpurifier/library'),
);
<?php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Autoload;

/**
 * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
 *
 *     $loader = new \Composer\Autoload\ClassLoader();
 *
 *     // register classes with namespaces
 *     $loader->add('Symfony\Component', __DIR__.'/component');
 *     $loader->add('Symfony',           __DIR__.'/framework');
 *
 *     // activate the autoloader
 *     $loader->register();
 *
 *     // to enable searching the include path (eg. for PEAR packages)
 *     $loader->setUseIncludePath(true);
 *
 * In this example, if you try to use a class in the Symfony\Component
 * namespace or one of its children (Symfony\Component\Console for instance),
 * the autoloader will first look for the class under the component/
 * directory, and it will then fallback to the framework/ directory if not
 * found before giving up.
 *
 * This class is loosely based on the Symfony UniversalClassLoader.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @see    http://www.php-fig.org/psr/psr-0/
 * @see    http://www.php-fig.org/psr/psr-4/
 */
class ClassLoader
{
    // PSR-4
    private $prefixLengthsPsr4 = array();
    private $prefixDirsPsr4 = array();
    private $fallbackDirsPsr4 = array();

    // PSR-0
    private $prefixesPsr0 = array();
    private $fallbackDirsPsr0 = array();

    private $useIncludePath = false;
    private $classMap = array();
    private $classMapAuthoritative = false;
    private $missingClasses = array();
    private $apcuPrefix;

    public function getPrefixes()
    {
        if (!empty($this->prefixesPsr0)) {
            return call_user_func_array('array_merge', $this->prefixesPsr0);
        }

        return array();
    }

    public function getPrefixesPsr4()
    {
        return $this->prefixDirsPsr4;
    }

    public function getFallbackDirs()
    {
        return $this->fallbackDirsPsr0;
    }

    public function getFallbackDirsPsr4()
    {
        return $this->fallbackDirsPsr4;
    }

    public function getClassMap()
    {
        return $this->classMap;
    }

    /**
     * @param array $classMap Class to filename map
     */
    public function addClassMap(array $classMap)
    {
        if ($this->classMap) {
            $this->classMap = array_merge($this->classMap, $classMap);
        } else {
            $this->classMap = $classMap;
        }
    }

    /**
     * Registers a set of PSR-0 directories for a given prefix, either
     * appending or prepending to the ones previously set for this prefix.
     *
     * @param string       $prefix  The prefix
     * @param array|string $paths   The PSR-0 root directories
     * @param bool         $prepend Whether to prepend the directories
     */
    public function add($prefix, $paths, $prepend = false)
    {
        if (!$prefix) {
            if ($prepend) {
                $this->fallbackDirsPsr0 = array_merge(
                    (array) $paths,
                    $this->fallbackDirsPsr0
                );
            } else {
                $this->fallbackDirsPsr0 = array_merge(
                    $this->fallbackDirsPsr0,
                    (array) $paths
                );
            }

            return;
        }

        $first = $prefix[0];
        if (!isset($this->prefixesPsr0[$first][$prefix])) {
            $this->prefixesPsr0[$first][$prefix] = (array) $paths;

            return;
        }
        if ($prepend) {
            $this->prefixesPsr0[$first][$prefix] = array_merge(
                (array) $paths,
                $this->prefixesPsr0[$first][$prefix]
            );
        } else {
            $this->prefixesPsr0[$first][$prefix] = array_merge(
                $this->prefixesPsr0[$first][$prefix],
                (array) $paths
            );
        }
    }

    /**
     * Registers a set of PSR-4 directories for a given namespace, either
     * appending or prepending to the ones previously set for this namespace.
     *
     * @param string       $prefix  The prefix/namespace, with trailing '\\'
     * @param array|string $paths   The PSR-4 base directories
     * @param bool         $prepend Whether to prepend the directories
     *
     * @throws \InvalidArgumentException
     */
    public function addPsr4($prefix, $paths, $prepend = false)
    {
        if (!$prefix) {
            // Register directories for the root namespace.
            if ($prepend) {
                $this->fallbackDirsPsr4 = array_merge(
                    (array) $paths,
                    $this->fallbackDirsPsr4
                );
            } else {
                $this->fallbackDirsPsr4 = array_merge(
                    $this->fallbackDirsPsr4,
                    (array) $paths
                );
            }
        } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
            // Register directories for a new namespace.
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = (array) $paths;
        } elseif ($prepend) {
            // Prepend directories for an already registered namespace.
            $this->prefixDirsPsr4[$prefix] = array_merge(
                (array) $paths,
                $this->prefixDirsPsr4[$prefix]
            );
        } else {
            // Append directories for an already registered namespace.
            $this->prefixDirsPsr4[$prefix] = array_merge(
                $this->prefixDirsPsr4[$prefix],
                (array) $paths
            );
        }
    }

    /**
     * Registers a set of PSR-0 directories for a given prefix,
     * replacing any others previously set for this prefix.
     *
     * @param string       $prefix The prefix
     * @param array|string $paths  The PSR-0 base directories
     */
    public function set($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr0 = (array) $paths;
        } else {
            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
        }
    }

    /**
     * Registers a set of PSR-4 directories for a given namespace,
     * replacing any others previously set for this namespace.
     *
     * @param string       $prefix The prefix/namespace, with trailing '\\'
     * @param array|string $paths  The PSR-4 base directories
     *
     * @throws \InvalidArgumentException
     */
    public function setPsr4($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr4 = (array) $paths;
        } else {
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = (array) $paths;
        }
    }

    /**
     * Turns on searching the include path for class files.
     *
     * @param bool $useIncludePath
     */
    public function setUseIncludePath($useIncludePath)
    {
        $this->useIncludePath = $useIncludePath;
    }

    /**
     * Can be used to check if the autoloader uses the include path to check
     * for classes.
     *
     * @return bool
     */
    public function getUseIncludePath()
    {
        return $this->useIncludePath;
    }

    /**
     * Turns off searching the prefix and fallback directories for classes
     * that have not been registered with the class map.
     *
     * @param bool $classMapAuthoritative
     */
    public function setClassMapAuthoritative($classMapAuthoritative)
    {
        $this->classMapAuthoritative = $classMapAuthoritative;
    }

    /**
     * Should class lookup fail if not found in the current class map?
     *
     * @return bool
     */
    public function isClassMapAuthoritative()
    {
        return $this->classMapAuthoritative;
    }

    /**
     * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
     *
     * @param string|null $apcuPrefix
     */
    public function setApcuPrefix($apcuPrefix)
    {
        $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
    }

    /**
     * The APCu prefix in use, or null if APCu caching is not enabled.
     *
     * @return string|null
     */
    public function getApcuPrefix()
    {
        return $this->apcuPrefix;
    }

    /**
     * Registers this instance as an autoloader.
     *
     * @param bool $prepend Whether to prepend the autoloader or not
     */
    public function register($prepend = false)
    {
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
    }

    /**
     * Unregisters this instance as an autoloader.
     */
    public function unregister()
    {
        spl_autoload_unregister(array($this, 'loadClass'));
    }

    /**
     * Loads the given class or interface.
     *
     * @param  string    $class The name of the class
     * @return bool|null True if loaded, null otherwise
     */
    public function loadClass($class)
    {
        if ($file = $this->findFile($class)) {
            includeFile($file);

            return true;
        }
    }

    /**
     * Finds the path to the file where the class is defined.
     *
     * @param string $class The name of the class
     *
     * @return string|false The path if found, false otherwise
     */
    public function findFile($class)
    {
        // class map lookup
        if (isset($this->classMap[$class])) {
            return $this->classMap[$class];
        }
        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
            return false;
        }
        if (null !== $this->apcuPrefix) {
            $file = apcu_fetch($this->apcuPrefix.$class, $hit);
            if ($hit) {
                return $file;
            }
        }

        $file = $this->findFileWithExtension($class, '.php');

        // Search for Hack files if we are running on HHVM
        if (false === $file && defined('HHVM_VERSION')) {
            $file = $this->findFileWithExtension($class, '.hh');
        }

        if (null !== $this->apcuPrefix) {
            apcu_add($this->apcuPrefix.$class, $file);
        }

        if (false === $file) {
            // Remember that this class does not exist.
            $this->missingClasses[$class] = true;
        }

        return $file;
    }

    private function findFileWithExtension($class, $ext)
    {
        // PSR-4 lookup
        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

        $first = $class[0];
        if (isset($this->prefixLengthsPsr4[$first])) {
            $subPath = $class;
            while (false !== $lastPos = strrpos($subPath, '\\')) {
                $subPath = substr($subPath, 0, $lastPos);
                $search = $subPath . '\\';
                if (isset($this->prefixDirsPsr4[$search])) {
                    $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
                    foreach ($this->prefixDirsPsr4[$search] as $dir) {
                        if (file_exists($file = $dir . $pathEnd)) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-4 fallback dirs
        foreach ($this->fallbackDirsPsr4 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
                return $file;
            }
        }

        // PSR-0 lookup
        if (false !== $pos = strrpos($class, '\\')) {
            // namespaced class name
            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
        } else {
            // PEAR-like class name
            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
        }

        if (isset($this->prefixesPsr0[$first])) {
            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
                if (0 === strpos($class, $prefix)) {
                    foreach ($dirs as $dir) {
                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-0 fallback dirs
        foreach ($this->fallbackDirsPsr0 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                return $file;
            }
        }

        // PSR-0 include paths.
        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
            return $file;
        }

        return false;
    }
}

/**
 * Scope isolated include.
 *
 * Prevents access to $this/self from included files.
 */
function includeFile($file)
{
    include $file;
}
<?php

// autoload_psr4.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'voku\\helper\\' => array($vendorDir . '/voku/php-hooks/src/voku/helper'),
    'phpDocumentor\\Reflection\\' => array($vendorDir . '/phpdocumentor/reflection-common/src', $vendorDir . '/phpdocumentor/reflection-docblock/src', $vendorDir . '/phpdocumentor/type-resolver/src'),
    'linways\\cli\\' => array($baseDir . '/src'),
    'com\\linways\\base\\' => array($vendorDir . '/linways/linways-base/src/com/linways/base'),
    'com\\linways\\' => array($vendorDir . '/linways/nucleus-core/src/com/linways'),
    'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'),
    'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
    'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
    'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'),
    'Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'),
    'Symfony\\Component\\Dotenv\\' => array($vendorDir . '/symfony/dotenv'),
    'Symfony\\Component\\Debug\\' => array($vendorDir . '/symfony/debug'),
    'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'),
    'Symfony\\Component\\Config\\' => array($vendorDir . '/symfony/config'),
    'React\\Promise\\' => array($vendorDir . '/react/promise/src'),
    'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
    'PhpAmqpLib\\' => array($vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib'),
    'Phinx\\' => array($vendorDir . '/robmorgan/phinx/src/Phinx'),
    'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
    'GuzzleHttp\\Stream\\' => array($vendorDir . '/guzzlehttp/streams/src'),
    'GuzzleHttp\\Ring\\' => array($vendorDir . '/guzzlehttp/ringphp/src'),
    'Firebase\\JWT\\' => array($vendorDir . '/firebase/php-jwt/src'),
    'Elasticsearch\\' => array($vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch'),
    'Doctrine\\Instantiator\\' => array($vendorDir . '/doctrine/instantiator/src/Doctrine/Instantiator'),
    'DeepCopy\\' => array($vendorDir . '/myclabs/deep-copy/src/DeepCopy'),
    'Cron\\' => array($vendorDir . '/dragonmantank/cron-expression/src/Cron'),
);
<?php

// autoload_classmap.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'Cron\\AbstractField' => $vendorDir . '/dragonmantank/cron-expression/src/Cron/AbstractField.php',
    'Cron\\CronExpression' => $vendorDir . '/dragonmantank/cron-expression/src/Cron/CronExpression.php',
    'Cron\\DayOfMonthField' => $vendorDir . '/dragonmantank/cron-expression/src/Cron/DayOfMonthField.php',
    'Cron\\DayOfWeekField' => $vendorDir . '/dragonmantank/cron-expression/src/Cron/DayOfWeekField.php',
    'Cron\\FieldFactory' => $vendorDir . '/dragonmantank/cron-expression/src/Cron/FieldFactory.php',
    'Cron\\FieldInterface' => $vendorDir . '/dragonmantank/cron-expression/src/Cron/FieldInterface.php',
    'Cron\\HoursField' => $vendorDir . '/dragonmantank/cron-expression/src/Cron/HoursField.php',
    'Cron\\MinutesField' => $vendorDir . '/dragonmantank/cron-expression/src/Cron/MinutesField.php',
    'Cron\\MonthField' => $vendorDir . '/dragonmantank/cron-expression/src/Cron/MonthField.php',
    'Cron\\YearField' => $vendorDir . '/dragonmantank/cron-expression/src/Cron/YearField.php',
    'DeepCopy\\DeepCopy' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/DeepCopy.php',
    'DeepCopy\\Exception\\CloneException' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Exception/CloneException.php',
    'DeepCopy\\Exception\\PropertyException' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Exception/PropertyException.php',
    'DeepCopy\\Filter\\Doctrine\\DoctrineCollectionFilter' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineCollectionFilter.php',
    'DeepCopy\\Filter\\Doctrine\\DoctrineEmptyCollectionFilter' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineEmptyCollectionFilter.php',
    'DeepCopy\\Filter\\Doctrine\\DoctrineProxyFilter' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineProxyFilter.php',
    'DeepCopy\\Filter\\Filter' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Filter/Filter.php',
    'DeepCopy\\Filter\\KeepFilter' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Filter/KeepFilter.php',
    'DeepCopy\\Filter\\ReplaceFilter' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Filter/ReplaceFilter.php',
    'DeepCopy\\Filter\\SetNullFilter' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Filter/SetNullFilter.php',
    'DeepCopy\\Matcher\\Doctrine\\DoctrineProxyMatcher' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Matcher/Doctrine/DoctrineProxyMatcher.php',
    'DeepCopy\\Matcher\\Matcher' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Matcher/Matcher.php',
    'DeepCopy\\Matcher\\PropertyMatcher' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyMatcher.php',
    'DeepCopy\\Matcher\\PropertyNameMatcher' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyNameMatcher.php',
    'DeepCopy\\Matcher\\PropertyTypeMatcher' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyTypeMatcher.php',
    'DeepCopy\\Reflection\\ReflectionHelper' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/Reflection/ReflectionHelper.php',
    'DeepCopy\\TypeFilter\\Date\\DateIntervalFilter' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/TypeFilter/Date/DateIntervalFilter.php',
    'DeepCopy\\TypeFilter\\ReplaceFilter' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/TypeFilter/ReplaceFilter.php',
    'DeepCopy\\TypeFilter\\ShallowCopyFilter' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/TypeFilter/ShallowCopyFilter.php',
    'DeepCopy\\TypeFilter\\Spl\\SplDoublyLinkedList' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/TypeFilter/Spl/SplDoublyLinkedList.php',
    'DeepCopy\\TypeFilter\\Spl\\SplDoublyLinkedListFilter' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/TypeFilter/Spl/SplDoublyLinkedListFilter.php',
    'DeepCopy\\TypeFilter\\TypeFilter' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/TypeFilter/TypeFilter.php',
    'DeepCopy\\TypeMatcher\\TypeMatcher' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/TypeMatcher/TypeMatcher.php',
    'Doctrine\\Instantiator\\Exception\\ExceptionInterface' => $vendorDir . '/doctrine/instantiator/src/Doctrine/Instantiator/Exception/ExceptionInterface.php',
    'Doctrine\\Instantiator\\Exception\\InvalidArgumentException' => $vendorDir . '/doctrine/instantiator/src/Doctrine/Instantiator/Exception/InvalidArgumentException.php',
    'Doctrine\\Instantiator\\Exception\\UnexpectedValueException' => $vendorDir . '/doctrine/instantiator/src/Doctrine/Instantiator/Exception/UnexpectedValueException.php',
    'Doctrine\\Instantiator\\Instantiator' => $vendorDir . '/doctrine/instantiator/src/Doctrine/Instantiator/Instantiator.php',
    'Doctrine\\Instantiator\\InstantiatorInterface' => $vendorDir . '/doctrine/instantiator/src/Doctrine/Instantiator/InstantiatorInterface.php',
    'Elasticsearch\\Client' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Client.php',
    'Elasticsearch\\ClientBuilder' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/ClientBuilder.php',
    'Elasticsearch\\Common\\EmptyLogger' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/EmptyLogger.php',
    'Elasticsearch\\Common\\Exceptions\\AlreadyExpiredException' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/AlreadyExpiredException.php',
    'Elasticsearch\\Common\\Exceptions\\BadMethodCallException' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/BadMethodCallException.php',
    'Elasticsearch\\Common\\Exceptions\\BadRequest400Exception' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/BadRequest400Exception.php',
    'Elasticsearch\\Common\\Exceptions\\ClientErrorResponseException' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/ClientErrorResponseException.php',
    'Elasticsearch\\Common\\Exceptions\\Conflict409Exception' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Conflict409Exception.php',
    'Elasticsearch\\Common\\Exceptions\\Curl\\CouldNotConnectToHost' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Curl/CouldNotConnectToHost.php',
    'Elasticsearch\\Common\\Exceptions\\Curl\\CouldNotResolveHostException' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Curl/CouldNotResolveHostException.php',
    'Elasticsearch\\Common\\Exceptions\\Curl\\OperationTimeoutException' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Curl/OperationTimeoutException.php',
    'Elasticsearch\\Common\\Exceptions\\ElasticsearchException' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/ElasticsearchException.php',
    'Elasticsearch\\Common\\Exceptions\\Forbidden403Exception' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Forbidden403Exception.php',
    'Elasticsearch\\Common\\Exceptions\\InvalidArgumentException' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/InvalidArgumentException.php',
    'Elasticsearch\\Common\\Exceptions\\MaxRetriesException' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/MaxRetriesException.php',
    'Elasticsearch\\Common\\Exceptions\\Missing404Exception' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Missing404Exception.php',
    'Elasticsearch\\Common\\Exceptions\\NoDocumentsToGetException' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/NoDocumentsToGetException.php',
    'Elasticsearch\\Common\\Exceptions\\NoNodesAvailableException' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/NoNodesAvailableException.php',
    'Elasticsearch\\Common\\Exceptions\\NoShardAvailableException' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/NoShardAvailableException.php',
    'Elasticsearch\\Common\\Exceptions\\RequestTimeout408Exception' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/RequestTimeout408Exception.php',
    'Elasticsearch\\Common\\Exceptions\\RoutingMissingException' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/RoutingMissingException.php',
    'Elasticsearch\\Common\\Exceptions\\RuntimeException' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/RuntimeException.php',
    'Elasticsearch\\Common\\Exceptions\\ScriptLangNotSupportedException' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/ScriptLangNotSupportedException.php',
    'Elasticsearch\\Common\\Exceptions\\Serializer\\JsonErrorException' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Serializer/JsonErrorException.php',
    'Elasticsearch\\Common\\Exceptions\\ServerErrorResponseException' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/ServerErrorResponseException.php',
    'Elasticsearch\\Common\\Exceptions\\TransportException' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/TransportException.php',
    'Elasticsearch\\Common\\Exceptions\\UnexpectedValueException' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/UnexpectedValueException.php',
    'Elasticsearch\\ConnectionPool\\AbstractConnectionPool' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/AbstractConnectionPool.php',
    'Elasticsearch\\ConnectionPool\\ConnectionPoolInterface' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/ConnectionPoolInterface.php',
    'Elasticsearch\\ConnectionPool\\Selectors\\RandomSelector' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/RandomSelector.php',
    'Elasticsearch\\ConnectionPool\\Selectors\\RoundRobinSelector' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/RoundRobinSelector.php',
    'Elasticsearch\\ConnectionPool\\Selectors\\SelectorInterface' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/SelectorInterface.php',
    'Elasticsearch\\ConnectionPool\\Selectors\\StickyRoundRobinSelector' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/StickyRoundRobinSelector.php',
    'Elasticsearch\\ConnectionPool\\SimpleConnectionPool' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/SimpleConnectionPool.php',
    'Elasticsearch\\ConnectionPool\\SniffingConnectionPool' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/SniffingConnectionPool.php',
    'Elasticsearch\\ConnectionPool\\StaticConnectionPool' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/StaticConnectionPool.php',
    'Elasticsearch\\ConnectionPool\\StaticNoPingConnectionPool' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/StaticNoPingConnectionPool.php',
    'Elasticsearch\\Connections\\Connection' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Connections/Connection.php',
    'Elasticsearch\\Connections\\ConnectionFactory' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Connections/ConnectionFactory.php',
    'Elasticsearch\\Connections\\ConnectionFactoryInterface' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Connections/ConnectionFactoryInterface.php',
    'Elasticsearch\\Connections\\ConnectionInterface' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Connections/ConnectionInterface.php',
    'Elasticsearch\\Endpoints\\AbstractEndpoint' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/AbstractEndpoint.php',
    'Elasticsearch\\Endpoints\\Bulk' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Bulk.php',
    'Elasticsearch\\Endpoints\\BulkEndpointInterface' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/BulkEndpointInterface.php',
    'Elasticsearch\\Endpoints\\Cat\\Aliases' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Aliases.php',
    'Elasticsearch\\Endpoints\\Cat\\Allocation' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Allocation.php',
    'Elasticsearch\\Endpoints\\Cat\\Count' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Count.php',
    'Elasticsearch\\Endpoints\\Cat\\Fielddata' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Fielddata.php',
    'Elasticsearch\\Endpoints\\Cat\\Health' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Health.php',
    'Elasticsearch\\Endpoints\\Cat\\Help' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Help.php',
    'Elasticsearch\\Endpoints\\Cat\\Indices' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Indices.php',
    'Elasticsearch\\Endpoints\\Cat\\Master' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Master.php',
    'Elasticsearch\\Endpoints\\Cat\\NodeAttrs' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/NodeAttrs.php',
    'Elasticsearch\\Endpoints\\Cat\\Nodes' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Nodes.php',
    'Elasticsearch\\Endpoints\\Cat\\PendingTasks' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/PendingTasks.php',
    'Elasticsearch\\Endpoints\\Cat\\Plugins' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Plugins.php',
    'Elasticsearch\\Endpoints\\Cat\\Recovery' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Recovery.php',
    'Elasticsearch\\Endpoints\\Cat\\Repositories' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Repositories.php',
    'Elasticsearch\\Endpoints\\Cat\\Segments' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Segments.php',
    'Elasticsearch\\Endpoints\\Cat\\Shards' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Shards.php',
    'Elasticsearch\\Endpoints\\Cat\\Snapshots' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Snapshots.php',
    'Elasticsearch\\Endpoints\\Cat\\Tasks' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Tasks.php',
    'Elasticsearch\\Endpoints\\Cat\\Templates' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Templates.php',
    'Elasticsearch\\Endpoints\\Cat\\ThreadPool' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/ThreadPool.php',
    'Elasticsearch\\Endpoints\\ClearScroll' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/ClearScroll.php',
    'Elasticsearch\\Endpoints\\Cluster\\AllocationExplain' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/AllocationExplain.php',
    'Elasticsearch\\Endpoints\\Cluster\\Health' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Health.php',
    'Elasticsearch\\Endpoints\\Cluster\\Nodes\\AbstractNodesEndpoint' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/AbstractNodesEndpoint.php',
    'Elasticsearch\\Endpoints\\Cluster\\Nodes\\HotThreads' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/HotThreads.php',
    'Elasticsearch\\Endpoints\\Cluster\\Nodes\\Info' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/Info.php',
    'Elasticsearch\\Endpoints\\Cluster\\Nodes\\Shutdown' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/Shutdown.php',
    'Elasticsearch\\Endpoints\\Cluster\\Nodes\\Stats' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/Stats.php',
    'Elasticsearch\\Endpoints\\Cluster\\PendingTasks' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/PendingTasks.php',
    'Elasticsearch\\Endpoints\\Cluster\\Reroute' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Reroute.php',
    'Elasticsearch\\Endpoints\\Cluster\\Settings\\Get' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Settings/Get.php',
    'Elasticsearch\\Endpoints\\Cluster\\Settings\\Put' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Settings/Put.php',
    'Elasticsearch\\Endpoints\\Cluster\\State' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/State.php',
    'Elasticsearch\\Endpoints\\Cluster\\Stats' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Stats.php',
    'Elasticsearch\\Endpoints\\Count' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Count.php',
    'Elasticsearch\\Endpoints\\CountPercolate' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/CountPercolate.php',
    'Elasticsearch\\Endpoints\\Create' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Create.php',
    'Elasticsearch\\Endpoints\\Delete' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Delete.php',
    'Elasticsearch\\Endpoints\\DeleteByQuery' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/DeleteByQuery.php',
    'Elasticsearch\\Endpoints\\Exists' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Exists.php',
    'Elasticsearch\\Endpoints\\Explain' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Explain.php',
    'Elasticsearch\\Endpoints\\FieldCaps' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/FieldCaps.php',
    'Elasticsearch\\Endpoints\\FieldStats' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/FieldStats.php',
    'Elasticsearch\\Endpoints\\Get' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Get.php',
    'Elasticsearch\\Endpoints\\Index' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Index.php',
    'Elasticsearch\\Endpoints\\Indices\\Alias\\AbstractAliasEndpoint' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/AbstractAliasEndpoint.php',
    'Elasticsearch\\Endpoints\\Indices\\Alias\\Delete' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/Delete.php',
    'Elasticsearch\\Endpoints\\Indices\\Alias\\Exists' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/Exists.php',
    'Elasticsearch\\Endpoints\\Indices\\Alias\\Get' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/Get.php',
    'Elasticsearch\\Endpoints\\Indices\\Alias\\Put' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/Put.php',
    'Elasticsearch\\Endpoints\\Indices\\Aliases\\Get' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Aliases/Get.php',
    'Elasticsearch\\Endpoints\\Indices\\Aliases\\Update' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Aliases/Update.php',
    'Elasticsearch\\Endpoints\\Indices\\Analyze' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Analyze.php',
    'Elasticsearch\\Endpoints\\Indices\\Cache\\Clear' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Cache/Clear.php',
    'Elasticsearch\\Endpoints\\Indices\\ClearCache' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/ClearCache.php',
    'Elasticsearch\\Endpoints\\Indices\\Close' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Close.php',
    'Elasticsearch\\Endpoints\\Indices\\Create' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Create.php',
    'Elasticsearch\\Endpoints\\Indices\\Delete' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Delete.php',
    'Elasticsearch\\Endpoints\\Indices\\Exists' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Exists.php',
    'Elasticsearch\\Endpoints\\Indices\\Exists\\Types' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Exists/Types.php',
    'Elasticsearch\\Endpoints\\Indices\\Field\\Get' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Field/Get.php',
    'Elasticsearch\\Endpoints\\Indices\\Flush' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Flush.php',
    'Elasticsearch\\Endpoints\\Indices\\ForceMerge' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/ForceMerge.php',
    'Elasticsearch\\Endpoints\\Indices\\Gateway\\Snapshot' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Gateway/Snapshot.php',
    'Elasticsearch\\Endpoints\\Indices\\Get' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Get.php',
    'Elasticsearch\\Endpoints\\Indices\\Mapping\\Delete' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Mapping/Delete.php',
    'Elasticsearch\\Endpoints\\Indices\\Mapping\\Get' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Mapping/Get.php',
    'Elasticsearch\\Endpoints\\Indices\\Mapping\\GetField' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Mapping/GetField.php',
    'Elasticsearch\\Endpoints\\Indices\\Mapping\\Put' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Mapping/Put.php',
    'Elasticsearch\\Endpoints\\Indices\\Open' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Open.php',
    'Elasticsearch\\Endpoints\\Indices\\Recovery' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Recovery.php',
    'Elasticsearch\\Endpoints\\Indices\\Refresh' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Refresh.php',
    'Elasticsearch\\Endpoints\\Indices\\Rollover' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Rollover.php',
    'Elasticsearch\\Endpoints\\Indices\\Seal' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Seal.php',
    'Elasticsearch\\Endpoints\\Indices\\Segments' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Segments.php',
    'Elasticsearch\\Endpoints\\Indices\\Settings\\Get' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Settings/Get.php',
    'Elasticsearch\\Endpoints\\Indices\\Settings\\Put' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Settings/Put.php',
    'Elasticsearch\\Endpoints\\Indices\\ShardStores' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/ShardStores.php',
    'Elasticsearch\\Endpoints\\Indices\\Shrink' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Shrink.php',
    'Elasticsearch\\Endpoints\\Indices\\Snapshotindex' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Snapshotindex.php',
    'Elasticsearch\\Endpoints\\Indices\\Stats' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Stats.php',
    'Elasticsearch\\Endpoints\\Indices\\Status' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Status.php',
    'Elasticsearch\\Endpoints\\Indices\\Template\\AbstractTemplateEndpoint' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/AbstractTemplateEndpoint.php',
    'Elasticsearch\\Endpoints\\Indices\\Template\\Delete' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/Delete.php',
    'Elasticsearch\\Endpoints\\Indices\\Template\\Exists' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/Exists.php',
    'Elasticsearch\\Endpoints\\Indices\\Template\\Get' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/Get.php',
    'Elasticsearch\\Endpoints\\Indices\\Template\\Put' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/Put.php',
    'Elasticsearch\\Endpoints\\Indices\\Type\\Exists' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Type/Exists.php',
    'Elasticsearch\\Endpoints\\Indices\\Upgrade\\Get' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Upgrade/Get.php',
    'Elasticsearch\\Endpoints\\Indices\\Upgrade\\Post' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Upgrade/Post.php',
    'Elasticsearch\\Endpoints\\Indices\\ValidateQuery' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/ValidateQuery.php',
    'Elasticsearch\\Endpoints\\Indices\\Validate\\Query' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Validate/Query.php',
    'Elasticsearch\\Endpoints\\Info' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Info.php',
    'Elasticsearch\\Endpoints\\Ingest\\Pipeline\\Delete' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ingest/Pipeline/Delete.php',
    'Elasticsearch\\Endpoints\\Ingest\\Pipeline\\Get' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ingest/Pipeline/Get.php',
    'Elasticsearch\\Endpoints\\Ingest\\Pipeline\\Put' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ingest/Pipeline/Put.php',
    'Elasticsearch\\Endpoints\\Ingest\\Simulate' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ingest/Simulate.php',
    'Elasticsearch\\Endpoints\\MPercolate' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/MPercolate.php',
    'Elasticsearch\\Endpoints\\MTermVectors' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/MTermVectors.php',
    'Elasticsearch\\Endpoints\\Mget' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Mget.php',
    'Elasticsearch\\Endpoints\\Msearch' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Msearch.php',
    'Elasticsearch\\Endpoints\\MsearchTemplate' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/MsearchTemplate.php',
    'Elasticsearch\\Endpoints\\Percolate' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Percolate.php',
    'Elasticsearch\\Endpoints\\Ping' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ping.php',
    'Elasticsearch\\Endpoints\\Reindex' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Reindex.php',
    'Elasticsearch\\Endpoints\\Remote\\Info' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Remote/Info.php',
    'Elasticsearch\\Endpoints\\RenderSearchTemplate' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/RenderSearchTemplate.php',
    'Elasticsearch\\Endpoints\\Script\\Delete' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Script/Delete.php',
    'Elasticsearch\\Endpoints\\Script\\Get' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Script/Get.php',
    'Elasticsearch\\Endpoints\\Script\\Put' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Script/Put.php',
    'Elasticsearch\\Endpoints\\Scroll' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Scroll.php',
    'Elasticsearch\\Endpoints\\Search' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Search.php',
    'Elasticsearch\\Endpoints\\SearchShards' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/SearchShards.php',
    'Elasticsearch\\Endpoints\\SearchTemplate' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/SearchTemplate.php',
    'Elasticsearch\\Endpoints\\Snapshot\\Create' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Create.php',
    'Elasticsearch\\Endpoints\\Snapshot\\Delete' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Delete.php',
    'Elasticsearch\\Endpoints\\Snapshot\\Get' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Get.php',
    'Elasticsearch\\Endpoints\\Snapshot\\Repository\\Create' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Repository/Create.php',
    'Elasticsearch\\Endpoints\\Snapshot\\Repository\\Delete' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Repository/Delete.php',
    'Elasticsearch\\Endpoints\\Snapshot\\Repository\\Get' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Repository/Get.php',
    'Elasticsearch\\Endpoints\\Snapshot\\Repository\\Verify' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Repository/Verify.php',
    'Elasticsearch\\Endpoints\\Snapshot\\Restore' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Restore.php',
    'Elasticsearch\\Endpoints\\Snapshot\\Status' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Status.php',
    'Elasticsearch\\Endpoints\\Source\\Get' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Source/Get.php',
    'Elasticsearch\\Endpoints\\Suggest' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Suggest.php',
    'Elasticsearch\\Endpoints\\Tasks\\Cancel' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Tasks/Cancel.php',
    'Elasticsearch\\Endpoints\\Tasks\\Get' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Tasks/Get.php',
    'Elasticsearch\\Endpoints\\Tasks\\TasksList' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Tasks/TasksList.php',
    'Elasticsearch\\Endpoints\\Template\\Delete' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Template/Delete.php',
    'Elasticsearch\\Endpoints\\Template\\Get' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Template/Get.php',
    'Elasticsearch\\Endpoints\\Template\\Put' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Template/Put.php',
    'Elasticsearch\\Endpoints\\TermVectors' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/TermVectors.php',
    'Elasticsearch\\Endpoints\\Update' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Update.php',
    'Elasticsearch\\Endpoints\\UpdateByQuery' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/UpdateByQuery.php',
    'Elasticsearch\\Helper\\Iterators\\SearchHitIterator' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Helper/Iterators/SearchHitIterator.php',
    'Elasticsearch\\Helper\\Iterators\\SearchResponseIterator' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Helper/Iterators/SearchResponseIterator.php',
    'Elasticsearch\\Namespaces\\AbstractNamespace' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/AbstractNamespace.php',
    'Elasticsearch\\Namespaces\\BooleanRequestWrapper' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/BooleanRequestWrapper.php',
    'Elasticsearch\\Namespaces\\CatNamespace' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/CatNamespace.php',
    'Elasticsearch\\Namespaces\\ClusterNamespace' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/ClusterNamespace.php',
    'Elasticsearch\\Namespaces\\IndicesNamespace' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/IndicesNamespace.php',
    'Elasticsearch\\Namespaces\\IngestNamespace' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/IngestNamespace.php',
    'Elasticsearch\\Namespaces\\NamespaceBuilderInterface' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/NamespaceBuilderInterface.php',
    'Elasticsearch\\Namespaces\\NodesNamespace' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/NodesNamespace.php',
    'Elasticsearch\\Namespaces\\RemoteNamespace' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/RemoteNamespace.php',
    'Elasticsearch\\Namespaces\\SnapshotNamespace' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/SnapshotNamespace.php',
    'Elasticsearch\\Namespaces\\TasksNamespace' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/TasksNamespace.php',
    'Elasticsearch\\Serializers\\ArrayToJSONSerializer' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/ArrayToJSONSerializer.php',
    'Elasticsearch\\Serializers\\EverythingToJSONSerializer' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/EverythingToJSONSerializer.php',
    'Elasticsearch\\Serializers\\SerializerInterface' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/SerializerInterface.php',
    'Elasticsearch\\Serializers\\SmartSerializer' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/SmartSerializer.php',
    'Elasticsearch\\Transport' => $vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch/Transport.php',
    'File_Iterator' => $vendorDir . '/phpunit/php-file-iterator/src/Iterator.php',
    'File_Iterator_Facade' => $vendorDir . '/phpunit/php-file-iterator/src/Facade.php',
    'File_Iterator_Factory' => $vendorDir . '/phpunit/php-file-iterator/src/Factory.php',
    'Firebase\\JWT\\BeforeValidException' => $vendorDir . '/firebase/php-jwt/src/BeforeValidException.php',
    'Firebase\\JWT\\ExpiredException' => $vendorDir . '/firebase/php-jwt/src/ExpiredException.php',
    'Firebase\\JWT\\JWT' => $vendorDir . '/firebase/php-jwt/src/JWT.php',
    'Firebase\\JWT\\SignatureInvalidException' => $vendorDir . '/firebase/php-jwt/src/SignatureInvalidException.php',
    'GuzzleHttp\\Ring\\Client\\ClientUtils' => $vendorDir . '/guzzlehttp/ringphp/src/Client/ClientUtils.php',
    'GuzzleHttp\\Ring\\Client\\CurlFactory' => $vendorDir . '/guzzlehttp/ringphp/src/Client/CurlFactory.php',
    'GuzzleHttp\\Ring\\Client\\CurlHandler' => $vendorDir . '/guzzlehttp/ringphp/src/Client/CurlHandler.php',
    'GuzzleHttp\\Ring\\Client\\CurlMultiHandler' => $vendorDir . '/guzzlehttp/ringphp/src/Client/CurlMultiHandler.php',
    'GuzzleHttp\\Ring\\Client\\Middleware' => $vendorDir . '/guzzlehttp/ringphp/src/Client/Middleware.php',
    'GuzzleHttp\\Ring\\Client\\MockHandler' => $vendorDir . '/guzzlehttp/ringphp/src/Client/MockHandler.php',
    'GuzzleHttp\\Ring\\Client\\StreamHandler' => $vendorDir . '/guzzlehttp/ringphp/src/Client/StreamHandler.php',
    'GuzzleHttp\\Ring\\Core' => $vendorDir . '/guzzlehttp/ringphp/src/Core.php',
    'GuzzleHttp\\Ring\\Exception\\CancelledException' => $vendorDir . '/guzzlehttp/ringphp/src/Exception/CancelledException.php',
    'GuzzleHttp\\Ring\\Exception\\CancelledFutureAccessException' => $vendorDir . '/guzzlehttp/ringphp/src/Exception/CancelledFutureAccessException.php',
    'GuzzleHttp\\Ring\\Exception\\ConnectException' => $vendorDir . '/guzzlehttp/ringphp/src/Exception/ConnectException.php',
    'GuzzleHttp\\Ring\\Exception\\RingException' => $vendorDir . '/guzzlehttp/ringphp/src/Exception/RingException.php',
    'GuzzleHttp\\Ring\\Future\\BaseFutureTrait' => $vendorDir . '/guzzlehttp/ringphp/src/Future/BaseFutureTrait.php',
    'GuzzleHttp\\Ring\\Future\\CompletedFutureArray' => $vendorDir . '/guzzlehttp/ringphp/src/Future/CompletedFutureArray.php',
    'GuzzleHttp\\Ring\\Future\\CompletedFutureValue' => $vendorDir . '/guzzlehttp/ringphp/src/Future/CompletedFutureValue.php',
    'GuzzleHttp\\Ring\\Future\\FutureArray' => $vendorDir . '/guzzlehttp/ringphp/src/Future/FutureArray.php',
    'GuzzleHttp\\Ring\\Future\\FutureArrayInterface' => $vendorDir . '/guzzlehttp/ringphp/src/Future/FutureArrayInterface.php',
    'GuzzleHttp\\Ring\\Future\\FutureInterface' => $vendorDir . '/guzzlehttp/ringphp/src/Future/FutureInterface.php',
    'GuzzleHttp\\Ring\\Future\\FutureValue' => $vendorDir . '/guzzlehttp/ringphp/src/Future/FutureValue.php',
    'GuzzleHttp\\Ring\\Future\\MagicFutureTrait' => $vendorDir . '/guzzlehttp/ringphp/src/Future/MagicFutureTrait.php',
    'GuzzleHttp\\Stream\\AppendStream' => $vendorDir . '/guzzlehttp/streams/src/AppendStream.php',
    'GuzzleHttp\\Stream\\AsyncReadStream' => $vendorDir . '/guzzlehttp/streams/src/AsyncReadStream.php',
    'GuzzleHttp\\Stream\\BufferStream' => $vendorDir . '/guzzlehttp/streams/src/BufferStream.php',
    'GuzzleHttp\\Stream\\CachingStream' => $vendorDir . '/guzzlehttp/streams/src/CachingStream.php',
    'GuzzleHttp\\Stream\\DroppingStream' => $vendorDir . '/guzzlehttp/streams/src/DroppingStream.php',
    'GuzzleHttp\\Stream\\Exception\\CannotAttachException' => $vendorDir . '/guzzlehttp/streams/src/Exception/CannotAttachException.php',
    'GuzzleHttp\\Stream\\Exception\\SeekException' => $vendorDir . '/guzzlehttp/streams/src/Exception/SeekException.php',
    'GuzzleHttp\\Stream\\FnStream' => $vendorDir . '/guzzlehttp/streams/src/FnStream.php',
    'GuzzleHttp\\Stream\\GuzzleStreamWrapper' => $vendorDir . '/guzzlehttp/streams/src/GuzzleStreamWrapper.php',
    'GuzzleHttp\\Stream\\InflateStream' => $vendorDir . '/guzzlehttp/streams/src/InflateStream.php',
    'GuzzleHttp\\Stream\\LazyOpenStream' => $vendorDir . '/guzzlehttp/streams/src/LazyOpenStream.php',
    'GuzzleHttp\\Stream\\LimitStream' => $vendorDir . '/guzzlehttp/streams/src/LimitStream.php',
    'GuzzleHttp\\Stream\\MetadataStreamInterface' => $vendorDir . '/guzzlehttp/streams/src/MetadataStreamInterface.php',
    'GuzzleHttp\\Stream\\NoSeekStream' => $vendorDir . '/guzzlehttp/streams/src/NoSeekStream.php',
    'GuzzleHttp\\Stream\\NullStream' => $vendorDir . '/guzzlehttp/streams/src/NullStream.php',
    'GuzzleHttp\\Stream\\PumpStream' => $vendorDir . '/guzzlehttp/streams/src/PumpStream.php',
    'GuzzleHttp\\Stream\\Stream' => $vendorDir . '/guzzlehttp/streams/src/Stream.php',
    'GuzzleHttp\\Stream\\StreamDecoratorTrait' => $vendorDir . '/guzzlehttp/streams/src/StreamDecoratorTrait.php',
    'GuzzleHttp\\Stream\\StreamInterface' => $vendorDir . '/guzzlehttp/streams/src/StreamInterface.php',
    'GuzzleHttp\\Stream\\Utils' => $vendorDir . '/guzzlehttp/streams/src/Utils.php',
    'HTMLPurifier' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier.php',
    'HTMLPurifier_Arborize' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Arborize.php',
    'HTMLPurifier_AttrCollections' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrCollections.php',
    'HTMLPurifier_AttrDef' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef.php',
    'HTMLPurifier_AttrDef_CSS' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS.php',
    'HTMLPurifier_AttrDef_CSS_AlphaValue' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/AlphaValue.php',
    'HTMLPurifier_AttrDef_CSS_Background' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Background.php',
    'HTMLPurifier_AttrDef_CSS_BackgroundPosition' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php',
    'HTMLPurifier_AttrDef_CSS_Border' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Border.php',
    'HTMLPurifier_AttrDef_CSS_Color' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Color.php',
    'HTMLPurifier_AttrDef_CSS_Composite' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Composite.php',
    'HTMLPurifier_AttrDef_CSS_DenyElementDecorator' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php',
    'HTMLPurifier_AttrDef_CSS_Filter' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Filter.php',
    'HTMLPurifier_AttrDef_CSS_Font' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Font.php',
    'HTMLPurifier_AttrDef_CSS_FontFamily' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/FontFamily.php',
    'HTMLPurifier_AttrDef_CSS_Ident' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Ident.php',
    'HTMLPurifier_AttrDef_CSS_ImportantDecorator' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php',
    'HTMLPurifier_AttrDef_CSS_Length' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Length.php',
    'HTMLPurifier_AttrDef_CSS_ListStyle' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ListStyle.php',
    'HTMLPurifier_AttrDef_CSS_Multiple' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Multiple.php',
    'HTMLPurifier_AttrDef_CSS_Number' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Number.php',
    'HTMLPurifier_AttrDef_CSS_Percentage' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Percentage.php',
    'HTMLPurifier_AttrDef_CSS_TextDecoration' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/TextDecoration.php',
    'HTMLPurifier_AttrDef_CSS_URI' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/URI.php',
    'HTMLPurifier_AttrDef_Clone' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Clone.php',
    'HTMLPurifier_AttrDef_Enum' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Enum.php',
    'HTMLPurifier_AttrDef_HTML_Bool' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Bool.php',
    'HTMLPurifier_AttrDef_HTML_Class' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Class.php',
    'HTMLPurifier_AttrDef_HTML_Color' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Color.php',
    'HTMLPurifier_AttrDef_HTML_FrameTarget' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/FrameTarget.php',
    'HTMLPurifier_AttrDef_HTML_ID' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/ID.php',
    'HTMLPurifier_AttrDef_HTML_Length' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Length.php',
    'HTMLPurifier_AttrDef_HTML_LinkTypes' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/LinkTypes.php',
    'HTMLPurifier_AttrDef_HTML_MultiLength' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/MultiLength.php',
    'HTMLPurifier_AttrDef_HTML_Nmtokens' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Nmtokens.php',
    'HTMLPurifier_AttrDef_HTML_Pixels' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Pixels.php',
    'HTMLPurifier_AttrDef_Integer' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Integer.php',
    'HTMLPurifier_AttrDef_Lang' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Lang.php',
    'HTMLPurifier_AttrDef_Switch' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Switch.php',
    'HTMLPurifier_AttrDef_Text' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Text.php',
    'HTMLPurifier_AttrDef_URI' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI.php',
    'HTMLPurifier_AttrDef_URI_Email' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email.php',
    'HTMLPurifier_AttrDef_URI_Email_SimpleCheck' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php',
    'HTMLPurifier_AttrDef_URI_Host' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Host.php',
    'HTMLPurifier_AttrDef_URI_IPv4' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv4.php',
    'HTMLPurifier_AttrDef_URI_IPv6' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv6.php',
    'HTMLPurifier_AttrTransform' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform.php',
    'HTMLPurifier_AttrTransform_Background' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Background.php',
    'HTMLPurifier_AttrTransform_BdoDir' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BdoDir.php',
    'HTMLPurifier_AttrTransform_BgColor' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BgColor.php',
    'HTMLPurifier_AttrTransform_BoolToCSS' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BoolToCSS.php',
    'HTMLPurifier_AttrTransform_Border' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Border.php',
    'HTMLPurifier_AttrTransform_EnumToCSS' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/EnumToCSS.php',
    'HTMLPurifier_AttrTransform_ImgRequired' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgRequired.php',
    'HTMLPurifier_AttrTransform_ImgSpace' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgSpace.php',
    'HTMLPurifier_AttrTransform_Input' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Input.php',
    'HTMLPurifier_AttrTransform_Lang' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Lang.php',
    'HTMLPurifier_AttrTransform_Length' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Length.php',
    'HTMLPurifier_AttrTransform_Name' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Name.php',
    'HTMLPurifier_AttrTransform_NameSync' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/NameSync.php',
    'HTMLPurifier_AttrTransform_Nofollow' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Nofollow.php',
    'HTMLPurifier_AttrTransform_SafeEmbed' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeEmbed.php',
    'HTMLPurifier_AttrTransform_SafeObject' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeObject.php',
    'HTMLPurifier_AttrTransform_SafeParam' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeParam.php',
    'HTMLPurifier_AttrTransform_ScriptRequired' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ScriptRequired.php',
    'HTMLPurifier_AttrTransform_TargetBlank' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetBlank.php',
    'HTMLPurifier_AttrTransform_TargetNoopener' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetNoopener.php',
    'HTMLPurifier_AttrTransform_TargetNoreferrer' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetNoreferrer.php',
    'HTMLPurifier_AttrTransform_Textarea' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Textarea.php',
    'HTMLPurifier_AttrTypes' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTypes.php',
    'HTMLPurifier_AttrValidator' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrValidator.php',
    'HTMLPurifier_Bootstrap' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Bootstrap.php',
    'HTMLPurifier_CSSDefinition' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/CSSDefinition.php',
    'HTMLPurifier_ChildDef' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef.php',
    'HTMLPurifier_ChildDef_Chameleon' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Chameleon.php',
    'HTMLPurifier_ChildDef_Custom' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Custom.php',
    'HTMLPurifier_ChildDef_Empty' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Empty.php',
    'HTMLPurifier_ChildDef_List' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/List.php',
    'HTMLPurifier_ChildDef_Optional' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Optional.php',
    'HTMLPurifier_ChildDef_Required' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Required.php',
    'HTMLPurifier_ChildDef_StrictBlockquote' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/StrictBlockquote.php',
    'HTMLPurifier_ChildDef_Table' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Table.php',
    'HTMLPurifier_Config' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Config.php',
    'HTMLPurifier_ConfigSchema' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema.php',
    'HTMLPurifier_ConfigSchema_Builder_ConfigSchema' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php',
    'HTMLPurifier_ConfigSchema_Builder_Xml' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/Xml.php',
    'HTMLPurifier_ConfigSchema_Exception' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Exception.php',
    'HTMLPurifier_ConfigSchema_Interchange' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange.php',
    'HTMLPurifier_ConfigSchema_InterchangeBuilder' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php',
    'HTMLPurifier_ConfigSchema_Interchange_Directive' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Directive.php',
    'HTMLPurifier_ConfigSchema_Interchange_Id' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Id.php',
    'HTMLPurifier_ConfigSchema_Validator' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Validator.php',
    'HTMLPurifier_ConfigSchema_ValidatorAtom' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php',
    'HTMLPurifier_ContentSets' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/ContentSets.php',
    'HTMLPurifier_Context' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Context.php',
    'HTMLPurifier_Definition' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Definition.php',
    'HTMLPurifier_DefinitionCache' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache.php',
    'HTMLPurifier_DefinitionCacheFactory' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCacheFactory.php',
    'HTMLPurifier_DefinitionCache_Decorator' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator.php',
    'HTMLPurifier_DefinitionCache_Decorator_Cleanup' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php',
    'HTMLPurifier_DefinitionCache_Decorator_Memory' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Memory.php',
    'HTMLPurifier_DefinitionCache_Null' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Null.php',
    'HTMLPurifier_DefinitionCache_Serializer' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer.php',
    'HTMLPurifier_Doctype' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Doctype.php',
    'HTMLPurifier_DoctypeRegistry' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/DoctypeRegistry.php',
    'HTMLPurifier_ElementDef' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/ElementDef.php',
    'HTMLPurifier_Encoder' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Encoder.php',
    'HTMLPurifier_EntityLookup' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup.php',
    'HTMLPurifier_EntityParser' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/EntityParser.php',
    'HTMLPurifier_ErrorCollector' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/ErrorCollector.php',
    'HTMLPurifier_ErrorStruct' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/ErrorStruct.php',
    'HTMLPurifier_Exception' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Exception.php',
    'HTMLPurifier_Filter' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Filter.php',
    'HTMLPurifier_Filter_ExtractStyleBlocks' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Filter/ExtractStyleBlocks.php',
    'HTMLPurifier_Filter_YouTube' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Filter/YouTube.php',
    'HTMLPurifier_Generator' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Generator.php',
    'HTMLPurifier_HTMLDefinition' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLDefinition.php',
    'HTMLPurifier_HTMLModule' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule.php',
    'HTMLPurifier_HTMLModuleManager' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModuleManager.php',
    'HTMLPurifier_HTMLModule_Bdo' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Bdo.php',
    'HTMLPurifier_HTMLModule_CommonAttributes' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/CommonAttributes.php',
    'HTMLPurifier_HTMLModule_Edit' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Edit.php',
    'HTMLPurifier_HTMLModule_Forms' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Forms.php',
    'HTMLPurifier_HTMLModule_Hypertext' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Hypertext.php',
    'HTMLPurifier_HTMLModule_Iframe' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Iframe.php',
    'HTMLPurifier_HTMLModule_Image' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Image.php',
    'HTMLPurifier_HTMLModule_Legacy' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Legacy.php',
    'HTMLPurifier_HTMLModule_List' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/List.php',
    'HTMLPurifier_HTMLModule_Name' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Name.php',
    'HTMLPurifier_HTMLModule_Nofollow' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Nofollow.php',
    'HTMLPurifier_HTMLModule_NonXMLCommonAttributes' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php',
    'HTMLPurifier_HTMLModule_Object' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Object.php',
    'HTMLPurifier_HTMLModule_Presentation' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Presentation.php',
    'HTMLPurifier_HTMLModule_Proprietary' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Proprietary.php',
    'HTMLPurifier_HTMLModule_Ruby' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Ruby.php',
    'HTMLPurifier_HTMLModule_SafeEmbed' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeEmbed.php',
    'HTMLPurifier_HTMLModule_SafeObject' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeObject.php',
    'HTMLPurifier_HTMLModule_SafeScripting' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeScripting.php',
    'HTMLPurifier_HTMLModule_Scripting' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Scripting.php',
    'HTMLPurifier_HTMLModule_StyleAttribute' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/StyleAttribute.php',
    'HTMLPurifier_HTMLModule_Tables' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tables.php',
    'HTMLPurifier_HTMLModule_Target' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Target.php',
    'HTMLPurifier_HTMLModule_TargetBlank' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetBlank.php',
    'HTMLPurifier_HTMLModule_TargetNoopener' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetNoopener.php',
    'HTMLPurifier_HTMLModule_TargetNoreferrer' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetNoreferrer.php',
    'HTMLPurifier_HTMLModule_Text' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Text.php',
    'HTMLPurifier_HTMLModule_Tidy' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy.php',
    'HTMLPurifier_HTMLModule_Tidy_Name' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Name.php',
    'HTMLPurifier_HTMLModule_Tidy_Proprietary' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Proprietary.php',
    'HTMLPurifier_HTMLModule_Tidy_Strict' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Strict.php',
    'HTMLPurifier_HTMLModule_Tidy_Transitional' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Transitional.php',
    'HTMLPurifier_HTMLModule_Tidy_XHTML' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/XHTML.php',
    'HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php',
    'HTMLPurifier_HTMLModule_XMLCommonAttributes' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/XMLCommonAttributes.php',
    'HTMLPurifier_IDAccumulator' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/IDAccumulator.php',
    'HTMLPurifier_Injector' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Injector.php',
    'HTMLPurifier_Injector_AutoParagraph' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Injector/AutoParagraph.php',
    'HTMLPurifier_Injector_DisplayLinkURI' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Injector/DisplayLinkURI.php',
    'HTMLPurifier_Injector_Linkify' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Injector/Linkify.php',
    'HTMLPurifier_Injector_PurifierLinkify' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Injector/PurifierLinkify.php',
    'HTMLPurifier_Injector_RemoveEmpty' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveEmpty.php',
    'HTMLPurifier_Injector_RemoveSpansWithoutAttributes' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php',
    'HTMLPurifier_Injector_SafeObject' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Injector/SafeObject.php',
    'HTMLPurifier_Language' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Language.php',
    'HTMLPurifier_LanguageFactory' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/LanguageFactory.php',
    'HTMLPurifier_Language_en_x_test' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Language/classes/en-x-test.php',
    'HTMLPurifier_Length' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Length.php',
    'HTMLPurifier_Lexer' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Lexer.php',
    'HTMLPurifier_Lexer_DOMLex' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DOMLex.php',
    'HTMLPurifier_Lexer_DirectLex' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DirectLex.php',
    'HTMLPurifier_Lexer_PH5P' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/PH5P.php',
    'HTMLPurifier_Node' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Node.php',
    'HTMLPurifier_Node_Comment' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Node/Comment.php',
    'HTMLPurifier_Node_Element' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Node/Element.php',
    'HTMLPurifier_Node_Text' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Node/Text.php',
    'HTMLPurifier_PercentEncoder' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/PercentEncoder.php',
    'HTMLPurifier_Printer' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Printer.php',
    'HTMLPurifier_Printer_CSSDefinition' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Printer/CSSDefinition.php',
    'HTMLPurifier_Printer_ConfigForm' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php',
    'HTMLPurifier_Printer_ConfigForm_NullDecorator' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php',
    'HTMLPurifier_Printer_ConfigForm_bool' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php',
    'HTMLPurifier_Printer_ConfigForm_default' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php',
    'HTMLPurifier_Printer_HTMLDefinition' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Printer/HTMLDefinition.php',
    'HTMLPurifier_PropertyList' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/PropertyList.php',
    'HTMLPurifier_PropertyListIterator' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/PropertyListIterator.php',
    'HTMLPurifier_Queue' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Queue.php',
    'HTMLPurifier_Strategy' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Strategy.php',
    'HTMLPurifier_Strategy_Composite' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Composite.php',
    'HTMLPurifier_Strategy_Core' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Core.php',
    'HTMLPurifier_Strategy_FixNesting' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/FixNesting.php',
    'HTMLPurifier_Strategy_MakeWellFormed' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/MakeWellFormed.php',
    'HTMLPurifier_Strategy_RemoveForeignElements' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/RemoveForeignElements.php',
    'HTMLPurifier_Strategy_ValidateAttributes' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/ValidateAttributes.php',
    'HTMLPurifier_StringHash' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/StringHash.php',
    'HTMLPurifier_StringHashParser' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/StringHashParser.php',
    'HTMLPurifier_TagTransform' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform.php',
    'HTMLPurifier_TagTransform_Font' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Font.php',
    'HTMLPurifier_TagTransform_Simple' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Simple.php',
    'HTMLPurifier_Token' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Token.php',
    'HTMLPurifier_TokenFactory' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/TokenFactory.php',
    'HTMLPurifier_Token_Comment' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Token/Comment.php',
    'HTMLPurifier_Token_Empty' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Token/Empty.php',
    'HTMLPurifier_Token_End' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Token/End.php',
    'HTMLPurifier_Token_Start' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Token/Start.php',
    'HTMLPurifier_Token_Tag' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Token/Tag.php',
    'HTMLPurifier_Token_Text' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Token/Text.php',
    'HTMLPurifier_URI' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/URI.php',
    'HTMLPurifier_URIDefinition' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/URIDefinition.php',
    'HTMLPurifier_URIFilter' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter.php',
    'HTMLPurifier_URIFilter_DisableExternal' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternal.php',
    'HTMLPurifier_URIFilter_DisableExternalResources' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternalResources.php',
    'HTMLPurifier_URIFilter_DisableResources' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableResources.php',
    'HTMLPurifier_URIFilter_HostBlacklist' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/HostBlacklist.php',
    'HTMLPurifier_URIFilter_MakeAbsolute' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/MakeAbsolute.php',
    'HTMLPurifier_URIFilter_Munge' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/Munge.php',
    'HTMLPurifier_URIFilter_SafeIframe' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/SafeIframe.php',
    'HTMLPurifier_URIParser' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/URIParser.php',
    'HTMLPurifier_URIScheme' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme.php',
    'HTMLPurifier_URISchemeRegistry' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/URISchemeRegistry.php',
    'HTMLPurifier_URIScheme_data' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/data.php',
    'HTMLPurifier_URIScheme_file' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/file.php',
    'HTMLPurifier_URIScheme_ftp' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/ftp.php',
    'HTMLPurifier_URIScheme_http' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/http.php',
    'HTMLPurifier_URIScheme_https' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/https.php',
    'HTMLPurifier_URIScheme_mailto' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/mailto.php',
    'HTMLPurifier_URIScheme_news' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/news.php',
    'HTMLPurifier_URIScheme_nntp' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/nntp.php',
    'HTMLPurifier_URIScheme_tel' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/tel.php',
    'HTMLPurifier_UnitConverter' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/UnitConverter.php',
    'HTMLPurifier_VarParser' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/VarParser.php',
    'HTMLPurifier_VarParserException' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/VarParserException.php',
    'HTMLPurifier_VarParser_Flexible' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php',
    'HTMLPurifier_VarParser_Native' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php',
    'HTMLPurifier_Zipper' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Zipper.php',
    'Monolog\\ErrorHandler' => $vendorDir . '/monolog/monolog/src/Monolog/ErrorHandler.php',
    'Monolog\\Formatter\\ChromePHPFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php',
    'Monolog\\Formatter\\ElasticaFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php',
    'Monolog\\Formatter\\FlowdockFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php',
    'Monolog\\Formatter\\FluentdFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php',
    'Monolog\\Formatter\\FormatterInterface' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php',
    'Monolog\\Formatter\\GelfMessageFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php',
    'Monolog\\Formatter\\HtmlFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php',
    'Monolog\\Formatter\\JsonFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php',
    'Monolog\\Formatter\\LineFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/LineFormatter.php',
    'Monolog\\Formatter\\LogglyFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php',
    'Monolog\\Formatter\\LogstashFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php',
    'Monolog\\Formatter\\MongoDBFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php',
    'Monolog\\Formatter\\NormalizerFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php',
    'Monolog\\Formatter\\ScalarFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php',
    'Monolog\\Formatter\\WildfireFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php',
    'Monolog\\Handler\\AbstractHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/AbstractHandler.php',
    'Monolog\\Handler\\AbstractProcessingHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php',
    'Monolog\\Handler\\AbstractSyslogHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php',
    'Monolog\\Handler\\AmqpHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/AmqpHandler.php',
    'Monolog\\Handler\\BrowserConsoleHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php',
    'Monolog\\Handler\\BufferHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/BufferHandler.php',
    'Monolog\\Handler\\ChromePHPHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php',
    'Monolog\\Handler\\CouchDBHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php',
    'Monolog\\Handler\\CubeHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/CubeHandler.php',
    'Monolog\\Handler\\Curl\\Util' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/Curl/Util.php',
    'Monolog\\Handler\\DeduplicationHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php',
    'Monolog\\Handler\\DoctrineCouchDBHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php',
    'Monolog\\Handler\\DynamoDbHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php',
    'Monolog\\Handler\\ElasticSearchHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php',
    'Monolog\\Handler\\ErrorLogHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php',
    'Monolog\\Handler\\FilterHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FilterHandler.php',
    'Monolog\\Handler\\FingersCrossedHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php',
    'Monolog\\Handler\\FingersCrossed\\ActivationStrategyInterface' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php',
    'Monolog\\Handler\\FingersCrossed\\ChannelLevelActivationStrategy' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php',
    'Monolog\\Handler\\FingersCrossed\\ErrorLevelActivationStrategy' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php',
    'Monolog\\Handler\\FirePHPHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php',
    'Monolog\\Handler\\FleepHookHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php',
    'Monolog\\Handler\\FlowdockHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php',
    'Monolog\\Handler\\GelfHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/GelfHandler.php',
    'Monolog\\Handler\\GroupHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/GroupHandler.php',
    'Monolog\\Handler\\HandlerInterface' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/HandlerInterface.php',
    'Monolog\\Handler\\HandlerWrapper' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php',
    'Monolog\\Handler\\HipChatHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/HipChatHandler.php',
    'Monolog\\Handler\\IFTTTHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php',
    'Monolog\\Handler\\InsightOpsHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php',
    'Monolog\\Handler\\LogEntriesHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php',
    'Monolog\\Handler\\LogglyHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/LogglyHandler.php',
    'Monolog\\Handler\\MailHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/MailHandler.php',
    'Monolog\\Handler\\MandrillHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/MandrillHandler.php',
    'Monolog\\Handler\\MissingExtensionException' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php',
    'Monolog\\Handler\\MongoDBHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php',
    'Monolog\\Handler\\NativeMailerHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php',
    'Monolog\\Handler\\NewRelicHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php',
    'Monolog\\Handler\\NullHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/NullHandler.php',
    'Monolog\\Handler\\PHPConsoleHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php',
    'Monolog\\Handler\\PsrHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/PsrHandler.php',
    'Monolog\\Handler\\PushoverHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/PushoverHandler.php',
    'Monolog\\Handler\\RavenHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/RavenHandler.php',
    'Monolog\\Handler\\RedisHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/RedisHandler.php',
    'Monolog\\Handler\\RollbarHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/RollbarHandler.php',
    'Monolog\\Handler\\RotatingFileHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php',
    'Monolog\\Handler\\SamplingHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SamplingHandler.php',
    'Monolog\\Handler\\SlackHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SlackHandler.php',
    'Monolog\\Handler\\SlackWebhookHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php',
    'Monolog\\Handler\\Slack\\SlackRecord' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php',
    'Monolog\\Handler\\SlackbotHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php',
    'Monolog\\Handler\\SocketHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SocketHandler.php',
    'Monolog\\Handler\\StreamHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/StreamHandler.php',
    'Monolog\\Handler\\SwiftMailerHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php',
    'Monolog\\Handler\\SyslogHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SyslogHandler.php',
    'Monolog\\Handler\\SyslogUdpHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php',
    'Monolog\\Handler\\SyslogUdp\\UdpSocket' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php',
    'Monolog\\Handler\\TestHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/TestHandler.php',
    'Monolog\\Handler\\WhatFailureGroupHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php',
    'Monolog\\Handler\\ZendMonitorHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php',
    'Monolog\\Logger' => $vendorDir . '/monolog/monolog/src/Monolog/Logger.php',
    'Monolog\\Processor\\GitProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/GitProcessor.php',
    'Monolog\\Processor\\IntrospectionProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php',
    'Monolog\\Processor\\MemoryPeakUsageProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php',
    'Monolog\\Processor\\MemoryProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php',
    'Monolog\\Processor\\MemoryUsageProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php',
    'Monolog\\Processor\\MercurialProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php',
    'Monolog\\Processor\\ProcessIdProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php',
    'Monolog\\Processor\\ProcessorInterface' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php',
    'Monolog\\Processor\\PsrLogMessageProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php',
    'Monolog\\Processor\\TagProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/TagProcessor.php',
    'Monolog\\Processor\\UidProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/UidProcessor.php',
    'Monolog\\Processor\\WebProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/WebProcessor.php',
    'Monolog\\Registry' => $vendorDir . '/monolog/monolog/src/Monolog/Registry.php',
    'Monolog\\ResettableInterface' => $vendorDir . '/monolog/monolog/src/Monolog/ResettableInterface.php',
    'Monolog\\SignalHandler' => $vendorDir . '/monolog/monolog/src/Monolog/SignalHandler.php',
    'Monolog\\Utils' => $vendorDir . '/monolog/monolog/src/Monolog/Utils.php',
    'PHPUnit\\Framework\\Assert' => $vendorDir . '/phpunit/phpunit/src/ForwardCompatibility/Assert.php',
    'PHPUnit\\Framework\\AssertionFailedError' => $vendorDir . '/phpunit/phpunit/src/ForwardCompatibility/AssertionFailedError.php',
    'PHPUnit\\Framework\\BaseTestListener' => $vendorDir . '/phpunit/phpunit/src/ForwardCompatibility/BaseTestListener.php',
    'PHPUnit\\Framework\\Test' => $vendorDir . '/phpunit/phpunit/src/ForwardCompatibility/Test.php',
    'PHPUnit\\Framework\\TestCase' => $vendorDir . '/phpunit/phpunit/src/ForwardCompatibility/TestCase.php',
    'PHPUnit\\Framework\\TestListener' => $vendorDir . '/phpunit/phpunit/src/ForwardCompatibility/TestListener.php',
    'PHPUnit\\Framework\\TestSuite' => $vendorDir . '/phpunit/phpunit/src/ForwardCompatibility/TestSuite.php',
    'PHPUnit_Exception' => $vendorDir . '/phpunit/phpunit/src/Exception.php',
    'PHPUnit_Extensions_GroupTestSuite' => $vendorDir . '/phpunit/phpunit/src/Extensions/GroupTestSuite.php',
    'PHPUnit_Extensions_PhptTestCase' => $vendorDir . '/phpunit/phpunit/src/Extensions/PhptTestCase.php',
    'PHPUnit_Extensions_PhptTestSuite' => $vendorDir . '/phpunit/phpunit/src/Extensions/PhptTestSuite.php',
    'PHPUnit_Extensions_RepeatedTest' => $vendorDir . '/phpunit/phpunit/src/Extensions/RepeatedTest.php',
    'PHPUnit_Extensions_TestDecorator' => $vendorDir . '/phpunit/phpunit/src/Extensions/TestDecorator.php',
    'PHPUnit_Extensions_TicketListener' => $vendorDir . '/phpunit/phpunit/src/Extensions/TicketListener.php',
    'PHPUnit_Framework_Assert' => $vendorDir . '/phpunit/phpunit/src/Framework/Assert.php',
    'PHPUnit_Framework_AssertionFailedError' => $vendorDir . '/phpunit/phpunit/src/Framework/AssertionFailedError.php',
    'PHPUnit_Framework_BaseTestListener' => $vendorDir . '/phpunit/phpunit/src/Framework/BaseTestListener.php',
    'PHPUnit_Framework_CodeCoverageException' => $vendorDir . '/phpunit/phpunit/src/Framework/CodeCoverageException.php',
    'PHPUnit_Framework_Constraint' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint.php',
    'PHPUnit_Framework_Constraint_And' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/And.php',
    'PHPUnit_Framework_Constraint_ArrayHasKey' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ArrayHasKey.php',
    'PHPUnit_Framework_Constraint_ArraySubset' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ArraySubset.php',
    'PHPUnit_Framework_Constraint_Attribute' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Attribute.php',
    'PHPUnit_Framework_Constraint_Callback' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Callback.php',
    'PHPUnit_Framework_Constraint_ClassHasAttribute' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ClassHasAttribute.php',
    'PHPUnit_Framework_Constraint_ClassHasStaticAttribute' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ClassHasStaticAttribute.php',
    'PHPUnit_Framework_Constraint_Composite' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Composite.php',
    'PHPUnit_Framework_Constraint_Count' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Count.php',
    'PHPUnit_Framework_Constraint_DirectoryExists' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/DirectoryExists.php',
    'PHPUnit_Framework_Constraint_Exception' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Exception.php',
    'PHPUnit_Framework_Constraint_ExceptionCode' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ExceptionCode.php',
    'PHPUnit_Framework_Constraint_ExceptionMessage' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ExceptionMessage.php',
    'PHPUnit_Framework_Constraint_ExceptionMessageRegExp' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ExceptionMessageRegExp.php',
    'PHPUnit_Framework_Constraint_FileExists' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/FileExists.php',
    'PHPUnit_Framework_Constraint_GreaterThan' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/GreaterThan.php',
    'PHPUnit_Framework_Constraint_IsAnything' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsAnything.php',
    'PHPUnit_Framework_Constraint_IsEmpty' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsEmpty.php',
    'PHPUnit_Framework_Constraint_IsEqual' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsEqual.php',
    'PHPUnit_Framework_Constraint_IsFalse' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsFalse.php',
    'PHPUnit_Framework_Constraint_IsFinite' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsFinite.php',
    'PHPUnit_Framework_Constraint_IsIdentical' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsIdentical.php',
    'PHPUnit_Framework_Constraint_IsInfinite' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsInfinite.php',
    'PHPUnit_Framework_Constraint_IsInstanceOf' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsInstanceOf.php',
    'PHPUnit_Framework_Constraint_IsJson' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsJson.php',
    'PHPUnit_Framework_Constraint_IsNan' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsNan.php',
    'PHPUnit_Framework_Constraint_IsNull' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsNull.php',
    'PHPUnit_Framework_Constraint_IsReadable' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsReadable.php',
    'PHPUnit_Framework_Constraint_IsTrue' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsTrue.php',
    'PHPUnit_Framework_Constraint_IsType' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsType.php',
    'PHPUnit_Framework_Constraint_IsWritable' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsWritable.php',
    'PHPUnit_Framework_Constraint_JsonMatches' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/JsonMatches.php',
    'PHPUnit_Framework_Constraint_JsonMatches_ErrorMessageProvider' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/JsonMatches/ErrorMessageProvider.php',
    'PHPUnit_Framework_Constraint_LessThan' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/LessThan.php',
    'PHPUnit_Framework_Constraint_Not' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Not.php',
    'PHPUnit_Framework_Constraint_ObjectHasAttribute' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ObjectHasAttribute.php',
    'PHPUnit_Framework_Constraint_Or' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Or.php',
    'PHPUnit_Framework_Constraint_PCREMatch' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/PCREMatch.php',
    'PHPUnit_Framework_Constraint_SameSize' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/SameSize.php',
    'PHPUnit_Framework_Constraint_StringContains' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/StringContains.php',
    'PHPUnit_Framework_Constraint_StringEndsWith' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/StringEndsWith.php',
    'PHPUnit_Framework_Constraint_StringMatches' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/StringMatches.php',
    'PHPUnit_Framework_Constraint_StringStartsWith' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/StringStartsWith.php',
    'PHPUnit_Framework_Constraint_TraversableContains' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/TraversableContains.php',
    'PHPUnit_Framework_Constraint_TraversableContainsOnly' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/TraversableContainsOnly.php',
    'PHPUnit_Framework_Constraint_Xor' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Xor.php',
    'PHPUnit_Framework_CoveredCodeNotExecutedException' => $vendorDir . '/phpunit/phpunit/src/Framework/CoveredCodeNotExecutedException.php',
    'PHPUnit_Framework_Error' => $vendorDir . '/phpunit/phpunit/src/Framework/Error.php',
    'PHPUnit_Framework_Error_Deprecated' => $vendorDir . '/phpunit/phpunit/src/Framework/Error/Deprecated.php',
    'PHPUnit_Framework_Error_Notice' => $vendorDir . '/phpunit/phpunit/src/Framework/Error/Notice.php',
    'PHPUnit_Framework_Error_Warning' => $vendorDir . '/phpunit/phpunit/src/Framework/Error/Warning.php',
    'PHPUnit_Framework_Exception' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception.php',
    'PHPUnit_Framework_ExceptionWrapper' => $vendorDir . '/phpunit/phpunit/src/Framework/ExceptionWrapper.php',
    'PHPUnit_Framework_ExpectationFailedException' => $vendorDir . '/phpunit/phpunit/src/Framework/ExpectationFailedException.php',
    'PHPUnit_Framework_IncompleteTest' => $vendorDir . '/phpunit/phpunit/src/Framework/IncompleteTest.php',
    'PHPUnit_Framework_IncompleteTestCase' => $vendorDir . '/phpunit/phpunit/src/Framework/IncompleteTestCase.php',
    'PHPUnit_Framework_IncompleteTestError' => $vendorDir . '/phpunit/phpunit/src/Framework/IncompleteTestError.php',
    'PHPUnit_Framework_InvalidCoversTargetException' => $vendorDir . '/phpunit/phpunit/src/Framework/InvalidCoversTargetException.php',
    'PHPUnit_Framework_MissingCoversAnnotationException' => $vendorDir . '/phpunit/phpunit/src/Framework/MissingCoversAnnotationException.php',
    'PHPUnit_Framework_MockObject_BadMethodCallException' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Exception/BadMethodCallException.php',
    'PHPUnit_Framework_MockObject_Builder_Identity' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Identity.php',
    'PHPUnit_Framework_MockObject_Builder_InvocationMocker' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/InvocationMocker.php',
    'PHPUnit_Framework_MockObject_Builder_Match' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Match.php',
    'PHPUnit_Framework_MockObject_Builder_MethodNameMatch' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/MethodNameMatch.php',
    'PHPUnit_Framework_MockObject_Builder_Namespace' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Namespace.php',
    'PHPUnit_Framework_MockObject_Builder_ParametersMatch' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/ParametersMatch.php',
    'PHPUnit_Framework_MockObject_Builder_Stub' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Stub.php',
    'PHPUnit_Framework_MockObject_Exception' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Exception/Exception.php',
    'PHPUnit_Framework_MockObject_Generator' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Generator.php',
    'PHPUnit_Framework_MockObject_Invocation' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invocation.php',
    'PHPUnit_Framework_MockObject_InvocationMocker' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/InvocationMocker.php',
    'PHPUnit_Framework_MockObject_Invocation_Object' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invocation/Object.php',
    'PHPUnit_Framework_MockObject_Invocation_Static' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invocation/Static.php',
    'PHPUnit_Framework_MockObject_Invokable' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invokable.php',
    'PHPUnit_Framework_MockObject_Matcher' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher.php',
    'PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/AnyInvokedCount.php',
    'PHPUnit_Framework_MockObject_Matcher_AnyParameters' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/AnyParameters.php',
    'PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/ConsecutiveParameters.php',
    'PHPUnit_Framework_MockObject_Matcher_Invocation' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/Invocation.php',
    'PHPUnit_Framework_MockObject_Matcher_InvokedAtIndex' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtIndex.php',
    'PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastCount' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtLeastCount.php',
    'PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastOnce' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtLeastOnce.php',
    'PHPUnit_Framework_MockObject_Matcher_InvokedAtMostCount' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtMostCount.php',
    'PHPUnit_Framework_MockObject_Matcher_InvokedCount' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedCount.php',
    'PHPUnit_Framework_MockObject_Matcher_InvokedRecorder' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedRecorder.php',
    'PHPUnit_Framework_MockObject_Matcher_MethodName' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/MethodName.php',
    'PHPUnit_Framework_MockObject_Matcher_Parameters' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/Parameters.php',
    'PHPUnit_Framework_MockObject_Matcher_StatelessInvocation' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/StatelessInvocation.php',
    'PHPUnit_Framework_MockObject_MockBuilder' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/MockBuilder.php',
    'PHPUnit_Framework_MockObject_MockObject' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/MockObject.php',
    'PHPUnit_Framework_MockObject_RuntimeException' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Exception/RuntimeException.php',
    'PHPUnit_Framework_MockObject_Stub' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub.php',
    'PHPUnit_Framework_MockObject_Stub_ConsecutiveCalls' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ConsecutiveCalls.php',
    'PHPUnit_Framework_MockObject_Stub_Exception' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/Exception.php',
    'PHPUnit_Framework_MockObject_Stub_MatcherCollection' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/MatcherCollection.php',
    'PHPUnit_Framework_MockObject_Stub_Return' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/Return.php',
    'PHPUnit_Framework_MockObject_Stub_ReturnArgument' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnArgument.php',
    'PHPUnit_Framework_MockObject_Stub_ReturnCallback' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnCallback.php',
    'PHPUnit_Framework_MockObject_Stub_ReturnReference' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnReference.php',
    'PHPUnit_Framework_MockObject_Stub_ReturnSelf' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnSelf.php',
    'PHPUnit_Framework_MockObject_Stub_ReturnValueMap' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnValueMap.php',
    'PHPUnit_Framework_MockObject_Verifiable' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Verifiable.php',
    'PHPUnit_Framework_OutputError' => $vendorDir . '/phpunit/phpunit/src/Framework/OutputError.php',
    'PHPUnit_Framework_RiskyTest' => $vendorDir . '/phpunit/phpunit/src/Framework/RiskyTest.php',
    'PHPUnit_Framework_RiskyTestError' => $vendorDir . '/phpunit/phpunit/src/Framework/RiskyTestError.php',
    'PHPUnit_Framework_SelfDescribing' => $vendorDir . '/phpunit/phpunit/src/Framework/SelfDescribing.php',
    'PHPUnit_Framework_SkippedTest' => $vendorDir . '/phpunit/phpunit/src/Framework/SkippedTest.php',
    'PHPUnit_Framework_SkippedTestCase' => $vendorDir . '/phpunit/phpunit/src/Framework/SkippedTestCase.php',
    'PHPUnit_Framework_SkippedTestError' => $vendorDir . '/phpunit/phpunit/src/Framework/SkippedTestError.php',
    'PHPUnit_Framework_SkippedTestSuiteError' => $vendorDir . '/phpunit/phpunit/src/Framework/SkippedTestSuiteError.php',
    'PHPUnit_Framework_SyntheticError' => $vendorDir . '/phpunit/phpunit/src/Framework/SyntheticError.php',
    'PHPUnit_Framework_Test' => $vendorDir . '/phpunit/phpunit/src/Framework/Test.php',
    'PHPUnit_Framework_TestCase' => $vendorDir . '/phpunit/phpunit/src/Framework/TestCase.php',
    'PHPUnit_Framework_TestFailure' => $vendorDir . '/phpunit/phpunit/src/Framework/TestFailure.php',
    'PHPUnit_Framework_TestListener' => $vendorDir . '/phpunit/phpunit/src/Framework/TestListener.php',
    'PHPUnit_Framework_TestResult' => $vendorDir . '/phpunit/phpunit/src/Framework/TestResult.php',
    'PHPUnit_Framework_TestSuite' => $vendorDir . '/phpunit/phpunit/src/Framework/TestSuite.php',
    'PHPUnit_Framework_TestSuite_DataProvider' => $vendorDir . '/phpunit/phpunit/src/Framework/TestSuite/DataProvider.php',
    'PHPUnit_Framework_UnintentionallyCoveredCodeError' => $vendorDir . '/phpunit/phpunit/src/Framework/UnintentionallyCoveredCodeError.php',
    'PHPUnit_Framework_Warning' => $vendorDir . '/phpunit/phpunit/src/Framework/Warning.php',
    'PHPUnit_Framework_WarningTestCase' => $vendorDir . '/phpunit/phpunit/src/Framework/WarningTestCase.php',
    'PHPUnit_Runner_BaseTestRunner' => $vendorDir . '/phpunit/phpunit/src/Runner/BaseTestRunner.php',
    'PHPUnit_Runner_Exception' => $vendorDir . '/phpunit/phpunit/src/Runner/Exception.php',
    'PHPUnit_Runner_Filter_Factory' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/Factory.php',
    'PHPUnit_Runner_Filter_GroupFilterIterator' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/Group.php',
    'PHPUnit_Runner_Filter_Group_Exclude' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/Group/Exclude.php',
    'PHPUnit_Runner_Filter_Group_Include' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/Group/Include.php',
    'PHPUnit_Runner_Filter_Test' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/Test.php',
    'PHPUnit_Runner_StandardTestSuiteLoader' => $vendorDir . '/phpunit/phpunit/src/Runner/StandardTestSuiteLoader.php',
    'PHPUnit_Runner_TestSuiteLoader' => $vendorDir . '/phpunit/phpunit/src/Runner/TestSuiteLoader.php',
    'PHPUnit_Runner_Version' => $vendorDir . '/phpunit/phpunit/src/Runner/Version.php',
    'PHPUnit_TextUI_Command' => $vendorDir . '/phpunit/phpunit/src/TextUI/Command.php',
    'PHPUnit_TextUI_ResultPrinter' => $vendorDir . '/phpunit/phpunit/src/TextUI/ResultPrinter.php',
    'PHPUnit_TextUI_TestRunner' => $vendorDir . '/phpunit/phpunit/src/TextUI/TestRunner.php',
    'PHPUnit_Util_Blacklist' => $vendorDir . '/phpunit/phpunit/src/Util/Blacklist.php',
    'PHPUnit_Util_Configuration' => $vendorDir . '/phpunit/phpunit/src/Util/Configuration.php',
    'PHPUnit_Util_ConfigurationGenerator' => $vendorDir . '/phpunit/phpunit/src/Util/ConfigurationGenerator.php',
    'PHPUnit_Util_ErrorHandler' => $vendorDir . '/phpunit/phpunit/src/Util/ErrorHandler.php',
    'PHPUnit_Util_Fileloader' => $vendorDir . '/phpunit/phpunit/src/Util/Fileloader.php',
    'PHPUnit_Util_Filesystem' => $vendorDir . '/phpunit/phpunit/src/Util/Filesystem.php',
    'PHPUnit_Util_Filter' => $vendorDir . '/phpunit/phpunit/src/Util/Filter.php',
    'PHPUnit_Util_Getopt' => $vendorDir . '/phpunit/phpunit/src/Util/Getopt.php',
    'PHPUnit_Util_GlobalState' => $vendorDir . '/phpunit/phpunit/src/Util/GlobalState.php',
    'PHPUnit_Util_InvalidArgumentHelper' => $vendorDir . '/phpunit/phpunit/src/Util/InvalidArgumentHelper.php',
    'PHPUnit_Util_Log_JSON' => $vendorDir . '/phpunit/phpunit/src/Util/Log/JSON.php',
    'PHPUnit_Util_Log_JUnit' => $vendorDir . '/phpunit/phpunit/src/Util/Log/JUnit.php',
    'PHPUnit_Util_Log_TAP' => $vendorDir . '/phpunit/phpunit/src/Util/Log/TAP.php',
    'PHPUnit_Util_Log_TeamCity' => $vendorDir . '/phpunit/phpunit/src/Util/Log/TeamCity.php',
    'PHPUnit_Util_PHP' => $vendorDir . '/phpunit/phpunit/src/Util/PHP.php',
    'PHPUnit_Util_PHP_Default' => $vendorDir . '/phpunit/phpunit/src/Util/PHP/Default.php',
    'PHPUnit_Util_PHP_Windows' => $vendorDir . '/phpunit/phpunit/src/Util/PHP/Windows.php',
    'PHPUnit_Util_Printer' => $vendorDir . '/phpunit/phpunit/src/Util/Printer.php',
    'PHPUnit_Util_Regex' => $vendorDir . '/phpunit/phpunit/src/Util/Regex.php',
    'PHPUnit_Util_String' => $vendorDir . '/phpunit/phpunit/src/Util/String.php',
    'PHPUnit_Util_Test' => $vendorDir . '/phpunit/phpunit/src/Util/Test.php',
    'PHPUnit_Util_TestDox_NamePrettifier' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/NamePrettifier.php',
    'PHPUnit_Util_TestDox_ResultPrinter' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/ResultPrinter.php',
    'PHPUnit_Util_TestDox_ResultPrinter_HTML' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/ResultPrinter/HTML.php',
    'PHPUnit_Util_TestDox_ResultPrinter_Text' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/ResultPrinter/Text.php',
    'PHPUnit_Util_TestDox_ResultPrinter_XML' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/ResultPrinter/XML.php',
    'PHPUnit_Util_TestSuiteIterator' => $vendorDir . '/phpunit/phpunit/src/Util/TestSuiteIterator.php',
    'PHPUnit_Util_Type' => $vendorDir . '/phpunit/phpunit/src/Util/Type.php',
    'PHPUnit_Util_XML' => $vendorDir . '/phpunit/phpunit/src/Util/XML.php',
    'PHP_Timer' => $vendorDir . '/phpunit/php-timer/src/Timer.php',
    'PHP_Token' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_TokenWithScope' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_TokenWithScopeAndVisibility' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_ABSTRACT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_AMPERSAND' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_AND_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_ARRAY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_ARRAY_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_AS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_ASYNC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_AT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_AWAIT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_BACKTICK' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_BAD_CHARACTER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_BOOLEAN_AND' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_BOOLEAN_OR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_BOOL_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_BREAK' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_CALLABLE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_CARET' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_CASE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_CATCH' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_CHARACTER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_CLASS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_CLASS_C' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_CLASS_NAME_CONSTANT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_CLONE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_CLOSE_BRACKET' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_CLOSE_CURLY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_CLOSE_SQUARE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_CLOSE_TAG' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_COALESCE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_COLON' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_COMMA' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_COMMENT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_COMPILER_HALT_OFFSET' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_CONCAT_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_CONST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_CONSTANT_ENCAPSED_STRING' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_CONTINUE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_CURLY_OPEN' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_DEC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_DECLARE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_DEFAULT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_DIR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_DIV' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_DIV_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_DNUMBER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_DO' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_DOC_COMMENT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_DOLLAR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_DOLLAR_OPEN_CURLY_BRACES' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_DOT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_DOUBLE_ARROW' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_DOUBLE_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_DOUBLE_COLON' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_DOUBLE_QUOTES' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_ECHO' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_ELLIPSIS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_ELSE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_ELSEIF' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_EMPTY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_ENCAPSED_AND_WHITESPACE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_ENDDECLARE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_ENDFOR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_ENDFOREACH' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_ENDIF' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_ENDSWITCH' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_ENDWHILE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_END_HEREDOC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_ENUM' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_EQUALS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_EVAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_EXCLAMATION_MARK' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_EXIT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_EXTENDS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_FILE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_FINAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_FINALLY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_FOR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_FOREACH' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_FUNCTION' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_FUNC_C' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_GLOBAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_GOTO' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_GT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_HALT_COMPILER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_IF' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_IMPLEMENTS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_IN' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_INC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_INCLUDE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_INCLUDE_ONCE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_INLINE_HTML' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_INSTANCEOF' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_INSTEADOF' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_INTERFACE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_INT_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_ISSET' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_IS_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_IS_GREATER_OR_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_IS_IDENTICAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_IS_NOT_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_IS_NOT_IDENTICAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_IS_SMALLER_OR_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_Includes' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_JOIN' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_LAMBDA_ARROW' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_LAMBDA_CP' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_LAMBDA_OP' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_LINE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_LIST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_LNUMBER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_LOGICAL_AND' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_LOGICAL_OR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_LOGICAL_XOR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_LT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_METHOD_C' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_MINUS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_MINUS_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_MOD_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_MULT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_MUL_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_NAMESPACE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_NEW' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_NS_C' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_NS_SEPARATOR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_NULLSAFE_OBJECT_OPERATOR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_NUM_STRING' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_OBJECT_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_OBJECT_OPERATOR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_ONUMBER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_OPEN_BRACKET' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_OPEN_CURLY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_OPEN_SQUARE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_OPEN_TAG' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_OPEN_TAG_WITH_ECHO' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_OR_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_PAAMAYIM_NEKUDOTAYIM' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_PERCENT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_PIPE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_PLUS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_PLUS_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_POW' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_POW_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_PRINT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_PRIVATE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_PROTECTED' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_PUBLIC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_QUESTION_MARK' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_REQUIRE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_REQUIRE_ONCE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_RETURN' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_SEMICOLON' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_SHAPE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_SL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_SL_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_SPACESHIP' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_SR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_SR_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_START_HEREDOC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_STATIC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_STRING' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_STRING_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_STRING_VARNAME' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_SUPER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_SWITCH' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_Stream' => $vendorDir . '/phpunit/php-token-stream/src/Token/Stream.php',
    'PHP_Token_Stream_CachingFactory' => $vendorDir . '/phpunit/php-token-stream/src/Token/Stream/CachingFactory.php',
    'PHP_Token_THROW' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_TILDE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_TRAIT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_TRAIT_C' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_TRY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_TYPE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_TYPELIST_GT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_TYPELIST_LT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_UNSET' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_UNSET_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_USE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_USE_FUNCTION' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_VAR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_VARIABLE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_WHERE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_WHILE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_WHITESPACE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_XHP_ATTRIBUTE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_XHP_CATEGORY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_XHP_CATEGORY_LABEL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_XHP_CHILDREN' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_XHP_LABEL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_XHP_REQUIRED' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_XHP_TAG_GT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_XHP_TAG_LT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_XHP_TEXT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_XOR_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_YIELD' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'PHP_Token_YIELD_FROM' => $vendorDir . '/phpunit/php-token-stream/src/Token.php',
    'Phinx\\Config\\Config' => $vendorDir . '/robmorgan/phinx/src/Phinx/Config/Config.php',
    'Phinx\\Config\\ConfigInterface' => $vendorDir . '/robmorgan/phinx/src/Phinx/Config/ConfigInterface.php',
    'Phinx\\Config\\NamespaceAwareInterface' => $vendorDir . '/robmorgan/phinx/src/Phinx/Config/NamespaceAwareInterface.php',
    'Phinx\\Config\\NamespaceAwareTrait' => $vendorDir . '/robmorgan/phinx/src/Phinx/Config/NamespaceAwareTrait.php',
    'Phinx\\Console\\Command\\AbstractCommand' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/AbstractCommand.php',
    'Phinx\\Console\\Command\\Breakpoint' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/Breakpoint.php',
    'Phinx\\Console\\Command\\Create' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/Create.php',
    'Phinx\\Console\\Command\\Init' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/Init.php',
    'Phinx\\Console\\Command\\Migrate' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/Migrate.php',
    'Phinx\\Console\\Command\\Rollback' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/Rollback.php',
    'Phinx\\Console\\Command\\SeedCreate' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/SeedCreate.php',
    'Phinx\\Console\\Command\\SeedRun' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/SeedRun.php',
    'Phinx\\Console\\Command\\Status' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/Status.php',
    'Phinx\\Console\\Command\\Test' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/Test.php',
    'Phinx\\Console\\PhinxApplication' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/PhinxApplication.php',
    'Phinx\\Db\\Adapter\\AbstractAdapter' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/AbstractAdapter.php',
    'Phinx\\Db\\Adapter\\AdapterFactory' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterFactory.php',
    'Phinx\\Db\\Adapter\\AdapterInterface' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterInterface.php',
    'Phinx\\Db\\Adapter\\AdapterWrapper' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterWrapper.php',
    'Phinx\\Db\\Adapter\\MysqlAdapter' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/MysqlAdapter.php',
    'Phinx\\Db\\Adapter\\PdoAdapter' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/PdoAdapter.php',
    'Phinx\\Db\\Adapter\\PostgresAdapter' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/PostgresAdapter.php',
    'Phinx\\Db\\Adapter\\ProxyAdapter' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/ProxyAdapter.php',
    'Phinx\\Db\\Adapter\\SQLiteAdapter' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/SQLiteAdapter.php',
    'Phinx\\Db\\Adapter\\SqlServerAdapter' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/SqlServerAdapter.php',
    'Phinx\\Db\\Adapter\\TablePrefixAdapter' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/TablePrefixAdapter.php',
    'Phinx\\Db\\Adapter\\TimedOutputAdapter' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/TimedOutputAdapter.php',
    'Phinx\\Db\\Adapter\\WrapperInterface' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/WrapperInterface.php',
    'Phinx\\Db\\Table' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Table.php',
    'Phinx\\Db\\Table\\Column' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Table/Column.php',
    'Phinx\\Db\\Table\\ForeignKey' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Table/ForeignKey.php',
    'Phinx\\Db\\Table\\Index' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Table/Index.php',
    'Phinx\\Migration\\AbstractMigration' => $vendorDir . '/robmorgan/phinx/src/Phinx/Migration/AbstractMigration.php',
    'Phinx\\Migration\\AbstractTemplateCreation' => $vendorDir . '/robmorgan/phinx/src/Phinx/Migration/AbstractTemplateCreation.php',
    'Phinx\\Migration\\CreationInterface' => $vendorDir . '/robmorgan/phinx/src/Phinx/Migration/CreationInterface.php',
    'Phinx\\Migration\\IrreversibleMigrationException' => $vendorDir . '/robmorgan/phinx/src/Phinx/Migration/IrreversibleMigrationException.php',
    'Phinx\\Migration\\Manager' => $vendorDir . '/robmorgan/phinx/src/Phinx/Migration/Manager.php',
    'Phinx\\Migration\\Manager\\Environment' => $vendorDir . '/robmorgan/phinx/src/Phinx/Migration/Manager/Environment.php',
    'Phinx\\Migration\\MigrationInterface' => $vendorDir . '/robmorgan/phinx/src/Phinx/Migration/MigrationInterface.php',
    'Phinx\\Seed\\AbstractSeed' => $vendorDir . '/robmorgan/phinx/src/Phinx/Seed/AbstractSeed.php',
    'Phinx\\Seed\\SeedInterface' => $vendorDir . '/robmorgan/phinx/src/Phinx/Seed/SeedInterface.php',
    'Phinx\\Util\\Util' => $vendorDir . '/robmorgan/phinx/src/Phinx/Util/Util.php',
    'Phinx\\Wrapper\\TextWrapper' => $vendorDir . '/robmorgan/phinx/src/Phinx/Wrapper/TextWrapper.php',
    'PhpAmqpLib\\Channel\\AMQPChannel' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Channel/AMQPChannel.php',
    'PhpAmqpLib\\Channel\\AbstractChannel' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Channel/AbstractChannel.php',
    'PhpAmqpLib\\Connection\\AMQPConnection' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AMQPConnection.php',
    'PhpAmqpLib\\Connection\\AMQPLazyConnection' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AMQPLazyConnection.php',
    'PhpAmqpLib\\Connection\\AMQPLazySocketConnection' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AMQPLazySocketConnection.php',
    'PhpAmqpLib\\Connection\\AMQPSSLConnection' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AMQPSSLConnection.php',
    'PhpAmqpLib\\Connection\\AMQPSocketConnection' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AMQPSocketConnection.php',
    'PhpAmqpLib\\Connection\\AMQPStreamConnection' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AMQPStreamConnection.php',
    'PhpAmqpLib\\Connection\\AbstractConnection' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AbstractConnection.php',
    'PhpAmqpLib\\Exception\\AMQPBasicCancelException' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPBasicCancelException.php',
    'PhpAmqpLib\\Exception\\AMQPChannelClosedException' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPChannelClosedException.php',
    'PhpAmqpLib\\Exception\\AMQPChannelException' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPChannelException.php',
    'PhpAmqpLib\\Exception\\AMQPConnectionClosedException' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPConnectionClosedException.php',
    'PhpAmqpLib\\Exception\\AMQPConnectionException' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPConnectionException.php',
    'PhpAmqpLib\\Exception\\AMQPDataReadException' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPDataReadException.php',
    'PhpAmqpLib\\Exception\\AMQPException' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPException.php',
    'PhpAmqpLib\\Exception\\AMQPExceptionInterface' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPExceptionInterface.php',
    'PhpAmqpLib\\Exception\\AMQPHeartbeatMissedException' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPHeartbeatMissedException.php',
    'PhpAmqpLib\\Exception\\AMQPIOException' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPIOException.php',
    'PhpAmqpLib\\Exception\\AMQPIOWaitException' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPIOWaitException.php',
    'PhpAmqpLib\\Exception\\AMQPInvalidArgumentException' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPInvalidArgumentException.php',
    'PhpAmqpLib\\Exception\\AMQPInvalidFrameException' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPInvalidFrameException.php',
    'PhpAmqpLib\\Exception\\AMQPLogicException' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPLogicException.php',
    'PhpAmqpLib\\Exception\\AMQPNoDataException' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPNoDataException.php',
    'PhpAmqpLib\\Exception\\AMQPNotImplementedException' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPNotImplementedException.php',
    'PhpAmqpLib\\Exception\\AMQPOutOfBoundsException' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPOutOfBoundsException.php',
    'PhpAmqpLib\\Exception\\AMQPOutOfRangeException' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPOutOfRangeException.php',
    'PhpAmqpLib\\Exception\\AMQPProtocolChannelException' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPProtocolChannelException.php',
    'PhpAmqpLib\\Exception\\AMQPProtocolConnectionException' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPProtocolConnectionException.php',
    'PhpAmqpLib\\Exception\\AMQPProtocolException' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPProtocolException.php',
    'PhpAmqpLib\\Exception\\AMQPRuntimeException' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPRuntimeException.php',
    'PhpAmqpLib\\Exception\\AMQPSocketException' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPSocketException.php',
    'PhpAmqpLib\\Exception\\AMQPTimeoutException' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPTimeoutException.php',
    'PhpAmqpLib\\Exchange\\AMQPExchangeType' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Exchange/AMQPExchangeType.php',
    'PhpAmqpLib\\Helper\\DebugHelper' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Helper/DebugHelper.php',
    'PhpAmqpLib\\Helper\\MiscHelper' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Helper/MiscHelper.php',
    'PhpAmqpLib\\Helper\\Protocol\\MethodMap080' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Helper/Protocol/MethodMap080.php',
    'PhpAmqpLib\\Helper\\Protocol\\MethodMap091' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Helper/Protocol/MethodMap091.php',
    'PhpAmqpLib\\Helper\\Protocol\\Protocol080' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Helper/Protocol/Protocol080.php',
    'PhpAmqpLib\\Helper\\Protocol\\Protocol091' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Helper/Protocol/Protocol091.php',
    'PhpAmqpLib\\Helper\\Protocol\\Wait080' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Helper/Protocol/Wait080.php',
    'PhpAmqpLib\\Helper\\Protocol\\Wait091' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Helper/Protocol/Wait091.php',
    'PhpAmqpLib\\Message\\AMQPMessage' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Message/AMQPMessage.php',
    'PhpAmqpLib\\Wire\\AMQPAbstractCollection' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Wire/AMQPAbstractCollection.php',
    'PhpAmqpLib\\Wire\\AMQPArray' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Wire/AMQPArray.php',
    'PhpAmqpLib\\Wire\\AMQPDecimal' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Wire/AMQPDecimal.php',
    'PhpAmqpLib\\Wire\\AMQPReader' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Wire/AMQPReader.php',
    'PhpAmqpLib\\Wire\\AMQPTable' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Wire/AMQPTable.php',
    'PhpAmqpLib\\Wire\\AMQPWriter' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Wire/AMQPWriter.php',
    'PhpAmqpLib\\Wire\\AbstractClient' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Wire/AbstractClient.php',
    'PhpAmqpLib\\Wire\\Constants080' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Wire/Constants080.php',
    'PhpAmqpLib\\Wire\\Constants091' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Wire/Constants091.php',
    'PhpAmqpLib\\Wire\\GenericContent' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Wire/GenericContent.php',
    'PhpAmqpLib\\Wire\\IO\\AbstractIO' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Wire/IO/AbstractIO.php',
    'PhpAmqpLib\\Wire\\IO\\SocketIO' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Wire/IO/SocketIO.php',
    'PhpAmqpLib\\Wire\\IO\\StreamIO' => $vendorDir . '/php-amqplib/php-amqplib/PhpAmqpLib/Wire/IO/StreamIO.php',
    'Prophecy\\Argument' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument.php',
    'Prophecy\\Argument\\ArgumentsWildcard' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/ArgumentsWildcard.php',
    'Prophecy\\Argument\\Token\\AnyValueToken' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/AnyValueToken.php',
    'Prophecy\\Argument\\Token\\AnyValuesToken' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/AnyValuesToken.php',
    'Prophecy\\Argument\\Token\\ApproximateValueToken' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/ApproximateValueToken.php',
    'Prophecy\\Argument\\Token\\ArrayCountToken' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/ArrayCountToken.php',
    'Prophecy\\Argument\\Token\\ArrayEntryToken' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/ArrayEntryToken.php',
    'Prophecy\\Argument\\Token\\ArrayEveryEntryToken' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/ArrayEveryEntryToken.php',
    'Prophecy\\Argument\\Token\\CallbackToken' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/CallbackToken.php',
    'Prophecy\\Argument\\Token\\ExactValueToken' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/ExactValueToken.php',
    'Prophecy\\Argument\\Token\\IdenticalValueToken' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/IdenticalValueToken.php',
    'Prophecy\\Argument\\Token\\LogicalAndToken' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/LogicalAndToken.php',
    'Prophecy\\Argument\\Token\\LogicalNotToken' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/LogicalNotToken.php',
    'Prophecy\\Argument\\Token\\ObjectStateToken' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/ObjectStateToken.php',
    'Prophecy\\Argument\\Token\\StringContainsToken' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/StringContainsToken.php',
    'Prophecy\\Argument\\Token\\TokenInterface' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/TokenInterface.php',
    'Prophecy\\Argument\\Token\\TypeToken' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Argument/Token/TypeToken.php',
    'Prophecy\\Call\\Call' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Call/Call.php',
    'Prophecy\\Call\\CallCenter' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Call/CallCenter.php',
    'Prophecy\\Comparator\\ClosureComparator' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Comparator/ClosureComparator.php',
    'Prophecy\\Comparator\\Factory' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Comparator/Factory.php',
    'Prophecy\\Comparator\\ProphecyComparator' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Comparator/ProphecyComparator.php',
    'Prophecy\\Doubler\\CachedDoubler' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/CachedDoubler.php',
    'Prophecy\\Doubler\\ClassPatch\\ClassPatchInterface' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ClassPatchInterface.php',
    'Prophecy\\Doubler\\ClassPatch\\DisableConstructorPatch' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/DisableConstructorPatch.php',
    'Prophecy\\Doubler\\ClassPatch\\HhvmExceptionPatch' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/HhvmExceptionPatch.php',
    'Prophecy\\Doubler\\ClassPatch\\KeywordPatch' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/KeywordPatch.php',
    'Prophecy\\Doubler\\ClassPatch\\MagicCallPatch' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/MagicCallPatch.php',
    'Prophecy\\Doubler\\ClassPatch\\ProphecySubjectPatch' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ProphecySubjectPatch.php',
    'Prophecy\\Doubler\\ClassPatch\\ReflectionClassNewInstancePatch' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ReflectionClassNewInstancePatch.php',
    'Prophecy\\Doubler\\ClassPatch\\SplFileInfoPatch' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/SplFileInfoPatch.php',
    'Prophecy\\Doubler\\ClassPatch\\ThrowablePatch' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ThrowablePatch.php',
    'Prophecy\\Doubler\\ClassPatch\\TraversablePatch' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/TraversablePatch.php',
    'Prophecy\\Doubler\\DoubleInterface' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/DoubleInterface.php',
    'Prophecy\\Doubler\\Doubler' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/Doubler.php',
    'Prophecy\\Doubler\\Generator\\ClassCodeGenerator' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassCodeGenerator.php',
    'Prophecy\\Doubler\\Generator\\ClassCreator' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassCreator.php',
    'Prophecy\\Doubler\\Generator\\ClassMirror' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassMirror.php',
    'Prophecy\\Doubler\\Generator\\Node\\ArgumentNode' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ArgumentNode.php',
    'Prophecy\\Doubler\\Generator\\Node\\ClassNode' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ClassNode.php',
    'Prophecy\\Doubler\\Generator\\Node\\MethodNode' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/MethodNode.php',
    'Prophecy\\Doubler\\Generator\\ReflectionInterface' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/ReflectionInterface.php',
    'Prophecy\\Doubler\\Generator\\TypeHintReference' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/TypeHintReference.php',
    'Prophecy\\Doubler\\LazyDouble' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/LazyDouble.php',
    'Prophecy\\Doubler\\NameGenerator' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Doubler/NameGenerator.php',
    'Prophecy\\Exception\\Call\\UnexpectedCallException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Call/UnexpectedCallException.php',
    'Prophecy\\Exception\\Doubler\\ClassCreatorException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/ClassCreatorException.php',
    'Prophecy\\Exception\\Doubler\\ClassMirrorException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/ClassMirrorException.php',
    'Prophecy\\Exception\\Doubler\\ClassNotFoundException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/ClassNotFoundException.php',
    'Prophecy\\Exception\\Doubler\\DoubleException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/DoubleException.php',
    'Prophecy\\Exception\\Doubler\\DoublerException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/DoublerException.php',
    'Prophecy\\Exception\\Doubler\\InterfaceNotFoundException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/InterfaceNotFoundException.php',
    'Prophecy\\Exception\\Doubler\\MethodNotExtendableException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/MethodNotExtendableException.php',
    'Prophecy\\Exception\\Doubler\\MethodNotFoundException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/MethodNotFoundException.php',
    'Prophecy\\Exception\\Doubler\\ReturnByReferenceException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/ReturnByReferenceException.php',
    'Prophecy\\Exception\\Exception' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Exception.php',
    'Prophecy\\Exception\\InvalidArgumentException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/InvalidArgumentException.php',
    'Prophecy\\Exception\\Prediction\\AggregateException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Prediction/AggregateException.php',
    'Prophecy\\Exception\\Prediction\\FailedPredictionException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Prediction/FailedPredictionException.php',
    'Prophecy\\Exception\\Prediction\\NoCallsException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Prediction/NoCallsException.php',
    'Prophecy\\Exception\\Prediction\\PredictionException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Prediction/PredictionException.php',
    'Prophecy\\Exception\\Prediction\\UnexpectedCallsCountException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Prediction/UnexpectedCallsCountException.php',
    'Prophecy\\Exception\\Prediction\\UnexpectedCallsException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Prediction/UnexpectedCallsException.php',
    'Prophecy\\Exception\\Prophecy\\MethodProphecyException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Prophecy/MethodProphecyException.php',
    'Prophecy\\Exception\\Prophecy\\ObjectProphecyException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Prophecy/ObjectProphecyException.php',
    'Prophecy\\Exception\\Prophecy\\ProphecyException' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Exception/Prophecy/ProphecyException.php',
    'Prophecy\\PhpDocumentor\\ClassAndInterfaceTagRetriever' => $vendorDir . '/phpspec/prophecy/src/Prophecy/PhpDocumentor/ClassAndInterfaceTagRetriever.php',
    'Prophecy\\PhpDocumentor\\ClassTagRetriever' => $vendorDir . '/phpspec/prophecy/src/Prophecy/PhpDocumentor/ClassTagRetriever.php',
    'Prophecy\\PhpDocumentor\\LegacyClassTagRetriever' => $vendorDir . '/phpspec/prophecy/src/Prophecy/PhpDocumentor/LegacyClassTagRetriever.php',
    'Prophecy\\PhpDocumentor\\MethodTagRetrieverInterface' => $vendorDir . '/phpspec/prophecy/src/Prophecy/PhpDocumentor/MethodTagRetrieverInterface.php',
    'Prophecy\\Prediction\\CallPrediction' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Prediction/CallPrediction.php',
    'Prophecy\\Prediction\\CallTimesPrediction' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Prediction/CallTimesPrediction.php',
    'Prophecy\\Prediction\\CallbackPrediction' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Prediction/CallbackPrediction.php',
    'Prophecy\\Prediction\\NoCallsPrediction' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Prediction/NoCallsPrediction.php',
    'Prophecy\\Prediction\\PredictionInterface' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Prediction/PredictionInterface.php',
    'Prophecy\\Promise\\CallbackPromise' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Promise/CallbackPromise.php',
    'Prophecy\\Promise\\PromiseInterface' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Promise/PromiseInterface.php',
    'Prophecy\\Promise\\ReturnArgumentPromise' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Promise/ReturnArgumentPromise.php',
    'Prophecy\\Promise\\ReturnPromise' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Promise/ReturnPromise.php',
    'Prophecy\\Promise\\ThrowPromise' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Promise/ThrowPromise.php',
    'Prophecy\\Prophecy\\MethodProphecy' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Prophecy/MethodProphecy.php',
    'Prophecy\\Prophecy\\ObjectProphecy' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Prophecy/ObjectProphecy.php',
    'Prophecy\\Prophecy\\ProphecyInterface' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Prophecy/ProphecyInterface.php',
    'Prophecy\\Prophecy\\ProphecySubjectInterface' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Prophecy/ProphecySubjectInterface.php',
    'Prophecy\\Prophecy\\Revealer' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Prophecy/Revealer.php',
    'Prophecy\\Prophecy\\RevealerInterface' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Prophecy/RevealerInterface.php',
    'Prophecy\\Prophet' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Prophet.php',
    'Prophecy\\Util\\ExportUtil' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Util/ExportUtil.php',
    'Prophecy\\Util\\StringUtil' => $vendorDir . '/phpspec/prophecy/src/Prophecy/Util/StringUtil.php',
    'Psr\\Log\\AbstractLogger' => $vendorDir . '/psr/log/Psr/Log/AbstractLogger.php',
    'Psr\\Log\\InvalidArgumentException' => $vendorDir . '/psr/log/Psr/Log/InvalidArgumentException.php',
    'Psr\\Log\\LogLevel' => $vendorDir . '/psr/log/Psr/Log/LogLevel.php',
    'Psr\\Log\\LoggerAwareInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareInterface.php',
    'Psr\\Log\\LoggerAwareTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareTrait.php',
    'Psr\\Log\\LoggerInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerInterface.php',
    'Psr\\Log\\LoggerTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerTrait.php',
    'Psr\\Log\\NullLogger' => $vendorDir . '/psr/log/Psr/Log/NullLogger.php',
    'Psr\\Log\\Test\\DummyTest' => $vendorDir . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php',
    'Psr\\Log\\Test\\LoggerInterfaceTest' => $vendorDir . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php',
    'Psr\\Log\\Test\\TestLogger' => $vendorDir . '/psr/log/Psr/Log/Test/TestLogger.php',
    'React\\Promise\\CancellablePromiseInterface' => $vendorDir . '/react/promise/src/CancellablePromiseInterface.php',
    'React\\Promise\\CancellationQueue' => $vendorDir . '/react/promise/src/CancellationQueue.php',
    'React\\Promise\\Deferred' => $vendorDir . '/react/promise/src/Deferred.php',
    'React\\Promise\\Exception\\LengthException' => $vendorDir . '/react/promise/src/Exception/LengthException.php',
    'React\\Promise\\ExtendedPromiseInterface' => $vendorDir . '/react/promise/src/ExtendedPromiseInterface.php',
    'React\\Promise\\FulfilledPromise' => $vendorDir . '/react/promise/src/FulfilledPromise.php',
    'React\\Promise\\LazyPromise' => $vendorDir . '/react/promise/src/LazyPromise.php',
    'React\\Promise\\Promise' => $vendorDir . '/react/promise/src/Promise.php',
    'React\\Promise\\PromiseInterface' => $vendorDir . '/react/promise/src/PromiseInterface.php',
    'React\\Promise\\PromisorInterface' => $vendorDir . '/react/promise/src/PromisorInterface.php',
    'React\\Promise\\RejectedPromise' => $vendorDir . '/react/promise/src/RejectedPromise.php',
    'React\\Promise\\UnhandledRejectionException' => $vendorDir . '/react/promise/src/UnhandledRejectionException.php',
    'SebastianBergmann\\CodeCoverage\\CodeCoverage' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage.php',
    'SebastianBergmann\\CodeCoverage\\CoveredCodeNotExecutedException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/CoveredCodeNotExecutedException.php',
    'SebastianBergmann\\CodeCoverage\\Driver\\Driver' => $vendorDir . '/phpunit/php-code-coverage/src/Driver/Driver.php',
    'SebastianBergmann\\CodeCoverage\\Driver\\HHVM' => $vendorDir . '/phpunit/php-code-coverage/src/Driver/HHVM.php',
    'SebastianBergmann\\CodeCoverage\\Driver\\PHPDBG' => $vendorDir . '/phpunit/php-code-coverage/src/Driver/PHPDBG.php',
    'SebastianBergmann\\CodeCoverage\\Driver\\Xdebug' => $vendorDir . '/phpunit/php-code-coverage/src/Driver/Xdebug.php',
    'SebastianBergmann\\CodeCoverage\\Exception' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/Exception.php',
    'SebastianBergmann\\CodeCoverage\\Filter' => $vendorDir . '/phpunit/php-code-coverage/src/Filter.php',
    'SebastianBergmann\\CodeCoverage\\InvalidArgumentException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/InvalidArgumentException.php',
    'SebastianBergmann\\CodeCoverage\\MissingCoversAnnotationException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/MissingCoversAnnotationException.php',
    'SebastianBergmann\\CodeCoverage\\Node\\AbstractNode' => $vendorDir . '/phpunit/php-code-coverage/src/Node/AbstractNode.php',
    'SebastianBergmann\\CodeCoverage\\Node\\Builder' => $vendorDir . '/phpunit/php-code-coverage/src/Node/Builder.php',
    'SebastianBergmann\\CodeCoverage\\Node\\Directory' => $vendorDir . '/phpunit/php-code-coverage/src/Node/Directory.php',
    'SebastianBergmann\\CodeCoverage\\Node\\File' => $vendorDir . '/phpunit/php-code-coverage/src/Node/File.php',
    'SebastianBergmann\\CodeCoverage\\Node\\Iterator' => $vendorDir . '/phpunit/php-code-coverage/src/Node/Iterator.php',
    'SebastianBergmann\\CodeCoverage\\Report\\Clover' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Clover.php',
    'SebastianBergmann\\CodeCoverage\\Report\\Crap4j' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Crap4j.php',
    'SebastianBergmann\\CodeCoverage\\Report\\Html\\Dashboard' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Html/Renderer/Dashboard.php',
    'SebastianBergmann\\CodeCoverage\\Report\\Html\\Directory' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Html/Renderer/Directory.php',
    'SebastianBergmann\\CodeCoverage\\Report\\Html\\Facade' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Html/Facade.php',
    'SebastianBergmann\\CodeCoverage\\Report\\Html\\File' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Html/Renderer/File.php',
    'SebastianBergmann\\CodeCoverage\\Report\\Html\\Renderer' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Html/Renderer.php',
    'SebastianBergmann\\CodeCoverage\\Report\\PHP' => $vendorDir . '/phpunit/php-code-coverage/src/Report/PHP.php',
    'SebastianBergmann\\CodeCoverage\\Report\\Text' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Text.php',
    'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Coverage' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Coverage.php',
    'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Directory' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Directory.php',
    'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Facade' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Facade.php',
    'SebastianBergmann\\CodeCoverage\\Report\\Xml\\File' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/File.php',
    'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Method' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Method.php',
    'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Node' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Node.php',
    'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Project' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Project.php',
    'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Report' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Report.php',
    'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Tests' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Tests.php',
    'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Totals' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Totals.php',
    'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Unit' => $vendorDir . '/phpunit/php-code-coverage/src/Report/Xml/Unit.php',
    'SebastianBergmann\\CodeCoverage\\RuntimeException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/RuntimeException.php',
    'SebastianBergmann\\CodeCoverage\\UnintentionallyCoveredCodeException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/UnintentionallyCoveredCodeException.php',
    'SebastianBergmann\\CodeCoverage\\Util' => $vendorDir . '/phpunit/php-code-coverage/src/Util.php',
    'SebastianBergmann\\CodeUnitReverseLookup\\Wizard' => $vendorDir . '/sebastian/code-unit-reverse-lookup/src/Wizard.php',
    'SebastianBergmann\\Comparator\\ArrayComparator' => $vendorDir . '/sebastian/comparator/src/ArrayComparator.php',
    'SebastianBergmann\\Comparator\\Comparator' => $vendorDir . '/sebastian/comparator/src/Comparator.php',
    'SebastianBergmann\\Comparator\\ComparisonFailure' => $vendorDir . '/sebastian/comparator/src/ComparisonFailure.php',
    'SebastianBergmann\\Comparator\\DOMNodeComparator' => $vendorDir . '/sebastian/comparator/src/DOMNodeComparator.php',
    'SebastianBergmann\\Comparator\\DateTimeComparator' => $vendorDir . '/sebastian/comparator/src/DateTimeComparator.php',
    'SebastianBergmann\\Comparator\\DoubleComparator' => $vendorDir . '/sebastian/comparator/src/DoubleComparator.php',
    'SebastianBergmann\\Comparator\\ExceptionComparator' => $vendorDir . '/sebastian/comparator/src/ExceptionComparator.php',
    'SebastianBergmann\\Comparator\\Factory' => $vendorDir . '/sebastian/comparator/src/Factory.php',
    'SebastianBergmann\\Comparator\\MockObjectComparator' => $vendorDir . '/sebastian/comparator/src/MockObjectComparator.php',
    'SebastianBergmann\\Comparator\\NumericComparator' => $vendorDir . '/sebastian/comparator/src/NumericComparator.php',
    'SebastianBergmann\\Comparator\\ObjectComparator' => $vendorDir . '/sebastian/comparator/src/ObjectComparator.php',
    'SebastianBergmann\\Comparator\\ResourceComparator' => $vendorDir . '/sebastian/comparator/src/ResourceComparator.php',
    'SebastianBergmann\\Comparator\\ScalarComparator' => $vendorDir . '/sebastian/comparator/src/ScalarComparator.php',
    'SebastianBergmann\\Comparator\\SplObjectStorageComparator' => $vendorDir . '/sebastian/comparator/src/SplObjectStorageComparator.php',
    'SebastianBergmann\\Comparator\\TypeComparator' => $vendorDir . '/sebastian/comparator/src/TypeComparator.php',
    'SebastianBergmann\\Diff\\Chunk' => $vendorDir . '/sebastian/diff/src/Chunk.php',
    'SebastianBergmann\\Diff\\Diff' => $vendorDir . '/sebastian/diff/src/Diff.php',
    'SebastianBergmann\\Diff\\Differ' => $vendorDir . '/sebastian/diff/src/Differ.php',
    'SebastianBergmann\\Diff\\LCS\\LongestCommonSubsequence' => $vendorDir . '/sebastian/diff/src/LCS/LongestCommonSubsequence.php',
    'SebastianBergmann\\Diff\\LCS\\MemoryEfficientImplementation' => $vendorDir . '/sebastian/diff/src/LCS/MemoryEfficientLongestCommonSubsequenceImplementation.php',
    'SebastianBergmann\\Diff\\LCS\\TimeEfficientImplementation' => $vendorDir . '/sebastian/diff/src/LCS/TimeEfficientLongestCommonSubsequenceImplementation.php',
    'SebastianBergmann\\Diff\\Line' => $vendorDir . '/sebastian/diff/src/Line.php',
    'SebastianBergmann\\Diff\\Parser' => $vendorDir . '/sebastian/diff/src/Parser.php',
    'SebastianBergmann\\Environment\\Console' => $vendorDir . '/sebastian/environment/src/Console.php',
    'SebastianBergmann\\Environment\\Runtime' => $vendorDir . '/sebastian/environment/src/Runtime.php',
    'SebastianBergmann\\Exporter\\Exporter' => $vendorDir . '/sebastian/exporter/src/Exporter.php',
    'SebastianBergmann\\GlobalState\\Blacklist' => $vendorDir . '/sebastian/global-state/src/Blacklist.php',
    'SebastianBergmann\\GlobalState\\CodeExporter' => $vendorDir . '/sebastian/global-state/src/CodeExporter.php',
    'SebastianBergmann\\GlobalState\\Exception' => $vendorDir . '/sebastian/global-state/src/Exception.php',
    'SebastianBergmann\\GlobalState\\Restorer' => $vendorDir . '/sebastian/global-state/src/Restorer.php',
    'SebastianBergmann\\GlobalState\\RuntimeException' => $vendorDir . '/sebastian/global-state/src/RuntimeException.php',
    'SebastianBergmann\\GlobalState\\Snapshot' => $vendorDir . '/sebastian/global-state/src/Snapshot.php',
    'SebastianBergmann\\ObjectEnumerator\\Enumerator' => $vendorDir . '/sebastian/object-enumerator/src/Enumerator.php',
    'SebastianBergmann\\ObjectEnumerator\\Exception' => $vendorDir . '/sebastian/object-enumerator/src/Exception.php',
    'SebastianBergmann\\ObjectEnumerator\\InvalidArgumentException' => $vendorDir . '/sebastian/object-enumerator/src/InvalidArgumentException.php',
    'SebastianBergmann\\RecursionContext\\Context' => $vendorDir . '/sebastian/recursion-context/src/Context.php',
    'SebastianBergmann\\RecursionContext\\Exception' => $vendorDir . '/sebastian/recursion-context/src/Exception.php',
    'SebastianBergmann\\RecursionContext\\InvalidArgumentException' => $vendorDir . '/sebastian/recursion-context/src/InvalidArgumentException.php',
    'SebastianBergmann\\ResourceOperations\\ResourceOperations' => $vendorDir . '/sebastian/resource-operations/src/ResourceOperations.php',
    'SebastianBergmann\\Version' => $vendorDir . '/sebastian/version/src/Version.php',
    'Symfony\\Component\\Config\\ConfigCache' => $vendorDir . '/symfony/config/ConfigCache.php',
    'Symfony\\Component\\Config\\ConfigCacheFactory' => $vendorDir . '/symfony/config/ConfigCacheFactory.php',
    'Symfony\\Component\\Config\\ConfigCacheFactoryInterface' => $vendorDir . '/symfony/config/ConfigCacheFactoryInterface.php',
    'Symfony\\Component\\Config\\ConfigCacheInterface' => $vendorDir . '/symfony/config/ConfigCacheInterface.php',
    'Symfony\\Component\\Config\\Definition\\ArrayNode' => $vendorDir . '/symfony/config/Definition/ArrayNode.php',
    'Symfony\\Component\\Config\\Definition\\BaseNode' => $vendorDir . '/symfony/config/Definition/BaseNode.php',
    'Symfony\\Component\\Config\\Definition\\BooleanNode' => $vendorDir . '/symfony/config/Definition/BooleanNode.php',
    'Symfony\\Component\\Config\\Definition\\Builder\\ArrayNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/ArrayNodeDefinition.php',
    'Symfony\\Component\\Config\\Definition\\Builder\\BooleanNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/BooleanNodeDefinition.php',
    'Symfony\\Component\\Config\\Definition\\Builder\\BuilderAwareInterface' => $vendorDir . '/symfony/config/Definition/Builder/BuilderAwareInterface.php',
    'Symfony\\Component\\Config\\Definition\\Builder\\EnumNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/EnumNodeDefinition.php',
    'Symfony\\Component\\Config\\Definition\\Builder\\ExprBuilder' => $vendorDir . '/symfony/config/Definition/Builder/ExprBuilder.php',
    'Symfony\\Component\\Config\\Definition\\Builder\\FloatNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/FloatNodeDefinition.php',
    'Symfony\\Component\\Config\\Definition\\Builder\\IntegerNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/IntegerNodeDefinition.php',
    'Symfony\\Component\\Config\\Definition\\Builder\\MergeBuilder' => $vendorDir . '/symfony/config/Definition/Builder/MergeBuilder.php',
    'Symfony\\Component\\Config\\Definition\\Builder\\NodeBuilder' => $vendorDir . '/symfony/config/Definition/Builder/NodeBuilder.php',
    'Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/NodeDefinition.php',
    'Symfony\\Component\\Config\\Definition\\Builder\\NodeParentInterface' => $vendorDir . '/symfony/config/Definition/Builder/NodeParentInterface.php',
    'Symfony\\Component\\Config\\Definition\\Builder\\NormalizationBuilder' => $vendorDir . '/symfony/config/Definition/Builder/NormalizationBuilder.php',
    'Symfony\\Component\\Config\\Definition\\Builder\\NumericNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/NumericNodeDefinition.php',
    'Symfony\\Component\\Config\\Definition\\Builder\\ParentNodeDefinitionInterface' => $vendorDir . '/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php',
    'Symfony\\Component\\Config\\Definition\\Builder\\ScalarNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/ScalarNodeDefinition.php',
    'Symfony\\Component\\Config\\Definition\\Builder\\TreeBuilder' => $vendorDir . '/symfony/config/Definition/Builder/TreeBuilder.php',
    'Symfony\\Component\\Config\\Definition\\Builder\\ValidationBuilder' => $vendorDir . '/symfony/config/Definition/Builder/ValidationBuilder.php',
    'Symfony\\Component\\Config\\Definition\\Builder\\VariableNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/VariableNodeDefinition.php',
    'Symfony\\Component\\Config\\Definition\\ConfigurationInterface' => $vendorDir . '/symfony/config/Definition/ConfigurationInterface.php',
    'Symfony\\Component\\Config\\Definition\\Dumper\\XmlReferenceDumper' => $vendorDir . '/symfony/config/Definition/Dumper/XmlReferenceDumper.php',
    'Symfony\\Component\\Config\\Definition\\Dumper\\YamlReferenceDumper' => $vendorDir . '/symfony/config/Definition/Dumper/YamlReferenceDumper.php',
    'Symfony\\Component\\Config\\Definition\\EnumNode' => $vendorDir . '/symfony/config/Definition/EnumNode.php',
    'Symfony\\Component\\Config\\Definition\\Exception\\DuplicateKeyException' => $vendorDir . '/symfony/config/Definition/Exception/DuplicateKeyException.php',
    'Symfony\\Component\\Config\\Definition\\Exception\\Exception' => $vendorDir . '/symfony/config/Definition/Exception/Exception.php',
    'Symfony\\Component\\Config\\Definition\\Exception\\ForbiddenOverwriteException' => $vendorDir . '/symfony/config/Definition/Exception/ForbiddenOverwriteException.php',
    'Symfony\\Component\\Config\\Definition\\Exception\\InvalidConfigurationException' => $vendorDir . '/symfony/config/Definition/Exception/InvalidConfigurationException.php',
    'Symfony\\Component\\Config\\Definition\\Exception\\InvalidDefinitionException' => $vendorDir . '/symfony/config/Definition/Exception/InvalidDefinitionException.php',
    'Symfony\\Component\\Config\\Definition\\Exception\\InvalidTypeException' => $vendorDir . '/symfony/config/Definition/Exception/InvalidTypeException.php',
    'Symfony\\Component\\Config\\Definition\\Exception\\TreeWithoutRootNodeException' => $vendorDir . '/symfony/config/Definition/Exception/TreeWithoutRootNodeException.php',
    'Symfony\\Component\\Config\\Definition\\Exception\\UnsetKeyException' => $vendorDir . '/symfony/config/Definition/Exception/UnsetKeyException.php',
    'Symfony\\Component\\Config\\Definition\\FloatNode' => $vendorDir . '/symfony/config/Definition/FloatNode.php',
    'Symfony\\Component\\Config\\Definition\\IntegerNode' => $vendorDir . '/symfony/config/Definition/IntegerNode.php',
    'Symfony\\Component\\Config\\Definition\\NodeInterface' => $vendorDir . '/symfony/config/Definition/NodeInterface.php',
    'Symfony\\Component\\Config\\Definition\\NumericNode' => $vendorDir . '/symfony/config/Definition/NumericNode.php',
    'Symfony\\Component\\Config\\Definition\\Processor' => $vendorDir . '/symfony/config/Definition/Processor.php',
    'Symfony\\Component\\Config\\Definition\\PrototypeNodeInterface' => $vendorDir . '/symfony/config/Definition/PrototypeNodeInterface.php',
    'Symfony\\Component\\Config\\Definition\\PrototypedArrayNode' => $vendorDir . '/symfony/config/Definition/PrototypedArrayNode.php',
    'Symfony\\Component\\Config\\Definition\\ScalarNode' => $vendorDir . '/symfony/config/Definition/ScalarNode.php',
    'Symfony\\Component\\Config\\Definition\\VariableNode' => $vendorDir . '/symfony/config/Definition/VariableNode.php',
    'Symfony\\Component\\Config\\Exception\\FileLoaderImportCircularReferenceException' => $vendorDir . '/symfony/config/Exception/FileLoaderImportCircularReferenceException.php',
    'Symfony\\Component\\Config\\Exception\\FileLoaderLoadException' => $vendorDir . '/symfony/config/Exception/FileLoaderLoadException.php',
    'Symfony\\Component\\Config\\Exception\\FileLocatorFileNotFoundException' => $vendorDir . '/symfony/config/Exception/FileLocatorFileNotFoundException.php',
    'Symfony\\Component\\Config\\Exception\\LoaderLoadException' => $vendorDir . '/symfony/config/Exception/LoaderLoadException.php',
    'Symfony\\Component\\Config\\FileLocator' => $vendorDir . '/symfony/config/FileLocator.php',
    'Symfony\\Component\\Config\\FileLocatorInterface' => $vendorDir . '/symfony/config/FileLocatorInterface.php',
    'Symfony\\Component\\Config\\Loader\\DelegatingLoader' => $vendorDir . '/symfony/config/Loader/DelegatingLoader.php',
    'Symfony\\Component\\Config\\Loader\\FileLoader' => $vendorDir . '/symfony/config/Loader/FileLoader.php',
    'Symfony\\Component\\Config\\Loader\\GlobFileLoader' => $vendorDir . '/symfony/config/Loader/GlobFileLoader.php',
    'Symfony\\Component\\Config\\Loader\\Loader' => $vendorDir . '/symfony/config/Loader/Loader.php',
    'Symfony\\Component\\Config\\Loader\\LoaderInterface' => $vendorDir . '/symfony/config/Loader/LoaderInterface.php',
    'Symfony\\Component\\Config\\Loader\\LoaderResolver' => $vendorDir . '/symfony/config/Loader/LoaderResolver.php',
    'Symfony\\Component\\Config\\Loader\\LoaderResolverInterface' => $vendorDir . '/symfony/config/Loader/LoaderResolverInterface.php',
    'Symfony\\Component\\Config\\ResourceCheckerConfigCache' => $vendorDir . '/symfony/config/ResourceCheckerConfigCache.php',
    'Symfony\\Component\\Config\\ResourceCheckerConfigCacheFactory' => $vendorDir . '/symfony/config/ResourceCheckerConfigCacheFactory.php',
    'Symfony\\Component\\Config\\ResourceCheckerInterface' => $vendorDir . '/symfony/config/ResourceCheckerInterface.php',
    'Symfony\\Component\\Config\\Resource\\ClassExistenceResource' => $vendorDir . '/symfony/config/Resource/ClassExistenceResource.php',
    'Symfony\\Component\\Config\\Resource\\ComposerResource' => $vendorDir . '/symfony/config/Resource/ComposerResource.php',
    'Symfony\\Component\\Config\\Resource\\DirectoryResource' => $vendorDir . '/symfony/config/Resource/DirectoryResource.php',
    'Symfony\\Component\\Config\\Resource\\FileExistenceResource' => $vendorDir . '/symfony/config/Resource/FileExistenceResource.php',
    'Symfony\\Component\\Config\\Resource\\FileResource' => $vendorDir . '/symfony/config/Resource/FileResource.php',
    'Symfony\\Component\\Config\\Resource\\GlobResource' => $vendorDir . '/symfony/config/Resource/GlobResource.php',
    'Symfony\\Component\\Config\\Resource\\ReflectionClassResource' => $vendorDir . '/symfony/config/Resource/ReflectionClassResource.php',
    'Symfony\\Component\\Config\\Resource\\ResourceInterface' => $vendorDir . '/symfony/config/Resource/ResourceInterface.php',
    'Symfony\\Component\\Config\\Resource\\SelfCheckingResourceChecker' => $vendorDir . '/symfony/config/Resource/SelfCheckingResourceChecker.php',
    'Symfony\\Component\\Config\\Resource\\SelfCheckingResourceInterface' => $vendorDir . '/symfony/config/Resource/SelfCheckingResourceInterface.php',
    'Symfony\\Component\\Config\\Util\\Exception\\InvalidXmlException' => $vendorDir . '/symfony/config/Util/Exception/InvalidXmlException.php',
    'Symfony\\Component\\Config\\Util\\Exception\\XmlParsingException' => $vendorDir . '/symfony/config/Util/Exception/XmlParsingException.php',
    'Symfony\\Component\\Config\\Util\\XmlUtils' => $vendorDir . '/symfony/config/Util/XmlUtils.php',
    'Symfony\\Component\\Console\\Application' => $vendorDir . '/symfony/console/Application.php',
    'Symfony\\Component\\Console\\CommandLoader\\CommandLoaderInterface' => $vendorDir . '/symfony/console/CommandLoader/CommandLoaderInterface.php',
    'Symfony\\Component\\Console\\CommandLoader\\ContainerCommandLoader' => $vendorDir . '/symfony/console/CommandLoader/ContainerCommandLoader.php',
    'Symfony\\Component\\Console\\CommandLoader\\FactoryCommandLoader' => $vendorDir . '/symfony/console/CommandLoader/FactoryCommandLoader.php',
    'Symfony\\Component\\Console\\Command\\Command' => $vendorDir . '/symfony/console/Command/Command.php',
    'Symfony\\Component\\Console\\Command\\HelpCommand' => $vendorDir . '/symfony/console/Command/HelpCommand.php',
    'Symfony\\Component\\Console\\Command\\ListCommand' => $vendorDir . '/symfony/console/Command/ListCommand.php',
    'Symfony\\Component\\Console\\Command\\LockableTrait' => $vendorDir . '/symfony/console/Command/LockableTrait.php',
    'Symfony\\Component\\Console\\ConsoleEvents' => $vendorDir . '/symfony/console/ConsoleEvents.php',
    'Symfony\\Component\\Console\\DependencyInjection\\AddConsoleCommandPass' => $vendorDir . '/symfony/console/DependencyInjection/AddConsoleCommandPass.php',
    'Symfony\\Component\\Console\\Descriptor\\ApplicationDescription' => $vendorDir . '/symfony/console/Descriptor/ApplicationDescription.php',
    'Symfony\\Component\\Console\\Descriptor\\Descriptor' => $vendorDir . '/symfony/console/Descriptor/Descriptor.php',
    'Symfony\\Component\\Console\\Descriptor\\DescriptorInterface' => $vendorDir . '/symfony/console/Descriptor/DescriptorInterface.php',
    'Symfony\\Component\\Console\\Descriptor\\JsonDescriptor' => $vendorDir . '/symfony/console/Descriptor/JsonDescriptor.php',
    'Symfony\\Component\\Console\\Descriptor\\MarkdownDescriptor' => $vendorDir . '/symfony/console/Descriptor/MarkdownDescriptor.php',
    'Symfony\\Component\\Console\\Descriptor\\TextDescriptor' => $vendorDir . '/symfony/console/Descriptor/TextDescriptor.php',
    'Symfony\\Component\\Console\\Descriptor\\XmlDescriptor' => $vendorDir . '/symfony/console/Descriptor/XmlDescriptor.php',
    'Symfony\\Component\\Console\\EventListener\\ErrorListener' => $vendorDir . '/symfony/console/EventListener/ErrorListener.php',
    'Symfony\\Component\\Console\\Event\\ConsoleCommandEvent' => $vendorDir . '/symfony/console/Event/ConsoleCommandEvent.php',
    'Symfony\\Component\\Console\\Event\\ConsoleErrorEvent' => $vendorDir . '/symfony/console/Event/ConsoleErrorEvent.php',
    'Symfony\\Component\\Console\\Event\\ConsoleEvent' => $vendorDir . '/symfony/console/Event/ConsoleEvent.php',
    'Symfony\\Component\\Console\\Event\\ConsoleExceptionEvent' => $vendorDir . '/symfony/console/Event/ConsoleExceptionEvent.php',
    'Symfony\\Component\\Console\\Event\\ConsoleTerminateEvent' => $vendorDir . '/symfony/console/Event/ConsoleTerminateEvent.php',
    'Symfony\\Component\\Console\\Exception\\CommandNotFoundException' => $vendorDir . '/symfony/console/Exception/CommandNotFoundException.php',
    'Symfony\\Component\\Console\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/console/Exception/ExceptionInterface.php',
    'Symfony\\Component\\Console\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/console/Exception/InvalidArgumentException.php',
    'Symfony\\Component\\Console\\Exception\\InvalidOptionException' => $vendorDir . '/symfony/console/Exception/InvalidOptionException.php',
    'Symfony\\Component\\Console\\Exception\\LogicException' => $vendorDir . '/symfony/console/Exception/LogicException.php',
    'Symfony\\Component\\Console\\Exception\\RuntimeException' => $vendorDir . '/symfony/console/Exception/RuntimeException.php',
    'Symfony\\Component\\Console\\Formatter\\OutputFormatter' => $vendorDir . '/symfony/console/Formatter/OutputFormatter.php',
    'Symfony\\Component\\Console\\Formatter\\OutputFormatterInterface' => $vendorDir . '/symfony/console/Formatter/OutputFormatterInterface.php',
    'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyle' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyle.php',
    'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleInterface' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyleInterface.php',
    'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleStack' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyleStack.php',
    'Symfony\\Component\\Console\\Helper\\DebugFormatterHelper' => $vendorDir . '/symfony/console/Helper/DebugFormatterHelper.php',
    'Symfony\\Component\\Console\\Helper\\DescriptorHelper' => $vendorDir . '/symfony/console/Helper/DescriptorHelper.php',
    'Symfony\\Component\\Console\\Helper\\FormatterHelper' => $vendorDir . '/symfony/console/Helper/FormatterHelper.php',
    'Symfony\\Component\\Console\\Helper\\Helper' => $vendorDir . '/symfony/console/Helper/Helper.php',
    'Symfony\\Component\\Console\\Helper\\HelperInterface' => $vendorDir . '/symfony/console/Helper/HelperInterface.php',
    'Symfony\\Component\\Console\\Helper\\HelperSet' => $vendorDir . '/symfony/console/Helper/HelperSet.php',
    'Symfony\\Component\\Console\\Helper\\InputAwareHelper' => $vendorDir . '/symfony/console/Helper/InputAwareHelper.php',
    'Symfony\\Component\\Console\\Helper\\ProcessHelper' => $vendorDir . '/symfony/console/Helper/ProcessHelper.php',
    'Symfony\\Component\\Console\\Helper\\ProgressBar' => $vendorDir . '/symfony/console/Helper/ProgressBar.php',
    'Symfony\\Component\\Console\\Helper\\ProgressIndicator' => $vendorDir . '/symfony/console/Helper/ProgressIndicator.php',
    'Symfony\\Component\\Console\\Helper\\QuestionHelper' => $vendorDir . '/symfony/console/Helper/QuestionHelper.php',
    'Symfony\\Component\\Console\\Helper\\SymfonyQuestionHelper' => $vendorDir . '/symfony/console/Helper/SymfonyQuestionHelper.php',
    'Symfony\\Component\\Console\\Helper\\Table' => $vendorDir . '/symfony/console/Helper/Table.php',
    'Symfony\\Component\\Console\\Helper\\TableCell' => $vendorDir . '/symfony/console/Helper/TableCell.php',
    'Symfony\\Component\\Console\\Helper\\TableSeparator' => $vendorDir . '/symfony/console/Helper/TableSeparator.php',
    'Symfony\\Component\\Console\\Helper\\TableStyle' => $vendorDir . '/symfony/console/Helper/TableStyle.php',
    'Symfony\\Component\\Console\\Input\\ArgvInput' => $vendorDir . '/symfony/console/Input/ArgvInput.php',
    'Symfony\\Component\\Console\\Input\\ArrayInput' => $vendorDir . '/symfony/console/Input/ArrayInput.php',
    'Symfony\\Component\\Console\\Input\\Input' => $vendorDir . '/symfony/console/Input/Input.php',
    'Symfony\\Component\\Console\\Input\\InputArgument' => $vendorDir . '/symfony/console/Input/InputArgument.php',
    'Symfony\\Component\\Console\\Input\\InputAwareInterface' => $vendorDir . '/symfony/console/Input/InputAwareInterface.php',
    'Symfony\\Component\\Console\\Input\\InputDefinition' => $vendorDir . '/symfony/console/Input/InputDefinition.php',
    'Symfony\\Component\\Console\\Input\\InputInterface' => $vendorDir . '/symfony/console/Input/InputInterface.php',
    'Symfony\\Component\\Console\\Input\\InputOption' => $vendorDir . '/symfony/console/Input/InputOption.php',
    'Symfony\\Component\\Console\\Input\\StreamableInputInterface' => $vendorDir . '/symfony/console/Input/StreamableInputInterface.php',
    'Symfony\\Component\\Console\\Input\\StringInput' => $vendorDir . '/symfony/console/Input/StringInput.php',
    'Symfony\\Component\\Console\\Logger\\ConsoleLogger' => $vendorDir . '/symfony/console/Logger/ConsoleLogger.php',
    'Symfony\\Component\\Console\\Output\\BufferedOutput' => $vendorDir . '/symfony/console/Output/BufferedOutput.php',
    'Symfony\\Component\\Console\\Output\\ConsoleOutput' => $vendorDir . '/symfony/console/Output/ConsoleOutput.php',
    'Symfony\\Component\\Console\\Output\\ConsoleOutputInterface' => $vendorDir . '/symfony/console/Output/ConsoleOutputInterface.php',
    'Symfony\\Component\\Console\\Output\\NullOutput' => $vendorDir . '/symfony/console/Output/NullOutput.php',
    'Symfony\\Component\\Console\\Output\\Output' => $vendorDir . '/symfony/console/Output/Output.php',
    'Symfony\\Component\\Console\\Output\\OutputInterface' => $vendorDir . '/symfony/console/Output/OutputInterface.php',
    'Symfony\\Component\\Console\\Output\\StreamOutput' => $vendorDir . '/symfony/console/Output/StreamOutput.php',
    'Symfony\\Component\\Console\\Question\\ChoiceQuestion' => $vendorDir . '/symfony/console/Question/ChoiceQuestion.php',
    'Symfony\\Component\\Console\\Question\\ConfirmationQuestion' => $vendorDir . '/symfony/console/Question/ConfirmationQuestion.php',
    'Symfony\\Component\\Console\\Question\\Question' => $vendorDir . '/symfony/console/Question/Question.php',
    'Symfony\\Component\\Console\\Style\\OutputStyle' => $vendorDir . '/symfony/console/Style/OutputStyle.php',
    'Symfony\\Component\\Console\\Style\\StyleInterface' => $vendorDir . '/symfony/console/Style/StyleInterface.php',
    'Symfony\\Component\\Console\\Style\\SymfonyStyle' => $vendorDir . '/symfony/console/Style/SymfonyStyle.php',
    'Symfony\\Component\\Console\\Terminal' => $vendorDir . '/symfony/console/Terminal.php',
    'Symfony\\Component\\Console\\Tester\\ApplicationTester' => $vendorDir . '/symfony/console/Tester/ApplicationTester.php',
    'Symfony\\Component\\Console\\Tester\\CommandTester' => $vendorDir . '/symfony/console/Tester/CommandTester.php',
    'Symfony\\Component\\Debug\\BufferingLogger' => $vendorDir . '/symfony/debug/BufferingLogger.php',
    'Symfony\\Component\\Debug\\Debug' => $vendorDir . '/symfony/debug/Debug.php',
    'Symfony\\Component\\Debug\\DebugClassLoader' => $vendorDir . '/symfony/debug/DebugClassLoader.php',
    'Symfony\\Component\\Debug\\ErrorHandler' => $vendorDir . '/symfony/debug/ErrorHandler.php',
    'Symfony\\Component\\Debug\\ExceptionHandler' => $vendorDir . '/symfony/debug/ExceptionHandler.php',
    'Symfony\\Component\\Debug\\Exception\\ClassNotFoundException' => $vendorDir . '/symfony/debug/Exception/ClassNotFoundException.php',
    'Symfony\\Component\\Debug\\Exception\\FatalErrorException' => $vendorDir . '/symfony/debug/Exception/FatalErrorException.php',
    'Symfony\\Component\\Debug\\Exception\\FatalThrowableError' => $vendorDir . '/symfony/debug/Exception/FatalThrowableError.php',
    'Symfony\\Component\\Debug\\Exception\\FlattenException' => $vendorDir . '/symfony/debug/Exception/FlattenException.php',
    'Symfony\\Component\\Debug\\Exception\\OutOfMemoryException' => $vendorDir . '/symfony/debug/Exception/OutOfMemoryException.php',
    'Symfony\\Component\\Debug\\Exception\\SilencedErrorContext' => $vendorDir . '/symfony/debug/Exception/SilencedErrorContext.php',
    'Symfony\\Component\\Debug\\Exception\\UndefinedFunctionException' => $vendorDir . '/symfony/debug/Exception/UndefinedFunctionException.php',
    'Symfony\\Component\\Debug\\Exception\\UndefinedMethodException' => $vendorDir . '/symfony/debug/Exception/UndefinedMethodException.php',
    'Symfony\\Component\\Debug\\FatalErrorHandler\\ClassNotFoundFatalErrorHandler' => $vendorDir . '/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php',
    'Symfony\\Component\\Debug\\FatalErrorHandler\\FatalErrorHandlerInterface' => $vendorDir . '/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php',
    'Symfony\\Component\\Debug\\FatalErrorHandler\\UndefinedFunctionFatalErrorHandler' => $vendorDir . '/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php',
    'Symfony\\Component\\Debug\\FatalErrorHandler\\UndefinedMethodFatalErrorHandler' => $vendorDir . '/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php',
    'Symfony\\Component\\Dotenv\\Dotenv' => $vendorDir . '/symfony/dotenv/Dotenv.php',
    'Symfony\\Component\\Dotenv\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/dotenv/Exception/ExceptionInterface.php',
    'Symfony\\Component\\Dotenv\\Exception\\FormatException' => $vendorDir . '/symfony/dotenv/Exception/FormatException.php',
    'Symfony\\Component\\Dotenv\\Exception\\FormatExceptionContext' => $vendorDir . '/symfony/dotenv/Exception/FormatExceptionContext.php',
    'Symfony\\Component\\Dotenv\\Exception\\PathException' => $vendorDir . '/symfony/dotenv/Exception/PathException.php',
    'Symfony\\Component\\Filesystem\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/filesystem/Exception/ExceptionInterface.php',
    'Symfony\\Component\\Filesystem\\Exception\\FileNotFoundException' => $vendorDir . '/symfony/filesystem/Exception/FileNotFoundException.php',
    'Symfony\\Component\\Filesystem\\Exception\\IOException' => $vendorDir . '/symfony/filesystem/Exception/IOException.php',
    'Symfony\\Component\\Filesystem\\Exception\\IOExceptionInterface' => $vendorDir . '/symfony/filesystem/Exception/IOExceptionInterface.php',
    'Symfony\\Component\\Filesystem\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/filesystem/Exception/InvalidArgumentException.php',
    'Symfony\\Component\\Filesystem\\Filesystem' => $vendorDir . '/symfony/filesystem/Filesystem.php',
    'Symfony\\Component\\Yaml\\Command\\LintCommand' => $vendorDir . '/symfony/yaml/Command/LintCommand.php',
    'Symfony\\Component\\Yaml\\Dumper' => $vendorDir . '/symfony/yaml/Dumper.php',
    'Symfony\\Component\\Yaml\\Escaper' => $vendorDir . '/symfony/yaml/Escaper.php',
    'Symfony\\Component\\Yaml\\Exception\\DumpException' => $vendorDir . '/symfony/yaml/Exception/DumpException.php',
    'Symfony\\Component\\Yaml\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/yaml/Exception/ExceptionInterface.php',
    'Symfony\\Component\\Yaml\\Exception\\ParseException' => $vendorDir . '/symfony/yaml/Exception/ParseException.php',
    'Symfony\\Component\\Yaml\\Exception\\RuntimeException' => $vendorDir . '/symfony/yaml/Exception/RuntimeException.php',
    'Symfony\\Component\\Yaml\\Inline' => $vendorDir . '/symfony/yaml/Inline.php',
    'Symfony\\Component\\Yaml\\Parser' => $vendorDir . '/symfony/yaml/Parser.php',
    'Symfony\\Component\\Yaml\\Tag\\TaggedValue' => $vendorDir . '/symfony/yaml/Tag/TaggedValue.php',
    'Symfony\\Component\\Yaml\\Unescaper' => $vendorDir . '/symfony/yaml/Unescaper.php',
    'Symfony\\Component\\Yaml\\Yaml' => $vendorDir . '/symfony/yaml/Yaml.php',
    'Symfony\\Polyfill\\Ctype\\Ctype' => $vendorDir . '/symfony/polyfill-ctype/Ctype.php',
    'Symfony\\Polyfill\\Mbstring\\Mbstring' => $vendorDir . '/symfony/polyfill-mbstring/Mbstring.php',
    'Text_Template' => $vendorDir . '/phpunit/php-text-template/src/Template.php',
    'Webmozart\\Assert\\Assert' => $vendorDir . '/webmozart/assert/src/Assert.php',
    'com\\linways\\base\\cache\\LinCache' => $vendorDir . '/linways/linways-base/src/com/linways/base/cache/LinCache.php',
    'com\\linways\\base\\connection\\IMySqlConnector' => $vendorDir . '/linways/linways-base/src/com/linways/base/connection/IMySqlConnector.php',
    'com\\linways\\base\\connection\\MySqlConnector' => $vendorDir . '/linways/linways-base/src/com/linways/base/connection/MySqlConnector.php',
    'com\\linways\\base\\connection\\MySqlQuery' => $vendorDir . '/linways/linways-base/src/com/linways/base/connection/MySqlQuery.php',
    'com\\linways\\base\\constant\\UserType' => $vendorDir . '/linways/linways-base/src/com/linways/base/constant/UserType.php',
    'com\\linways\\base\\dto\\AjaxPaginator' => $vendorDir . '/linways/linways-base/src/com/linways/base/dto/AjaxPaginator.php',
    'com\\linways\\base\\dto\\BaseDTO' => $vendorDir . '/linways/linways-base/src/com/linways/base/dto/BaseDTO.php',
    'com\\linways\\base\\dto\\Email' => $vendorDir . '/linways/linways-base/src/com/linways/base/dto/Email.php',
    'com\\linways\\base\\dto\\EmailAttachment' => $vendorDir . '/linways/linways-base/src/com/linways/base/dto/EmailAttachment.php',
    'com\\linways\\base\\dto\\EmailCC' => $vendorDir . '/linways/linways-base/src/com/linways/base/dto/EmailTo.php',
    'com\\linways\\base\\dto\\EmailFrom' => $vendorDir . '/linways/linways-base/src/com/linways/base/dto/EmailCC.php',
    'com\\linways\\base\\dto\\EmailTo' => $vendorDir . '/linways/linways-base/src/com/linways/base/dto/EmailFrom.php',
    'com\\linways\\base\\dto\\MySqlResult' => $vendorDir . '/linways/linways-base/src/com/linways/base/dto/MySqlResult.php',
    'com\\linways\\base\\dto\\PageSupport' => $vendorDir . '/linways/linways-base/src/com/linways/base/dto/PageSupport.php',
    'com\\linways\\base\\dto\\Toggle' => $vendorDir . '/linways/linways-base/src/com/linways/base/dto/Toggle.php',
    'com\\linways\\base\\dto\\sms\\SMS' => $vendorDir . '/linways/linways-base/src/com/linways/base/dto/sms/SMS.php',
    'com\\linways\\base\\dto\\sms\\SMSProvider' => $vendorDir . '/linways/linways-base/src/com/linways/base/dto/sms/SMSProvider.php',
    'com\\linways\\base\\dto\\sms\\SMSProviderAttribute' => $vendorDir . '/linways/linways-base/src/com/linways/base/dto/sms/SMSProviderAttribute.php',
    'com\\linways\\base\\dto\\sms\\SMSProviderAttributeValue' => $vendorDir . '/linways/linways-base/src/com/linways/base/dto/sms/SMSProviderAttributeValue.php',
    'com\\linways\\base\\dto\\sms\\SMSProviderModule' => $vendorDir . '/linways/linways-base/src/com/linways/base/dto/sms/SMSProviderModule.php',
    'com\\linways\\base\\dto\\sms\\SMSResponse' => $vendorDir . '/linways/linways-base/src/com/linways/base/dto/sms/SMSResponse.php',
    'com\\linways\\base\\email\\PHPMailer' => $vendorDir . '/linways/linways-base/src/com/linways/base/email/PHPMailer.php',
    'com\\linways\\base\\email\\SMTP' => $vendorDir . '/linways/linways-base/src/com/linways/base/email/SMTP.php',
    'com\\linways\\base\\exception\\CoreException' => $vendorDir . '/linways/linways-base/src/com/linways/base/exception/CoreException.php',
    'com\\linways\\base\\exception\\EmailException' => $vendorDir . '/linways/linways-base/src/com/linways/base/exception/EmailException.php',
    'com\\linways\\base\\exception\\MapperException' => $vendorDir . '/linways/linways-base/src/com/linways/base/exception/MapperException.php',
    'com\\linways\\base\\exception\\SMSException' => $vendorDir . '/linways/linways-base/src/com/linways/base/exception/SMSException.php',
    'com\\linways\\base\\exception\\SQLException' => $vendorDir . '/linways/linways-base/src/com/linways/base/exception/SQLException.php',
    'com\\linways\\base\\exception\\TaskQueueException' => $vendorDir . '/linways/linways-base/src/com/linways/base/exception/TaskQueueException.php',
    'com\\linways\\base\\helper\\ResultHandler' => $vendorDir . '/linways/linways-base/src/com/linways/base/helper/ResultHandler.php',
    'com\\linways\\base\\hooks\\LinwaysHooks' => $vendorDir . '/linways/linways-base/src/com/linways/base/hooks/LinwaysHooks.php',
    'com\\linways\\base\\logging\\BaseEntity' => $vendorDir . '/linways/linways-base/src/com/linways/base/logging/BaseEntity.php',
    'com\\linways\\base\\logging\\ElasticHandler' => $vendorDir . '/linways/linways-base/src/com/linways/base/logging/ElasticHandler.php',
    'com\\linways\\base\\logging\\Logger' => $vendorDir . '/linways/linways-base/src/com/linways/base/logging/Logger.php',
    'com\\linways\\base\\mapper\\IMapper' => $vendorDir . '/linways/linways-base/src/com/linways/base/mapper/IMapper.php',
    'com\\linways\\base\\mapper\\Result' => $vendorDir . '/linways/linways-base/src/com/linways/base/mapper/Result.php',
    'com\\linways\\base\\mapper\\ResultMap' => $vendorDir . '/linways/linways-base/src/com/linways/base/mapper/ResultMap.php',
    'com\\linways\\base\\mcrypt\\CryptAES' => $vendorDir . '/linways/linways-base/src/com/linways/base/mcrypt/CryptAES.php',
    'com\\linways\\base\\messages\\CommonMessages' => $vendorDir . '/linways/linways-base/src/com/linways/base/messages/CommonMessages.php',
    'com\\linways\\base\\queue\\Queue' => $vendorDir . '/linways/linways-base/src/com/linways/base/queue/Queue.php',
    'com\\linways\\base\\request\\BaseRequest' => $vendorDir . '/linways/linways-base/src/com/linways/base/request/BaseRequest.php',
    'com\\linways\\base\\request\\ScheduleRequest' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/request/ScheduleRequest.php',
    'com\\linways\\base\\response\\BaseResponse' => $vendorDir . '/linways/linways-base/src/com/linways/base/response/BaseResponse.php',
    'com\\linways\\base\\service\\EmailService' => $vendorDir . '/linways/linways-base/src/com/linways/base/service/EmailService.php',
    'com\\linways\\base\\soap\\XMLSchema' => $vendorDir . '/linways/linways-base/src/com/linways/base/soap/XMLSchema.php',
    'com\\linways\\base\\soap\\nusoap_base' => $vendorDir . '/linways/linways-base/src/com/linways/base/soap/nusoap_base.php',
    'com\\linways\\base\\soap\\nusoap_client' => $vendorDir . '/linways/linways-base/src/com/linways/base/soap/nusoap_client.php',
    'com\\linways\\base\\soap\\nusoap_fault' => $vendorDir . '/linways/linways-base/src/com/linways/base/soap/nusoap_fault.php',
    'com\\linways\\base\\soap\\nusoap_parser' => $vendorDir . '/linways/linways-base/src/com/linways/base/soap/nusoap_parser.php',
    'com\\linways\\base\\soap\\nusoap_server' => $vendorDir . '/linways/linways-base/src/com/linways/base/soap/nusoap_server.php',
    'com\\linways\\base\\soap\\nusoap_xmlschema' => $vendorDir . '/linways/linways-base/src/com/linways/base/soap/nusoap_xmlschema.php',
    'com\\linways\\base\\soap\\soap_fault' => $vendorDir . '/linways/linways-base/src/com/linways/base/soap/soap_fault.php',
    'com\\linways\\base\\soap\\soap_parser' => $vendorDir . '/linways/linways-base/src/com/linways/base/soap/soap_parser.php',
    'com\\linways\\base\\soap\\soap_server' => $vendorDir . '/linways/linways-base/src/com/linways/base/soap/soap_server.php',
    'com\\linways\\base\\soap\\soap_transport_http' => $vendorDir . '/linways/linways-base/src/com/linways/base/soap/soap_transport_http.php',
    'com\\linways\\base\\soap\\soapclient' => $vendorDir . '/linways/linways-base/src/com/linways/base/soap/soapclient.php',
    'com\\linways\\base\\soap\\soapval' => $vendorDir . '/linways/linways-base/src/com/linways/base/soap/soapval.php',
    'com\\linways\\base\\soap\\wsdl' => $vendorDir . '/linways/linways-base/src/com/linways/base/soap/wsdl.php',
    'com\\linways\\base\\test\\BaseTestCase' => $vendorDir . '/linways/linways-base/src/com/linways/base/testcase/BaseTestCase.php',
    'com\\linways\\base\\toggles\\AbstractToggleMigration' => $vendorDir . '/linways/linways-base/src/com/linways/base/toggles/AbstractToggleMigration.php',
    'com\\linways\\base\\toggles\\FeatureToggle' => $vendorDir . '/linways/linways-base/src/com/linways/base/toggles/FeatureToggle.php',
    'com\\linways\\base\\toggles\\FeatureToggleInterface' => $vendorDir . '/linways/linways-base/src/com/linways/base/toggles/FeatureToggleInterface.php',
    'com\\linways\\base\\util\\CalendarUtil' => $vendorDir . '/linways/linways-base/src/com/linways/base/util/CalendarUtil.php',
    'com\\linways\\base\\util\\CommonUtil' => $vendorDir . '/linways/linways-base/src/com/linways/base/util/CommonUtil.php',
    'com\\linways\\base\\util\\MakeSingletonTrait' => $vendorDir . '/linways/linways-base/src/com/linways/base/util/MakeSingletonTrait.php',
    'com\\linways\\base\\util\\MapperUtil' => $vendorDir . '/linways/linways-base/src/com/linways/base/util/MapperUtil.php',
    'com\\linways\\base\\util\\MySqlUtil' => $vendorDir . '/linways/linways-base/src/com/linways/base/util/MySqlUtil.php',
    'com\\linways\\base\\util\\ObjectUtil' => $vendorDir . '/linways/linways-base/src/com/linways/base/util/ObjectUtil.php',
    'com\\linways\\base\\util\\PhoneUtil' => $vendorDir . '/linways/linways-base/src/com/linways/base/util/PhoneUtil.php',
    'com\\linways\\base\\util\\RequestUtil' => $vendorDir . '/linways/linways-base/src/com/linways/base/util/RequestUtil.php',
    'com\\linways\\base\\util\\SecurityUtils' => $vendorDir . '/linways/linways-base/src/com/linways/base/util/SecurityUtils.php',
    'com\\linways\\base\\util\\StringUtil' => $vendorDir . '/linways/linways-base/src/com/linways/base/util/StringUtil.php',
    'com\\linways\\nucleus\\core\\constants\\PermissionConstants' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/constants/PermissionConstants.php',
    'com\\linways\\nucleus\\core\\constants\\Scope' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/constants/Scope.php',
    'com\\linways\\nucleus\\core\\constants\\SingleSignonConstant' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/constants/SingleSignonConstant.php',
    'com\\linways\\nucleus\\core\\dto\\Addon' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/Addon.php',
    'com\\linways\\nucleus\\core\\dto\\AddonAttribute' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/AddonAttribute.php',
    'com\\linways\\nucleus\\core\\dto\\AddonAttributeValue' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/AddonAttributeValue.php',
    'com\\linways\\nucleus\\core\\dto\\AddonDataStore' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/AddonDataStore.php',
    'com\\linways\\nucleus\\core\\dto\\AddonEnabled' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/AddonEnabled.php',
    'com\\linways\\nucleus\\core\\dto\\ApiOrganizationDetails' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/ApiOrganizationDetails.php',
    'com\\linways\\nucleus\\core\\dto\\Client' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/Client.php',
    'com\\linways\\nucleus\\core\\dto\\College' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/College.php',
    'com\\linways\\nucleus\\core\\dto\\Expense' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/Expense.php',
    'com\\linways\\nucleus\\core\\dto\\ExpenseCategory' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/ExpenseCategory.php',
    'com\\linways\\nucleus\\core\\dto\\ExpenseRecurring' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/ExpenseRecurring.php',
    'com\\linways\\nucleus\\core\\dto\\ExpenseRecurringAmount' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/ExpenseRecurringAmount.php',
    'com\\linways\\nucleus\\core\\dto\\ProductVariant' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/ProductVariant.php',
    'com\\linways\\nucleus\\core\\dto\\ScheduledTask' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/ScheduledTask.php',
    'com\\linways\\nucleus\\core\\dto\\SingleSignonLog' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/SingleSignonLog.php',
    'com\\linways\\nucleus\\core\\dto\\user\\User' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/user/User.php',
    'com\\linways\\nucleus\\core\\exception\\NucleusException' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/exception/NucleusException.php',
    'com\\linways\\nucleus\\core\\exception\\SchedulerException' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/exception/SchedulerException.php',
    'com\\linways\\nucleus\\core\\exception\\TaskQueueWorkerException' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/exception/TaskQueueWorkerException.php',
    'com\\linways\\nucleus\\core\\mapper\\AddonDataStoreMapper' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/mapper/AddonDataStoreMapper.php',
    'com\\linways\\nucleus\\core\\mapper\\AddonServiceMapper' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/mapper/AddonServiceMapper.php',
    'com\\linways\\nucleus\\core\\mapper\\ClientServiceMapper' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/mapper/ClientServiceMapper.php',
    'com\\linways\\nucleus\\core\\mapper\\CollegeServiceMapper' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/mapper/CollegeServiceMapper.php',
    'com\\linways\\nucleus\\core\\mapper\\ExpenseServiceMapper' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/mapper/ExpenseServiceMapper.php',
    'com\\linways\\nucleus\\core\\mapper\\ProductVariantServiceMapper' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/mapper/ProductVariantServiceMapper.php',
    'com\\linways\\nucleus\\core\\mapper\\RecurringExpenseServiceMapper' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/mapper/RecurringExpenseServiceMapper.php',
    'com\\linways\\nucleus\\core\\mapper\\SchedulerServiceMapper' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/mapper/ScheduleServiceMapper.php',
    'com\\linways\\nucleus\\core\\service\\APIAccessTokenService' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/service/APIAccessTokenService.php',
    'com\\linways\\nucleus\\core\\service\\AddonAttributeService' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/service/AddonAttributeService.php',
    'com\\linways\\nucleus\\core\\service\\AddonDataStoreSevice' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/service/AddonDataStoreSevice.php',
    'com\\linways\\nucleus\\core\\service\\AddonService' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/service/AddonService.php',
    'com\\linways\\nucleus\\core\\service\\BaseService' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/service/BaseService.php',
    'com\\linways\\nucleus\\core\\service\\ClientService' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/service/ClientService.php',
    'com\\linways\\nucleus\\core\\service\\CollegeService' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/service/CollegeService.php',
    'com\\linways\\nucleus\\core\\service\\ConfigurationService' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/service/ConfigurationService.php',
    'com\\linways\\nucleus\\core\\service\\ExpenseCategoryService' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/service/ExpenseCategoryService.php',
    'com\\linways\\nucleus\\core\\service\\ExpenseService' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/service/ExpenseService.php',
    'com\\linways\\nucleus\\core\\service\\PermissionService' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/service/PermissionService.php',
    'com\\linways\\nucleus\\core\\service\\PermissionToggleService' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/service/PermissionToggleService.php',
    'com\\linways\\nucleus\\core\\service\\ProductVariantService' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/service/ProductVariantService.php',
    'com\\linways\\nucleus\\core\\service\\RecurringExpenseService' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/service/RecurringExpenseService.php',
    'com\\linways\\nucleus\\core\\service\\ReportService' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/service/ReportService.php',
    'com\\linways\\nucleus\\core\\service\\SchedulerService' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/service/SchedulerService.php',
    'com\\linways\\nucleus\\core\\service\\SingleSignonService' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/service/SingleSignonService.php',
    'com\\linways\\nucleus\\core\\service\\user\\UserService' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/service/user/UserService.php',
    'com\\linways\\nucleus\\core\\utils\\DateUtils' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/utils/DateUtils.php',
    'com\\linways\\nucleus\\core\\utils\\JWTUtils' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/utils/JWTUtils.php',
    'com\\linways\\nucleus\\core\\utils\\StringUtils' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/utils/StringUtils.php',
    'com\\linways\\nucleus\\core\\utils\\user\\UserServiceUtils' => $vendorDir . '/linways/nucleus-core/src/com/linways/nucleus/core/utils/user/UserServiceUtils.php',
    'linways\\cli\\command\\db\\CreateMigrationCommand' => $baseDir . '/src/commands/db/CreateMigrationCommand.php',
    'linways\\cli\\command\\db\\MigrateCommand' => $baseDir . '/src/commands/db/MigrateCommand.php',
    'linways\\cli\\command\\db\\MigrateFakeCommand' => $baseDir . '/src/commands/db/MigrateFakeCommand.php',
    'linways\\cli\\command\\git\\UpdateStyleGuideCommand' => $baseDir . '/src/commands/git/UpdateStyleGuideCommand.php',
    'linways\\cli\\commands\\toggles\\CreatePermissionToggleCommand' => $baseDir . '/src/commands/toggles/CreatePermissionToggleCommand.php',
    'linways\\cli\\commands\\toggles\\MigrateTogglesCommand' => $baseDir . '/src/commands/toggles/MigrateTogglesCommand.php',
    'linways\\cli\\service\\ToggleManagerService' => $baseDir . '/src/service/toggles/ToggleManagerService.php',
    'linways\\cli\\service\\db\\MigrateFakeService' => $baseDir . '/src/service/db/MigrateFakeService.php',
    'linways\\cli\\service\\db\\MigrateService' => $baseDir . '/src/service/db/MigrateService.php',
    'linways\\cli\\utils\\FileNameUtils' => $baseDir . '/src/utils/FileNameUtils.php',
    'linways\\cli\\utls\\MigrationUtils' => $baseDir . '/src/utils/MigrationUtils.php',
    'phpDocumentor\\Reflection\\DocBlock' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock.php',
    'phpDocumentor\\Reflection\\DocBlockFactory' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlockFactory.php',
    'phpDocumentor\\Reflection\\DocBlockFactoryInterface' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlockFactoryInterface.php',
    'phpDocumentor\\Reflection\\DocBlock\\Description' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Description.php',
    'phpDocumentor\\Reflection\\DocBlock\\DescriptionFactory' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.php',
    'phpDocumentor\\Reflection\\DocBlock\\ExampleFinder' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.php',
    'phpDocumentor\\Reflection\\DocBlock\\Serializer' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.php',
    'phpDocumentor\\Reflection\\DocBlock\\StandardTagFactory' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tag' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tag.php',
    'phpDocumentor\\Reflection\\DocBlock\\TagFactory' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/TagFactory.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Author' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\BaseTag' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Covers' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Deprecated' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Example' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Factory\\StaticMethod' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/StaticMethod.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Factory\\Strategy' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/Strategy.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Formatter' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Formatter\\AlignFormatter' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/AlignFormatter.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Formatter\\PassthroughFormatter' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Generic' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Link' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Method' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Param' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Property' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\PropertyRead' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\PropertyWrite' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Reference\\Fqsen' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Fqsen.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Reference\\Reference' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Reference.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Reference\\Url' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Url.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Return_' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\See' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Since' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Source' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Throws' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Uses' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Var_' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.php',
    'phpDocumentor\\Reflection\\DocBlock\\Tags\\Version' => $vendorDir . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.php',
    'phpDocumentor\\Reflection\\Element' => $vendorDir . '/phpdocumentor/reflection-common/src/Element.php',
    'phpDocumentor\\Reflection\\File' => $vendorDir . '/phpdocumentor/reflection-common/src/File.php',
    'phpDocumentor\\Reflection\\Fqsen' => $vendorDir . '/phpdocumentor/reflection-common/src/Fqsen.php',
    'phpDocumentor\\Reflection\\FqsenResolver' => $vendorDir . '/phpdocumentor/type-resolver/src/FqsenResolver.php',
    'phpDocumentor\\Reflection\\Location' => $vendorDir . '/phpdocumentor/reflection-common/src/Location.php',
    'phpDocumentor\\Reflection\\Project' => $vendorDir . '/phpdocumentor/reflection-common/src/Project.php',
    'phpDocumentor\\Reflection\\ProjectFactory' => $vendorDir . '/phpdocumentor/reflection-common/src/ProjectFactory.php',
    'phpDocumentor\\Reflection\\Type' => $vendorDir . '/phpdocumentor/type-resolver/src/Type.php',
    'phpDocumentor\\Reflection\\TypeResolver' => $vendorDir . '/phpdocumentor/type-resolver/src/TypeResolver.php',
    'phpDocumentor\\Reflection\\Types\\Array_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Array_.php',
    'phpDocumentor\\Reflection\\Types\\Boolean' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Boolean.php',
    'phpDocumentor\\Reflection\\Types\\Callable_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Callable_.php',
    'phpDocumentor\\Reflection\\Types\\Compound' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Compound.php',
    'phpDocumentor\\Reflection\\Types\\Context' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Context.php',
    'phpDocumentor\\Reflection\\Types\\ContextFactory' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/ContextFactory.php',
    'phpDocumentor\\Reflection\\Types\\Float_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Float_.php',
    'phpDocumentor\\Reflection\\Types\\Integer' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Integer.php',
    'phpDocumentor\\Reflection\\Types\\Iterable_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Iterable_.php',
    'phpDocumentor\\Reflection\\Types\\Mixed_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Mixed_.php',
    'phpDocumentor\\Reflection\\Types\\Null_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Null_.php',
    'phpDocumentor\\Reflection\\Types\\Nullable' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Nullable.php',
    'phpDocumentor\\Reflection\\Types\\Object_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Object_.php',
    'phpDocumentor\\Reflection\\Types\\Parent_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Parent_.php',
    'phpDocumentor\\Reflection\\Types\\Resource_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Resource_.php',
    'phpDocumentor\\Reflection\\Types\\Scalar' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Scalar.php',
    'phpDocumentor\\Reflection\\Types\\Self_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Self_.php',
    'phpDocumentor\\Reflection\\Types\\Static_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Static_.php',
    'phpDocumentor\\Reflection\\Types\\String_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/String_.php',
    'phpDocumentor\\Reflection\\Types\\This' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/This.php',
    'phpDocumentor\\Reflection\\Types\\Void_' => $vendorDir . '/phpdocumentor/type-resolver/src/Types/Void_.php',
    'voku\\helper\\Hooks' => $vendorDir . '/voku/php-hooks/src/voku/helper/Hooks.php',
);
<?php

// autoload_static.php @generated by Composer

namespace Composer\Autoload;

class ComposerStaticInit555f246cb7db6869b6fb9f48d5e5f3d2
{
    public static $files = array (
        '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
        '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
        'ad155f8f1cf0d418fe49e248db8c661b' => __DIR__ . '/..' . '/react/promise/src/functions_include.php',
        '2cffec82183ee1cea088009cef9a6fc3' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
        '6124b4c8570aa390c21fafd04a26c69f' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php',
    );

    public static $prefixLengthsPsr4 = array (
        'v' => 
        array (
            'voku\\helper\\' => 12,
        ),
        'p' => 
        array (
            'phpDocumentor\\Reflection\\' => 25,
        ),
        'l' => 
        array (
            'linways\\cli\\' => 12,
        ),
        'c' => 
        array (
            'com\\linways\\base\\' => 17,
            'com\\linways\\' => 12,
        ),
        'W' => 
        array (
            'Webmozart\\Assert\\' => 17,
        ),
        'S' => 
        array (
            'Symfony\\Polyfill\\Mbstring\\' => 26,
            'Symfony\\Polyfill\\Ctype\\' => 23,
            'Symfony\\Component\\Yaml\\' => 23,
            'Symfony\\Component\\Filesystem\\' => 29,
            'Symfony\\Component\\Dotenv\\' => 25,
            'Symfony\\Component\\Debug\\' => 24,
            'Symfony\\Component\\Console\\' => 26,
            'Symfony\\Component\\Config\\' => 25,
        ),
        'R' => 
        array (
            'React\\Promise\\' => 14,
        ),
        'P' => 
        array (
            'Psr\\Log\\' => 8,
            'PhpAmqpLib\\' => 11,
            'Phinx\\' => 6,
        ),
        'M' => 
        array (
            'Monolog\\' => 8,
        ),
        'G' => 
        array (
            'GuzzleHttp\\Stream\\' => 18,
            'GuzzleHttp\\Ring\\' => 16,
        ),
        'F' => 
        array (
            'Firebase\\JWT\\' => 13,
        ),
        'E' => 
        array (
            'Elasticsearch\\' => 14,
        ),
        'D' => 
        array (
            'Doctrine\\Instantiator\\' => 22,
            'DeepCopy\\' => 9,
        ),
        'C' => 
        array (
            'Cron\\' => 5,
        ),
    );

    public static $prefixDirsPsr4 = array (
        'voku\\helper\\' => 
        array (
            0 => __DIR__ . '/..' . '/voku/php-hooks/src/voku/helper',
        ),
        'phpDocumentor\\Reflection\\' => 
        array (
            0 => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src',
            1 => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src',
            2 => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src',
        ),
        'linways\\cli\\' => 
        array (
            0 => __DIR__ . '/../..' . '/src',
        ),
        'com\\linways\\base\\' => 
        array (
            0 => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base',
        ),
        'com\\linways\\' => 
        array (
            0 => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways',
        ),
        'Webmozart\\Assert\\' => 
        array (
            0 => __DIR__ . '/..' . '/webmozart/assert/src',
        ),
        'Symfony\\Polyfill\\Mbstring\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
        ),
        'Symfony\\Polyfill\\Ctype\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/polyfill-ctype',
        ),
        'Symfony\\Component\\Yaml\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/yaml',
        ),
        'Symfony\\Component\\Filesystem\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/filesystem',
        ),
        'Symfony\\Component\\Dotenv\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/dotenv',
        ),
        'Symfony\\Component\\Debug\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/debug',
        ),
        'Symfony\\Component\\Console\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/console',
        ),
        'Symfony\\Component\\Config\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/config',
        ),
        'React\\Promise\\' => 
        array (
            0 => __DIR__ . '/..' . '/react/promise/src',
        ),
        'Psr\\Log\\' => 
        array (
            0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
        ),
        'PhpAmqpLib\\' => 
        array (
            0 => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib',
        ),
        'Phinx\\' => 
        array (
            0 => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx',
        ),
        'Monolog\\' => 
        array (
            0 => __DIR__ . '/..' . '/monolog/monolog/src/Monolog',
        ),
        'GuzzleHttp\\Stream\\' => 
        array (
            0 => __DIR__ . '/..' . '/guzzlehttp/streams/src',
        ),
        'GuzzleHttp\\Ring\\' => 
        array (
            0 => __DIR__ . '/..' . '/guzzlehttp/ringphp/src',
        ),
        'Firebase\\JWT\\' => 
        array (
            0 => __DIR__ . '/..' . '/firebase/php-jwt/src',
        ),
        'Elasticsearch\\' => 
        array (
            0 => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch',
        ),
        'Doctrine\\Instantiator\\' => 
        array (
            0 => __DIR__ . '/..' . '/doctrine/instantiator/src/Doctrine/Instantiator',
        ),
        'DeepCopy\\' => 
        array (
            0 => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy',
        ),
        'Cron\\' => 
        array (
            0 => __DIR__ . '/..' . '/dragonmantank/cron-expression/src/Cron',
        ),
    );

    public static $prefixesPsr0 = array (
        'P' => 
        array (
            'Prophecy\\' => 
            array (
                0 => __DIR__ . '/..' . '/phpspec/prophecy/src',
            ),
        ),
        'H' => 
        array (
            'HTMLPurifier' => 
            array (
                0 => __DIR__ . '/..' . '/ezyang/htmlpurifier/library',
            ),
        ),
    );

    public static $classMap = array (
        'Cron\\AbstractField' => __DIR__ . '/..' . '/dragonmantank/cron-expression/src/Cron/AbstractField.php',
        'Cron\\CronExpression' => __DIR__ . '/..' . '/dragonmantank/cron-expression/src/Cron/CronExpression.php',
        'Cron\\DayOfMonthField' => __DIR__ . '/..' . '/dragonmantank/cron-expression/src/Cron/DayOfMonthField.php',
        'Cron\\DayOfWeekField' => __DIR__ . '/..' . '/dragonmantank/cron-expression/src/Cron/DayOfWeekField.php',
        'Cron\\FieldFactory' => __DIR__ . '/..' . '/dragonmantank/cron-expression/src/Cron/FieldFactory.php',
        'Cron\\FieldInterface' => __DIR__ . '/..' . '/dragonmantank/cron-expression/src/Cron/FieldInterface.php',
        'Cron\\HoursField' => __DIR__ . '/..' . '/dragonmantank/cron-expression/src/Cron/HoursField.php',
        'Cron\\MinutesField' => __DIR__ . '/..' . '/dragonmantank/cron-expression/src/Cron/MinutesField.php',
        'Cron\\MonthField' => __DIR__ . '/..' . '/dragonmantank/cron-expression/src/Cron/MonthField.php',
        'Cron\\YearField' => __DIR__ . '/..' . '/dragonmantank/cron-expression/src/Cron/YearField.php',
        'DeepCopy\\DeepCopy' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/DeepCopy.php',
        'DeepCopy\\Exception\\CloneException' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Exception/CloneException.php',
        'DeepCopy\\Exception\\PropertyException' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Exception/PropertyException.php',
        'DeepCopy\\Filter\\Doctrine\\DoctrineCollectionFilter' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineCollectionFilter.php',
        'DeepCopy\\Filter\\Doctrine\\DoctrineEmptyCollectionFilter' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineEmptyCollectionFilter.php',
        'DeepCopy\\Filter\\Doctrine\\DoctrineProxyFilter' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineProxyFilter.php',
        'DeepCopy\\Filter\\Filter' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Filter/Filter.php',
        'DeepCopy\\Filter\\KeepFilter' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Filter/KeepFilter.php',
        'DeepCopy\\Filter\\ReplaceFilter' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Filter/ReplaceFilter.php',
        'DeepCopy\\Filter\\SetNullFilter' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Filter/SetNullFilter.php',
        'DeepCopy\\Matcher\\Doctrine\\DoctrineProxyMatcher' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Matcher/Doctrine/DoctrineProxyMatcher.php',
        'DeepCopy\\Matcher\\Matcher' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Matcher/Matcher.php',
        'DeepCopy\\Matcher\\PropertyMatcher' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyMatcher.php',
        'DeepCopy\\Matcher\\PropertyNameMatcher' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyNameMatcher.php',
        'DeepCopy\\Matcher\\PropertyTypeMatcher' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyTypeMatcher.php',
        'DeepCopy\\Reflection\\ReflectionHelper' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/Reflection/ReflectionHelper.php',
        'DeepCopy\\TypeFilter\\Date\\DateIntervalFilter' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/TypeFilter/Date/DateIntervalFilter.php',
        'DeepCopy\\TypeFilter\\ReplaceFilter' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/TypeFilter/ReplaceFilter.php',
        'DeepCopy\\TypeFilter\\ShallowCopyFilter' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/TypeFilter/ShallowCopyFilter.php',
        'DeepCopy\\TypeFilter\\Spl\\SplDoublyLinkedList' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/TypeFilter/Spl/SplDoublyLinkedList.php',
        'DeepCopy\\TypeFilter\\Spl\\SplDoublyLinkedListFilter' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/TypeFilter/Spl/SplDoublyLinkedListFilter.php',
        'DeepCopy\\TypeFilter\\TypeFilter' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/TypeFilter/TypeFilter.php',
        'DeepCopy\\TypeMatcher\\TypeMatcher' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/TypeMatcher/TypeMatcher.php',
        'Doctrine\\Instantiator\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/doctrine/instantiator/src/Doctrine/Instantiator/Exception/ExceptionInterface.php',
        'Doctrine\\Instantiator\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/doctrine/instantiator/src/Doctrine/Instantiator/Exception/InvalidArgumentException.php',
        'Doctrine\\Instantiator\\Exception\\UnexpectedValueException' => __DIR__ . '/..' . '/doctrine/instantiator/src/Doctrine/Instantiator/Exception/UnexpectedValueException.php',
        'Doctrine\\Instantiator\\Instantiator' => __DIR__ . '/..' . '/doctrine/instantiator/src/Doctrine/Instantiator/Instantiator.php',
        'Doctrine\\Instantiator\\InstantiatorInterface' => __DIR__ . '/..' . '/doctrine/instantiator/src/Doctrine/Instantiator/InstantiatorInterface.php',
        'Elasticsearch\\Client' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Client.php',
        'Elasticsearch\\ClientBuilder' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/ClientBuilder.php',
        'Elasticsearch\\Common\\EmptyLogger' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/EmptyLogger.php',
        'Elasticsearch\\Common\\Exceptions\\AlreadyExpiredException' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/AlreadyExpiredException.php',
        'Elasticsearch\\Common\\Exceptions\\BadMethodCallException' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/BadMethodCallException.php',
        'Elasticsearch\\Common\\Exceptions\\BadRequest400Exception' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/BadRequest400Exception.php',
        'Elasticsearch\\Common\\Exceptions\\ClientErrorResponseException' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/ClientErrorResponseException.php',
        'Elasticsearch\\Common\\Exceptions\\Conflict409Exception' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Conflict409Exception.php',
        'Elasticsearch\\Common\\Exceptions\\Curl\\CouldNotConnectToHost' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Curl/CouldNotConnectToHost.php',
        'Elasticsearch\\Common\\Exceptions\\Curl\\CouldNotResolveHostException' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Curl/CouldNotResolveHostException.php',
        'Elasticsearch\\Common\\Exceptions\\Curl\\OperationTimeoutException' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Curl/OperationTimeoutException.php',
        'Elasticsearch\\Common\\Exceptions\\ElasticsearchException' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/ElasticsearchException.php',
        'Elasticsearch\\Common\\Exceptions\\Forbidden403Exception' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Forbidden403Exception.php',
        'Elasticsearch\\Common\\Exceptions\\InvalidArgumentException' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/InvalidArgumentException.php',
        'Elasticsearch\\Common\\Exceptions\\MaxRetriesException' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/MaxRetriesException.php',
        'Elasticsearch\\Common\\Exceptions\\Missing404Exception' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Missing404Exception.php',
        'Elasticsearch\\Common\\Exceptions\\NoDocumentsToGetException' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/NoDocumentsToGetException.php',
        'Elasticsearch\\Common\\Exceptions\\NoNodesAvailableException' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/NoNodesAvailableException.php',
        'Elasticsearch\\Common\\Exceptions\\NoShardAvailableException' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/NoShardAvailableException.php',
        'Elasticsearch\\Common\\Exceptions\\RequestTimeout408Exception' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/RequestTimeout408Exception.php',
        'Elasticsearch\\Common\\Exceptions\\RoutingMissingException' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/RoutingMissingException.php',
        'Elasticsearch\\Common\\Exceptions\\RuntimeException' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/RuntimeException.php',
        'Elasticsearch\\Common\\Exceptions\\ScriptLangNotSupportedException' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/ScriptLangNotSupportedException.php',
        'Elasticsearch\\Common\\Exceptions\\Serializer\\JsonErrorException' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Serializer/JsonErrorException.php',
        'Elasticsearch\\Common\\Exceptions\\ServerErrorResponseException' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/ServerErrorResponseException.php',
        'Elasticsearch\\Common\\Exceptions\\TransportException' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/TransportException.php',
        'Elasticsearch\\Common\\Exceptions\\UnexpectedValueException' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/UnexpectedValueException.php',
        'Elasticsearch\\ConnectionPool\\AbstractConnectionPool' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/AbstractConnectionPool.php',
        'Elasticsearch\\ConnectionPool\\ConnectionPoolInterface' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/ConnectionPoolInterface.php',
        'Elasticsearch\\ConnectionPool\\Selectors\\RandomSelector' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/RandomSelector.php',
        'Elasticsearch\\ConnectionPool\\Selectors\\RoundRobinSelector' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/RoundRobinSelector.php',
        'Elasticsearch\\ConnectionPool\\Selectors\\SelectorInterface' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/SelectorInterface.php',
        'Elasticsearch\\ConnectionPool\\Selectors\\StickyRoundRobinSelector' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/StickyRoundRobinSelector.php',
        'Elasticsearch\\ConnectionPool\\SimpleConnectionPool' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/SimpleConnectionPool.php',
        'Elasticsearch\\ConnectionPool\\SniffingConnectionPool' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/SniffingConnectionPool.php',
        'Elasticsearch\\ConnectionPool\\StaticConnectionPool' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/StaticConnectionPool.php',
        'Elasticsearch\\ConnectionPool\\StaticNoPingConnectionPool' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/StaticNoPingConnectionPool.php',
        'Elasticsearch\\Connections\\Connection' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Connections/Connection.php',
        'Elasticsearch\\Connections\\ConnectionFactory' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Connections/ConnectionFactory.php',
        'Elasticsearch\\Connections\\ConnectionFactoryInterface' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Connections/ConnectionFactoryInterface.php',
        'Elasticsearch\\Connections\\ConnectionInterface' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Connections/ConnectionInterface.php',
        'Elasticsearch\\Endpoints\\AbstractEndpoint' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/AbstractEndpoint.php',
        'Elasticsearch\\Endpoints\\Bulk' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Bulk.php',
        'Elasticsearch\\Endpoints\\BulkEndpointInterface' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/BulkEndpointInterface.php',
        'Elasticsearch\\Endpoints\\Cat\\Aliases' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Aliases.php',
        'Elasticsearch\\Endpoints\\Cat\\Allocation' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Allocation.php',
        'Elasticsearch\\Endpoints\\Cat\\Count' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Count.php',
        'Elasticsearch\\Endpoints\\Cat\\Fielddata' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Fielddata.php',
        'Elasticsearch\\Endpoints\\Cat\\Health' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Health.php',
        'Elasticsearch\\Endpoints\\Cat\\Help' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Help.php',
        'Elasticsearch\\Endpoints\\Cat\\Indices' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Indices.php',
        'Elasticsearch\\Endpoints\\Cat\\Master' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Master.php',
        'Elasticsearch\\Endpoints\\Cat\\NodeAttrs' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/NodeAttrs.php',
        'Elasticsearch\\Endpoints\\Cat\\Nodes' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Nodes.php',
        'Elasticsearch\\Endpoints\\Cat\\PendingTasks' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/PendingTasks.php',
        'Elasticsearch\\Endpoints\\Cat\\Plugins' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Plugins.php',
        'Elasticsearch\\Endpoints\\Cat\\Recovery' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Recovery.php',
        'Elasticsearch\\Endpoints\\Cat\\Repositories' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Repositories.php',
        'Elasticsearch\\Endpoints\\Cat\\Segments' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Segments.php',
        'Elasticsearch\\Endpoints\\Cat\\Shards' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Shards.php',
        'Elasticsearch\\Endpoints\\Cat\\Snapshots' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Snapshots.php',
        'Elasticsearch\\Endpoints\\Cat\\Tasks' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Tasks.php',
        'Elasticsearch\\Endpoints\\Cat\\Templates' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Templates.php',
        'Elasticsearch\\Endpoints\\Cat\\ThreadPool' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/ThreadPool.php',
        'Elasticsearch\\Endpoints\\ClearScroll' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/ClearScroll.php',
        'Elasticsearch\\Endpoints\\Cluster\\AllocationExplain' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/AllocationExplain.php',
        'Elasticsearch\\Endpoints\\Cluster\\Health' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Health.php',
        'Elasticsearch\\Endpoints\\Cluster\\Nodes\\AbstractNodesEndpoint' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/AbstractNodesEndpoint.php',
        'Elasticsearch\\Endpoints\\Cluster\\Nodes\\HotThreads' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/HotThreads.php',
        'Elasticsearch\\Endpoints\\Cluster\\Nodes\\Info' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/Info.php',
        'Elasticsearch\\Endpoints\\Cluster\\Nodes\\Shutdown' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/Shutdown.php',
        'Elasticsearch\\Endpoints\\Cluster\\Nodes\\Stats' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/Stats.php',
        'Elasticsearch\\Endpoints\\Cluster\\PendingTasks' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/PendingTasks.php',
        'Elasticsearch\\Endpoints\\Cluster\\Reroute' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Reroute.php',
        'Elasticsearch\\Endpoints\\Cluster\\Settings\\Get' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Settings/Get.php',
        'Elasticsearch\\Endpoints\\Cluster\\Settings\\Put' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Settings/Put.php',
        'Elasticsearch\\Endpoints\\Cluster\\State' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/State.php',
        'Elasticsearch\\Endpoints\\Cluster\\Stats' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Stats.php',
        'Elasticsearch\\Endpoints\\Count' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Count.php',
        'Elasticsearch\\Endpoints\\CountPercolate' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/CountPercolate.php',
        'Elasticsearch\\Endpoints\\Create' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Create.php',
        'Elasticsearch\\Endpoints\\Delete' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Delete.php',
        'Elasticsearch\\Endpoints\\DeleteByQuery' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/DeleteByQuery.php',
        'Elasticsearch\\Endpoints\\Exists' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Exists.php',
        'Elasticsearch\\Endpoints\\Explain' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Explain.php',
        'Elasticsearch\\Endpoints\\FieldCaps' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/FieldCaps.php',
        'Elasticsearch\\Endpoints\\FieldStats' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/FieldStats.php',
        'Elasticsearch\\Endpoints\\Get' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Get.php',
        'Elasticsearch\\Endpoints\\Index' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Index.php',
        'Elasticsearch\\Endpoints\\Indices\\Alias\\AbstractAliasEndpoint' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/AbstractAliasEndpoint.php',
        'Elasticsearch\\Endpoints\\Indices\\Alias\\Delete' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/Delete.php',
        'Elasticsearch\\Endpoints\\Indices\\Alias\\Exists' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/Exists.php',
        'Elasticsearch\\Endpoints\\Indices\\Alias\\Get' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/Get.php',
        'Elasticsearch\\Endpoints\\Indices\\Alias\\Put' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/Put.php',
        'Elasticsearch\\Endpoints\\Indices\\Aliases\\Get' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Aliases/Get.php',
        'Elasticsearch\\Endpoints\\Indices\\Aliases\\Update' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Aliases/Update.php',
        'Elasticsearch\\Endpoints\\Indices\\Analyze' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Analyze.php',
        'Elasticsearch\\Endpoints\\Indices\\Cache\\Clear' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Cache/Clear.php',
        'Elasticsearch\\Endpoints\\Indices\\ClearCache' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/ClearCache.php',
        'Elasticsearch\\Endpoints\\Indices\\Close' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Close.php',
        'Elasticsearch\\Endpoints\\Indices\\Create' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Create.php',
        'Elasticsearch\\Endpoints\\Indices\\Delete' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Delete.php',
        'Elasticsearch\\Endpoints\\Indices\\Exists' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Exists.php',
        'Elasticsearch\\Endpoints\\Indices\\Exists\\Types' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Exists/Types.php',
        'Elasticsearch\\Endpoints\\Indices\\Field\\Get' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Field/Get.php',
        'Elasticsearch\\Endpoints\\Indices\\Flush' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Flush.php',
        'Elasticsearch\\Endpoints\\Indices\\ForceMerge' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/ForceMerge.php',
        'Elasticsearch\\Endpoints\\Indices\\Gateway\\Snapshot' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Gateway/Snapshot.php',
        'Elasticsearch\\Endpoints\\Indices\\Get' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Get.php',
        'Elasticsearch\\Endpoints\\Indices\\Mapping\\Delete' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Mapping/Delete.php',
        'Elasticsearch\\Endpoints\\Indices\\Mapping\\Get' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Mapping/Get.php',
        'Elasticsearch\\Endpoints\\Indices\\Mapping\\GetField' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Mapping/GetField.php',
        'Elasticsearch\\Endpoints\\Indices\\Mapping\\Put' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Mapping/Put.php',
        'Elasticsearch\\Endpoints\\Indices\\Open' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Open.php',
        'Elasticsearch\\Endpoints\\Indices\\Recovery' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Recovery.php',
        'Elasticsearch\\Endpoints\\Indices\\Refresh' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Refresh.php',
        'Elasticsearch\\Endpoints\\Indices\\Rollover' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Rollover.php',
        'Elasticsearch\\Endpoints\\Indices\\Seal' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Seal.php',
        'Elasticsearch\\Endpoints\\Indices\\Segments' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Segments.php',
        'Elasticsearch\\Endpoints\\Indices\\Settings\\Get' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Settings/Get.php',
        'Elasticsearch\\Endpoints\\Indices\\Settings\\Put' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Settings/Put.php',
        'Elasticsearch\\Endpoints\\Indices\\ShardStores' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/ShardStores.php',
        'Elasticsearch\\Endpoints\\Indices\\Shrink' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Shrink.php',
        'Elasticsearch\\Endpoints\\Indices\\Snapshotindex' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Snapshotindex.php',
        'Elasticsearch\\Endpoints\\Indices\\Stats' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Stats.php',
        'Elasticsearch\\Endpoints\\Indices\\Status' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Status.php',
        'Elasticsearch\\Endpoints\\Indices\\Template\\AbstractTemplateEndpoint' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/AbstractTemplateEndpoint.php',
        'Elasticsearch\\Endpoints\\Indices\\Template\\Delete' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/Delete.php',
        'Elasticsearch\\Endpoints\\Indices\\Template\\Exists' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/Exists.php',
        'Elasticsearch\\Endpoints\\Indices\\Template\\Get' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/Get.php',
        'Elasticsearch\\Endpoints\\Indices\\Template\\Put' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/Put.php',
        'Elasticsearch\\Endpoints\\Indices\\Type\\Exists' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Type/Exists.php',
        'Elasticsearch\\Endpoints\\Indices\\Upgrade\\Get' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Upgrade/Get.php',
        'Elasticsearch\\Endpoints\\Indices\\Upgrade\\Post' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Upgrade/Post.php',
        'Elasticsearch\\Endpoints\\Indices\\ValidateQuery' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/ValidateQuery.php',
        'Elasticsearch\\Endpoints\\Indices\\Validate\\Query' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Validate/Query.php',
        'Elasticsearch\\Endpoints\\Info' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Info.php',
        'Elasticsearch\\Endpoints\\Ingest\\Pipeline\\Delete' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ingest/Pipeline/Delete.php',
        'Elasticsearch\\Endpoints\\Ingest\\Pipeline\\Get' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ingest/Pipeline/Get.php',
        'Elasticsearch\\Endpoints\\Ingest\\Pipeline\\Put' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ingest/Pipeline/Put.php',
        'Elasticsearch\\Endpoints\\Ingest\\Simulate' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ingest/Simulate.php',
        'Elasticsearch\\Endpoints\\MPercolate' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/MPercolate.php',
        'Elasticsearch\\Endpoints\\MTermVectors' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/MTermVectors.php',
        'Elasticsearch\\Endpoints\\Mget' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Mget.php',
        'Elasticsearch\\Endpoints\\Msearch' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Msearch.php',
        'Elasticsearch\\Endpoints\\MsearchTemplate' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/MsearchTemplate.php',
        'Elasticsearch\\Endpoints\\Percolate' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Percolate.php',
        'Elasticsearch\\Endpoints\\Ping' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ping.php',
        'Elasticsearch\\Endpoints\\Reindex' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Reindex.php',
        'Elasticsearch\\Endpoints\\Remote\\Info' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Remote/Info.php',
        'Elasticsearch\\Endpoints\\RenderSearchTemplate' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/RenderSearchTemplate.php',
        'Elasticsearch\\Endpoints\\Script\\Delete' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Script/Delete.php',
        'Elasticsearch\\Endpoints\\Script\\Get' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Script/Get.php',
        'Elasticsearch\\Endpoints\\Script\\Put' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Script/Put.php',
        'Elasticsearch\\Endpoints\\Scroll' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Scroll.php',
        'Elasticsearch\\Endpoints\\Search' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Search.php',
        'Elasticsearch\\Endpoints\\SearchShards' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/SearchShards.php',
        'Elasticsearch\\Endpoints\\SearchTemplate' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/SearchTemplate.php',
        'Elasticsearch\\Endpoints\\Snapshot\\Create' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Create.php',
        'Elasticsearch\\Endpoints\\Snapshot\\Delete' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Delete.php',
        'Elasticsearch\\Endpoints\\Snapshot\\Get' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Get.php',
        'Elasticsearch\\Endpoints\\Snapshot\\Repository\\Create' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Repository/Create.php',
        'Elasticsearch\\Endpoints\\Snapshot\\Repository\\Delete' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Repository/Delete.php',
        'Elasticsearch\\Endpoints\\Snapshot\\Repository\\Get' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Repository/Get.php',
        'Elasticsearch\\Endpoints\\Snapshot\\Repository\\Verify' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Repository/Verify.php',
        'Elasticsearch\\Endpoints\\Snapshot\\Restore' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Restore.php',
        'Elasticsearch\\Endpoints\\Snapshot\\Status' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Status.php',
        'Elasticsearch\\Endpoints\\Source\\Get' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Source/Get.php',
        'Elasticsearch\\Endpoints\\Suggest' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Suggest.php',
        'Elasticsearch\\Endpoints\\Tasks\\Cancel' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Tasks/Cancel.php',
        'Elasticsearch\\Endpoints\\Tasks\\Get' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Tasks/Get.php',
        'Elasticsearch\\Endpoints\\Tasks\\TasksList' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Tasks/TasksList.php',
        'Elasticsearch\\Endpoints\\Template\\Delete' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Template/Delete.php',
        'Elasticsearch\\Endpoints\\Template\\Get' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Template/Get.php',
        'Elasticsearch\\Endpoints\\Template\\Put' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Template/Put.php',
        'Elasticsearch\\Endpoints\\TermVectors' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/TermVectors.php',
        'Elasticsearch\\Endpoints\\Update' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Update.php',
        'Elasticsearch\\Endpoints\\UpdateByQuery' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/UpdateByQuery.php',
        'Elasticsearch\\Helper\\Iterators\\SearchHitIterator' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Helper/Iterators/SearchHitIterator.php',
        'Elasticsearch\\Helper\\Iterators\\SearchResponseIterator' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Helper/Iterators/SearchResponseIterator.php',
        'Elasticsearch\\Namespaces\\AbstractNamespace' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/AbstractNamespace.php',
        'Elasticsearch\\Namespaces\\BooleanRequestWrapper' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/BooleanRequestWrapper.php',
        'Elasticsearch\\Namespaces\\CatNamespace' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/CatNamespace.php',
        'Elasticsearch\\Namespaces\\ClusterNamespace' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/ClusterNamespace.php',
        'Elasticsearch\\Namespaces\\IndicesNamespace' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/IndicesNamespace.php',
        'Elasticsearch\\Namespaces\\IngestNamespace' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/IngestNamespace.php',
        'Elasticsearch\\Namespaces\\NamespaceBuilderInterface' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/NamespaceBuilderInterface.php',
        'Elasticsearch\\Namespaces\\NodesNamespace' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/NodesNamespace.php',
        'Elasticsearch\\Namespaces\\RemoteNamespace' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/RemoteNamespace.php',
        'Elasticsearch\\Namespaces\\SnapshotNamespace' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/SnapshotNamespace.php',
        'Elasticsearch\\Namespaces\\TasksNamespace' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/TasksNamespace.php',
        'Elasticsearch\\Serializers\\ArrayToJSONSerializer' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/ArrayToJSONSerializer.php',
        'Elasticsearch\\Serializers\\EverythingToJSONSerializer' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/EverythingToJSONSerializer.php',
        'Elasticsearch\\Serializers\\SerializerInterface' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/SerializerInterface.php',
        'Elasticsearch\\Serializers\\SmartSerializer' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/SmartSerializer.php',
        'Elasticsearch\\Transport' => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch/Transport.php',
        'File_Iterator' => __DIR__ . '/..' . '/phpunit/php-file-iterator/src/Iterator.php',
        'File_Iterator_Facade' => __DIR__ . '/..' . '/phpunit/php-file-iterator/src/Facade.php',
        'File_Iterator_Factory' => __DIR__ . '/..' . '/phpunit/php-file-iterator/src/Factory.php',
        'Firebase\\JWT\\BeforeValidException' => __DIR__ . '/..' . '/firebase/php-jwt/src/BeforeValidException.php',
        'Firebase\\JWT\\ExpiredException' => __DIR__ . '/..' . '/firebase/php-jwt/src/ExpiredException.php',
        'Firebase\\JWT\\JWT' => __DIR__ . '/..' . '/firebase/php-jwt/src/JWT.php',
        'Firebase\\JWT\\SignatureInvalidException' => __DIR__ . '/..' . '/firebase/php-jwt/src/SignatureInvalidException.php',
        'GuzzleHttp\\Ring\\Client\\ClientUtils' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Client/ClientUtils.php',
        'GuzzleHttp\\Ring\\Client\\CurlFactory' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Client/CurlFactory.php',
        'GuzzleHttp\\Ring\\Client\\CurlHandler' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Client/CurlHandler.php',
        'GuzzleHttp\\Ring\\Client\\CurlMultiHandler' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Client/CurlMultiHandler.php',
        'GuzzleHttp\\Ring\\Client\\Middleware' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Client/Middleware.php',
        'GuzzleHttp\\Ring\\Client\\MockHandler' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Client/MockHandler.php',
        'GuzzleHttp\\Ring\\Client\\StreamHandler' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Client/StreamHandler.php',
        'GuzzleHttp\\Ring\\Core' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Core.php',
        'GuzzleHttp\\Ring\\Exception\\CancelledException' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Exception/CancelledException.php',
        'GuzzleHttp\\Ring\\Exception\\CancelledFutureAccessException' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Exception/CancelledFutureAccessException.php',
        'GuzzleHttp\\Ring\\Exception\\ConnectException' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Exception/ConnectException.php',
        'GuzzleHttp\\Ring\\Exception\\RingException' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Exception/RingException.php',
        'GuzzleHttp\\Ring\\Future\\BaseFutureTrait' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Future/BaseFutureTrait.php',
        'GuzzleHttp\\Ring\\Future\\CompletedFutureArray' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Future/CompletedFutureArray.php',
        'GuzzleHttp\\Ring\\Future\\CompletedFutureValue' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Future/CompletedFutureValue.php',
        'GuzzleHttp\\Ring\\Future\\FutureArray' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Future/FutureArray.php',
        'GuzzleHttp\\Ring\\Future\\FutureArrayInterface' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Future/FutureArrayInterface.php',
        'GuzzleHttp\\Ring\\Future\\FutureInterface' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Future/FutureInterface.php',
        'GuzzleHttp\\Ring\\Future\\FutureValue' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Future/FutureValue.php',
        'GuzzleHttp\\Ring\\Future\\MagicFutureTrait' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Future/MagicFutureTrait.php',
        'GuzzleHttp\\Stream\\AppendStream' => __DIR__ . '/..' . '/guzzlehttp/streams/src/AppendStream.php',
        'GuzzleHttp\\Stream\\AsyncReadStream' => __DIR__ . '/..' . '/guzzlehttp/streams/src/AsyncReadStream.php',
        'GuzzleHttp\\Stream\\BufferStream' => __DIR__ . '/..' . '/guzzlehttp/streams/src/BufferStream.php',
        'GuzzleHttp\\Stream\\CachingStream' => __DIR__ . '/..' . '/guzzlehttp/streams/src/CachingStream.php',
        'GuzzleHttp\\Stream\\DroppingStream' => __DIR__ . '/..' . '/guzzlehttp/streams/src/DroppingStream.php',
        'GuzzleHttp\\Stream\\Exception\\CannotAttachException' => __DIR__ . '/..' . '/guzzlehttp/streams/src/Exception/CannotAttachException.php',
        'GuzzleHttp\\Stream\\Exception\\SeekException' => __DIR__ . '/..' . '/guzzlehttp/streams/src/Exception/SeekException.php',
        'GuzzleHttp\\Stream\\FnStream' => __DIR__ . '/..' . '/guzzlehttp/streams/src/FnStream.php',
        'GuzzleHttp\\Stream\\GuzzleStreamWrapper' => __DIR__ . '/..' . '/guzzlehttp/streams/src/GuzzleStreamWrapper.php',
        'GuzzleHttp\\Stream\\InflateStream' => __DIR__ . '/..' . '/guzzlehttp/streams/src/InflateStream.php',
        'GuzzleHttp\\Stream\\LazyOpenStream' => __DIR__ . '/..' . '/guzzlehttp/streams/src/LazyOpenStream.php',
        'GuzzleHttp\\Stream\\LimitStream' => __DIR__ . '/..' . '/guzzlehttp/streams/src/LimitStream.php',
        'GuzzleHttp\\Stream\\MetadataStreamInterface' => __DIR__ . '/..' . '/guzzlehttp/streams/src/MetadataStreamInterface.php',
        'GuzzleHttp\\Stream\\NoSeekStream' => __DIR__ . '/..' . '/guzzlehttp/streams/src/NoSeekStream.php',
        'GuzzleHttp\\Stream\\NullStream' => __DIR__ . '/..' . '/guzzlehttp/streams/src/NullStream.php',
        'GuzzleHttp\\Stream\\PumpStream' => __DIR__ . '/..' . '/guzzlehttp/streams/src/PumpStream.php',
        'GuzzleHttp\\Stream\\Stream' => __DIR__ . '/..' . '/guzzlehttp/streams/src/Stream.php',
        'GuzzleHttp\\Stream\\StreamDecoratorTrait' => __DIR__ . '/..' . '/guzzlehttp/streams/src/StreamDecoratorTrait.php',
        'GuzzleHttp\\Stream\\StreamInterface' => __DIR__ . '/..' . '/guzzlehttp/streams/src/StreamInterface.php',
        'GuzzleHttp\\Stream\\Utils' => __DIR__ . '/..' . '/guzzlehttp/streams/src/Utils.php',
        'HTMLPurifier' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier.php',
        'HTMLPurifier_Arborize' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Arborize.php',
        'HTMLPurifier_AttrCollections' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrCollections.php',
        'HTMLPurifier_AttrDef' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef.php',
        'HTMLPurifier_AttrDef_CSS' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS.php',
        'HTMLPurifier_AttrDef_CSS_AlphaValue' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/AlphaValue.php',
        'HTMLPurifier_AttrDef_CSS_Background' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Background.php',
        'HTMLPurifier_AttrDef_CSS_BackgroundPosition' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php',
        'HTMLPurifier_AttrDef_CSS_Border' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Border.php',
        'HTMLPurifier_AttrDef_CSS_Color' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Color.php',
        'HTMLPurifier_AttrDef_CSS_Composite' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Composite.php',
        'HTMLPurifier_AttrDef_CSS_DenyElementDecorator' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php',
        'HTMLPurifier_AttrDef_CSS_Filter' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Filter.php',
        'HTMLPurifier_AttrDef_CSS_Font' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Font.php',
        'HTMLPurifier_AttrDef_CSS_FontFamily' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/FontFamily.php',
        'HTMLPurifier_AttrDef_CSS_Ident' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Ident.php',
        'HTMLPurifier_AttrDef_CSS_ImportantDecorator' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php',
        'HTMLPurifier_AttrDef_CSS_Length' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Length.php',
        'HTMLPurifier_AttrDef_CSS_ListStyle' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ListStyle.php',
        'HTMLPurifier_AttrDef_CSS_Multiple' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Multiple.php',
        'HTMLPurifier_AttrDef_CSS_Number' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Number.php',
        'HTMLPurifier_AttrDef_CSS_Percentage' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Percentage.php',
        'HTMLPurifier_AttrDef_CSS_TextDecoration' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/TextDecoration.php',
        'HTMLPurifier_AttrDef_CSS_URI' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/URI.php',
        'HTMLPurifier_AttrDef_Clone' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Clone.php',
        'HTMLPurifier_AttrDef_Enum' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Enum.php',
        'HTMLPurifier_AttrDef_HTML_Bool' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Bool.php',
        'HTMLPurifier_AttrDef_HTML_Class' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Class.php',
        'HTMLPurifier_AttrDef_HTML_Color' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Color.php',
        'HTMLPurifier_AttrDef_HTML_FrameTarget' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/FrameTarget.php',
        'HTMLPurifier_AttrDef_HTML_ID' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/ID.php',
        'HTMLPurifier_AttrDef_HTML_Length' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Length.php',
        'HTMLPurifier_AttrDef_HTML_LinkTypes' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/LinkTypes.php',
        'HTMLPurifier_AttrDef_HTML_MultiLength' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/MultiLength.php',
        'HTMLPurifier_AttrDef_HTML_Nmtokens' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Nmtokens.php',
        'HTMLPurifier_AttrDef_HTML_Pixels' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Pixels.php',
        'HTMLPurifier_AttrDef_Integer' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Integer.php',
        'HTMLPurifier_AttrDef_Lang' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Lang.php',
        'HTMLPurifier_AttrDef_Switch' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Switch.php',
        'HTMLPurifier_AttrDef_Text' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Text.php',
        'HTMLPurifier_AttrDef_URI' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI.php',
        'HTMLPurifier_AttrDef_URI_Email' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email.php',
        'HTMLPurifier_AttrDef_URI_Email_SimpleCheck' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php',
        'HTMLPurifier_AttrDef_URI_Host' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Host.php',
        'HTMLPurifier_AttrDef_URI_IPv4' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv4.php',
        'HTMLPurifier_AttrDef_URI_IPv6' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv6.php',
        'HTMLPurifier_AttrTransform' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform.php',
        'HTMLPurifier_AttrTransform_Background' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Background.php',
        'HTMLPurifier_AttrTransform_BdoDir' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BdoDir.php',
        'HTMLPurifier_AttrTransform_BgColor' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BgColor.php',
        'HTMLPurifier_AttrTransform_BoolToCSS' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BoolToCSS.php',
        'HTMLPurifier_AttrTransform_Border' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Border.php',
        'HTMLPurifier_AttrTransform_EnumToCSS' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/EnumToCSS.php',
        'HTMLPurifier_AttrTransform_ImgRequired' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgRequired.php',
        'HTMLPurifier_AttrTransform_ImgSpace' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgSpace.php',
        'HTMLPurifier_AttrTransform_Input' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Input.php',
        'HTMLPurifier_AttrTransform_Lang' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Lang.php',
        'HTMLPurifier_AttrTransform_Length' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Length.php',
        'HTMLPurifier_AttrTransform_Name' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Name.php',
        'HTMLPurifier_AttrTransform_NameSync' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/NameSync.php',
        'HTMLPurifier_AttrTransform_Nofollow' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Nofollow.php',
        'HTMLPurifier_AttrTransform_SafeEmbed' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeEmbed.php',
        'HTMLPurifier_AttrTransform_SafeObject' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeObject.php',
        'HTMLPurifier_AttrTransform_SafeParam' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeParam.php',
        'HTMLPurifier_AttrTransform_ScriptRequired' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ScriptRequired.php',
        'HTMLPurifier_AttrTransform_TargetBlank' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetBlank.php',
        'HTMLPurifier_AttrTransform_TargetNoopener' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetNoopener.php',
        'HTMLPurifier_AttrTransform_TargetNoreferrer' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetNoreferrer.php',
        'HTMLPurifier_AttrTransform_Textarea' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Textarea.php',
        'HTMLPurifier_AttrTypes' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrTypes.php',
        'HTMLPurifier_AttrValidator' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrValidator.php',
        'HTMLPurifier_Bootstrap' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Bootstrap.php',
        'HTMLPurifier_CSSDefinition' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/CSSDefinition.php',
        'HTMLPurifier_ChildDef' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef.php',
        'HTMLPurifier_ChildDef_Chameleon' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Chameleon.php',
        'HTMLPurifier_ChildDef_Custom' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Custom.php',
        'HTMLPurifier_ChildDef_Empty' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Empty.php',
        'HTMLPurifier_ChildDef_List' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/List.php',
        'HTMLPurifier_ChildDef_Optional' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Optional.php',
        'HTMLPurifier_ChildDef_Required' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Required.php',
        'HTMLPurifier_ChildDef_StrictBlockquote' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/StrictBlockquote.php',
        'HTMLPurifier_ChildDef_Table' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Table.php',
        'HTMLPurifier_Config' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Config.php',
        'HTMLPurifier_ConfigSchema' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema.php',
        'HTMLPurifier_ConfigSchema_Builder_ConfigSchema' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php',
        'HTMLPurifier_ConfigSchema_Builder_Xml' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/Xml.php',
        'HTMLPurifier_ConfigSchema_Exception' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Exception.php',
        'HTMLPurifier_ConfigSchema_Interchange' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange.php',
        'HTMLPurifier_ConfigSchema_InterchangeBuilder' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php',
        'HTMLPurifier_ConfigSchema_Interchange_Directive' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Directive.php',
        'HTMLPurifier_ConfigSchema_Interchange_Id' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Id.php',
        'HTMLPurifier_ConfigSchema_Validator' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Validator.php',
        'HTMLPurifier_ConfigSchema_ValidatorAtom' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php',
        'HTMLPurifier_ContentSets' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/ContentSets.php',
        'HTMLPurifier_Context' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Context.php',
        'HTMLPurifier_Definition' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Definition.php',
        'HTMLPurifier_DefinitionCache' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache.php',
        'HTMLPurifier_DefinitionCacheFactory' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCacheFactory.php',
        'HTMLPurifier_DefinitionCache_Decorator' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator.php',
        'HTMLPurifier_DefinitionCache_Decorator_Cleanup' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php',
        'HTMLPurifier_DefinitionCache_Decorator_Memory' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Memory.php',
        'HTMLPurifier_DefinitionCache_Null' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Null.php',
        'HTMLPurifier_DefinitionCache_Serializer' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer.php',
        'HTMLPurifier_Doctype' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Doctype.php',
        'HTMLPurifier_DoctypeRegistry' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/DoctypeRegistry.php',
        'HTMLPurifier_ElementDef' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/ElementDef.php',
        'HTMLPurifier_Encoder' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Encoder.php',
        'HTMLPurifier_EntityLookup' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup.php',
        'HTMLPurifier_EntityParser' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/EntityParser.php',
        'HTMLPurifier_ErrorCollector' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/ErrorCollector.php',
        'HTMLPurifier_ErrorStruct' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/ErrorStruct.php',
        'HTMLPurifier_Exception' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Exception.php',
        'HTMLPurifier_Filter' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Filter.php',
        'HTMLPurifier_Filter_ExtractStyleBlocks' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Filter/ExtractStyleBlocks.php',
        'HTMLPurifier_Filter_YouTube' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Filter/YouTube.php',
        'HTMLPurifier_Generator' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Generator.php',
        'HTMLPurifier_HTMLDefinition' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLDefinition.php',
        'HTMLPurifier_HTMLModule' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule.php',
        'HTMLPurifier_HTMLModuleManager' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModuleManager.php',
        'HTMLPurifier_HTMLModule_Bdo' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Bdo.php',
        'HTMLPurifier_HTMLModule_CommonAttributes' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/CommonAttributes.php',
        'HTMLPurifier_HTMLModule_Edit' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Edit.php',
        'HTMLPurifier_HTMLModule_Forms' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Forms.php',
        'HTMLPurifier_HTMLModule_Hypertext' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Hypertext.php',
        'HTMLPurifier_HTMLModule_Iframe' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Iframe.php',
        'HTMLPurifier_HTMLModule_Image' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Image.php',
        'HTMLPurifier_HTMLModule_Legacy' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Legacy.php',
        'HTMLPurifier_HTMLModule_List' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/List.php',
        'HTMLPurifier_HTMLModule_Name' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Name.php',
        'HTMLPurifier_HTMLModule_Nofollow' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Nofollow.php',
        'HTMLPurifier_HTMLModule_NonXMLCommonAttributes' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php',
        'HTMLPurifier_HTMLModule_Object' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Object.php',
        'HTMLPurifier_HTMLModule_Presentation' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Presentation.php',
        'HTMLPurifier_HTMLModule_Proprietary' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Proprietary.php',
        'HTMLPurifier_HTMLModule_Ruby' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Ruby.php',
        'HTMLPurifier_HTMLModule_SafeEmbed' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeEmbed.php',
        'HTMLPurifier_HTMLModule_SafeObject' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeObject.php',
        'HTMLPurifier_HTMLModule_SafeScripting' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeScripting.php',
        'HTMLPurifier_HTMLModule_Scripting' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Scripting.php',
        'HTMLPurifier_HTMLModule_StyleAttribute' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/StyleAttribute.php',
        'HTMLPurifier_HTMLModule_Tables' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tables.php',
        'HTMLPurifier_HTMLModule_Target' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Target.php',
        'HTMLPurifier_HTMLModule_TargetBlank' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetBlank.php',
        'HTMLPurifier_HTMLModule_TargetNoopener' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetNoopener.php',
        'HTMLPurifier_HTMLModule_TargetNoreferrer' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetNoreferrer.php',
        'HTMLPurifier_HTMLModule_Text' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Text.php',
        'HTMLPurifier_HTMLModule_Tidy' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy.php',
        'HTMLPurifier_HTMLModule_Tidy_Name' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Name.php',
        'HTMLPurifier_HTMLModule_Tidy_Proprietary' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Proprietary.php',
        'HTMLPurifier_HTMLModule_Tidy_Strict' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Strict.php',
        'HTMLPurifier_HTMLModule_Tidy_Transitional' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Transitional.php',
        'HTMLPurifier_HTMLModule_Tidy_XHTML' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/XHTML.php',
        'HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php',
        'HTMLPurifier_HTMLModule_XMLCommonAttributes' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/XMLCommonAttributes.php',
        'HTMLPurifier_IDAccumulator' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/IDAccumulator.php',
        'HTMLPurifier_Injector' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Injector.php',
        'HTMLPurifier_Injector_AutoParagraph' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Injector/AutoParagraph.php',
        'HTMLPurifier_Injector_DisplayLinkURI' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Injector/DisplayLinkURI.php',
        'HTMLPurifier_Injector_Linkify' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Injector/Linkify.php',
        'HTMLPurifier_Injector_PurifierLinkify' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Injector/PurifierLinkify.php',
        'HTMLPurifier_Injector_RemoveEmpty' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveEmpty.php',
        'HTMLPurifier_Injector_RemoveSpansWithoutAttributes' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php',
        'HTMLPurifier_Injector_SafeObject' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Injector/SafeObject.php',
        'HTMLPurifier_Language' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Language.php',
        'HTMLPurifier_LanguageFactory' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/LanguageFactory.php',
        'HTMLPurifier_Language_en_x_test' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Language/classes/en-x-test.php',
        'HTMLPurifier_Length' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Length.php',
        'HTMLPurifier_Lexer' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Lexer.php',
        'HTMLPurifier_Lexer_DOMLex' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DOMLex.php',
        'HTMLPurifier_Lexer_DirectLex' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DirectLex.php',
        'HTMLPurifier_Lexer_PH5P' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/PH5P.php',
        'HTMLPurifier_Node' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Node.php',
        'HTMLPurifier_Node_Comment' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Node/Comment.php',
        'HTMLPurifier_Node_Element' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Node/Element.php',
        'HTMLPurifier_Node_Text' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Node/Text.php',
        'HTMLPurifier_PercentEncoder' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/PercentEncoder.php',
        'HTMLPurifier_Printer' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Printer.php',
        'HTMLPurifier_Printer_CSSDefinition' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Printer/CSSDefinition.php',
        'HTMLPurifier_Printer_ConfigForm' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php',
        'HTMLPurifier_Printer_ConfigForm_NullDecorator' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php',
        'HTMLPurifier_Printer_ConfigForm_bool' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php',
        'HTMLPurifier_Printer_ConfigForm_default' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php',
        'HTMLPurifier_Printer_HTMLDefinition' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Printer/HTMLDefinition.php',
        'HTMLPurifier_PropertyList' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/PropertyList.php',
        'HTMLPurifier_PropertyListIterator' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/PropertyListIterator.php',
        'HTMLPurifier_Queue' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Queue.php',
        'HTMLPurifier_Strategy' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Strategy.php',
        'HTMLPurifier_Strategy_Composite' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Composite.php',
        'HTMLPurifier_Strategy_Core' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Core.php',
        'HTMLPurifier_Strategy_FixNesting' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/FixNesting.php',
        'HTMLPurifier_Strategy_MakeWellFormed' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/MakeWellFormed.php',
        'HTMLPurifier_Strategy_RemoveForeignElements' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/RemoveForeignElements.php',
        'HTMLPurifier_Strategy_ValidateAttributes' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/ValidateAttributes.php',
        'HTMLPurifier_StringHash' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/StringHash.php',
        'HTMLPurifier_StringHashParser' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/StringHashParser.php',
        'HTMLPurifier_TagTransform' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform.php',
        'HTMLPurifier_TagTransform_Font' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Font.php',
        'HTMLPurifier_TagTransform_Simple' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Simple.php',
        'HTMLPurifier_Token' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Token.php',
        'HTMLPurifier_TokenFactory' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/TokenFactory.php',
        'HTMLPurifier_Token_Comment' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Token/Comment.php',
        'HTMLPurifier_Token_Empty' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Token/Empty.php',
        'HTMLPurifier_Token_End' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Token/End.php',
        'HTMLPurifier_Token_Start' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Token/Start.php',
        'HTMLPurifier_Token_Tag' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Token/Tag.php',
        'HTMLPurifier_Token_Text' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Token/Text.php',
        'HTMLPurifier_URI' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/URI.php',
        'HTMLPurifier_URIDefinition' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/URIDefinition.php',
        'HTMLPurifier_URIFilter' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter.php',
        'HTMLPurifier_URIFilter_DisableExternal' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternal.php',
        'HTMLPurifier_URIFilter_DisableExternalResources' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternalResources.php',
        'HTMLPurifier_URIFilter_DisableResources' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableResources.php',
        'HTMLPurifier_URIFilter_HostBlacklist' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/HostBlacklist.php',
        'HTMLPurifier_URIFilter_MakeAbsolute' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/MakeAbsolute.php',
        'HTMLPurifier_URIFilter_Munge' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/Munge.php',
        'HTMLPurifier_URIFilter_SafeIframe' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/SafeIframe.php',
        'HTMLPurifier_URIParser' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/URIParser.php',
        'HTMLPurifier_URIScheme' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme.php',
        'HTMLPurifier_URISchemeRegistry' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/URISchemeRegistry.php',
        'HTMLPurifier_URIScheme_data' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/data.php',
        'HTMLPurifier_URIScheme_file' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/file.php',
        'HTMLPurifier_URIScheme_ftp' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/ftp.php',
        'HTMLPurifier_URIScheme_http' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/http.php',
        'HTMLPurifier_URIScheme_https' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/https.php',
        'HTMLPurifier_URIScheme_mailto' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/mailto.php',
        'HTMLPurifier_URIScheme_news' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/news.php',
        'HTMLPurifier_URIScheme_nntp' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/nntp.php',
        'HTMLPurifier_URIScheme_tel' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/tel.php',
        'HTMLPurifier_UnitConverter' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/UnitConverter.php',
        'HTMLPurifier_VarParser' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/VarParser.php',
        'HTMLPurifier_VarParserException' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/VarParserException.php',
        'HTMLPurifier_VarParser_Flexible' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php',
        'HTMLPurifier_VarParser_Native' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php',
        'HTMLPurifier_Zipper' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Zipper.php',
        'Monolog\\ErrorHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/ErrorHandler.php',
        'Monolog\\Formatter\\ChromePHPFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php',
        'Monolog\\Formatter\\ElasticaFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php',
        'Monolog\\Formatter\\FlowdockFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php',
        'Monolog\\Formatter\\FluentdFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php',
        'Monolog\\Formatter\\FormatterInterface' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php',
        'Monolog\\Formatter\\GelfMessageFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php',
        'Monolog\\Formatter\\HtmlFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php',
        'Monolog\\Formatter\\JsonFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php',
        'Monolog\\Formatter\\LineFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/LineFormatter.php',
        'Monolog\\Formatter\\LogglyFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php',
        'Monolog\\Formatter\\LogstashFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php',
        'Monolog\\Formatter\\MongoDBFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php',
        'Monolog\\Formatter\\NormalizerFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php',
        'Monolog\\Formatter\\ScalarFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php',
        'Monolog\\Formatter\\WildfireFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php',
        'Monolog\\Handler\\AbstractHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/AbstractHandler.php',
        'Monolog\\Handler\\AbstractProcessingHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php',
        'Monolog\\Handler\\AbstractSyslogHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php',
        'Monolog\\Handler\\AmqpHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/AmqpHandler.php',
        'Monolog\\Handler\\BrowserConsoleHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php',
        'Monolog\\Handler\\BufferHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/BufferHandler.php',
        'Monolog\\Handler\\ChromePHPHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php',
        'Monolog\\Handler\\CouchDBHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php',
        'Monolog\\Handler\\CubeHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/CubeHandler.php',
        'Monolog\\Handler\\Curl\\Util' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/Curl/Util.php',
        'Monolog\\Handler\\DeduplicationHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php',
        'Monolog\\Handler\\DoctrineCouchDBHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php',
        'Monolog\\Handler\\DynamoDbHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php',
        'Monolog\\Handler\\ElasticSearchHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php',
        'Monolog\\Handler\\ErrorLogHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php',
        'Monolog\\Handler\\FilterHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FilterHandler.php',
        'Monolog\\Handler\\FingersCrossedHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php',
        'Monolog\\Handler\\FingersCrossed\\ActivationStrategyInterface' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php',
        'Monolog\\Handler\\FingersCrossed\\ChannelLevelActivationStrategy' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php',
        'Monolog\\Handler\\FingersCrossed\\ErrorLevelActivationStrategy' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php',
        'Monolog\\Handler\\FirePHPHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php',
        'Monolog\\Handler\\FleepHookHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php',
        'Monolog\\Handler\\FlowdockHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php',
        'Monolog\\Handler\\GelfHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/GelfHandler.php',
        'Monolog\\Handler\\GroupHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/GroupHandler.php',
        'Monolog\\Handler\\HandlerInterface' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/HandlerInterface.php',
        'Monolog\\Handler\\HandlerWrapper' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php',
        'Monolog\\Handler\\HipChatHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/HipChatHandler.php',
        'Monolog\\Handler\\IFTTTHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php',
        'Monolog\\Handler\\InsightOpsHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php',
        'Monolog\\Handler\\LogEntriesHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php',
        'Monolog\\Handler\\LogglyHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/LogglyHandler.php',
        'Monolog\\Handler\\MailHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/MailHandler.php',
        'Monolog\\Handler\\MandrillHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/MandrillHandler.php',
        'Monolog\\Handler\\MissingExtensionException' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php',
        'Monolog\\Handler\\MongoDBHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php',
        'Monolog\\Handler\\NativeMailerHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php',
        'Monolog\\Handler\\NewRelicHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php',
        'Monolog\\Handler\\NullHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/NullHandler.php',
        'Monolog\\Handler\\PHPConsoleHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php',
        'Monolog\\Handler\\PsrHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/PsrHandler.php',
        'Monolog\\Handler\\PushoverHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/PushoverHandler.php',
        'Monolog\\Handler\\RavenHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/RavenHandler.php',
        'Monolog\\Handler\\RedisHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/RedisHandler.php',
        'Monolog\\Handler\\RollbarHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/RollbarHandler.php',
        'Monolog\\Handler\\RotatingFileHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php',
        'Monolog\\Handler\\SamplingHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SamplingHandler.php',
        'Monolog\\Handler\\SlackHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SlackHandler.php',
        'Monolog\\Handler\\SlackWebhookHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php',
        'Monolog\\Handler\\Slack\\SlackRecord' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php',
        'Monolog\\Handler\\SlackbotHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php',
        'Monolog\\Handler\\SocketHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SocketHandler.php',
        'Monolog\\Handler\\StreamHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/StreamHandler.php',
        'Monolog\\Handler\\SwiftMailerHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php',
        'Monolog\\Handler\\SyslogHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SyslogHandler.php',
        'Monolog\\Handler\\SyslogUdpHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php',
        'Monolog\\Handler\\SyslogUdp\\UdpSocket' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php',
        'Monolog\\Handler\\TestHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/TestHandler.php',
        'Monolog\\Handler\\WhatFailureGroupHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php',
        'Monolog\\Handler\\ZendMonitorHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php',
        'Monolog\\Logger' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Logger.php',
        'Monolog\\Processor\\GitProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/GitProcessor.php',
        'Monolog\\Processor\\IntrospectionProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php',
        'Monolog\\Processor\\MemoryPeakUsageProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php',
        'Monolog\\Processor\\MemoryProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php',
        'Monolog\\Processor\\MemoryUsageProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php',
        'Monolog\\Processor\\MercurialProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php',
        'Monolog\\Processor\\ProcessIdProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php',
        'Monolog\\Processor\\ProcessorInterface' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php',
        'Monolog\\Processor\\PsrLogMessageProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php',
        'Monolog\\Processor\\TagProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/TagProcessor.php',
        'Monolog\\Processor\\UidProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/UidProcessor.php',
        'Monolog\\Processor\\WebProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/WebProcessor.php',
        'Monolog\\Registry' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Registry.php',
        'Monolog\\ResettableInterface' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/ResettableInterface.php',
        'Monolog\\SignalHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/SignalHandler.php',
        'Monolog\\Utils' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Utils.php',
        'PHPUnit\\Framework\\Assert' => __DIR__ . '/..' . '/phpunit/phpunit/src/ForwardCompatibility/Assert.php',
        'PHPUnit\\Framework\\AssertionFailedError' => __DIR__ . '/..' . '/phpunit/phpunit/src/ForwardCompatibility/AssertionFailedError.php',
        'PHPUnit\\Framework\\BaseTestListener' => __DIR__ . '/..' . '/phpunit/phpunit/src/ForwardCompatibility/BaseTestListener.php',
        'PHPUnit\\Framework\\Test' => __DIR__ . '/..' . '/phpunit/phpunit/src/ForwardCompatibility/Test.php',
        'PHPUnit\\Framework\\TestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/ForwardCompatibility/TestCase.php',
        'PHPUnit\\Framework\\TestListener' => __DIR__ . '/..' . '/phpunit/phpunit/src/ForwardCompatibility/TestListener.php',
        'PHPUnit\\Framework\\TestSuite' => __DIR__ . '/..' . '/phpunit/phpunit/src/ForwardCompatibility/TestSuite.php',
        'PHPUnit_Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Exception.php',
        'PHPUnit_Extensions_GroupTestSuite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Extensions/GroupTestSuite.php',
        'PHPUnit_Extensions_PhptTestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Extensions/PhptTestCase.php',
        'PHPUnit_Extensions_PhptTestSuite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Extensions/PhptTestSuite.php',
        'PHPUnit_Extensions_RepeatedTest' => __DIR__ . '/..' . '/phpunit/phpunit/src/Extensions/RepeatedTest.php',
        'PHPUnit_Extensions_TestDecorator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Extensions/TestDecorator.php',
        'PHPUnit_Extensions_TicketListener' => __DIR__ . '/..' . '/phpunit/phpunit/src/Extensions/TicketListener.php',
        'PHPUnit_Framework_Assert' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Assert.php',
        'PHPUnit_Framework_AssertionFailedError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/AssertionFailedError.php',
        'PHPUnit_Framework_BaseTestListener' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/BaseTestListener.php',
        'PHPUnit_Framework_CodeCoverageException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/CodeCoverageException.php',
        'PHPUnit_Framework_Constraint' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint.php',
        'PHPUnit_Framework_Constraint_And' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/And.php',
        'PHPUnit_Framework_Constraint_ArrayHasKey' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ArrayHasKey.php',
        'PHPUnit_Framework_Constraint_ArraySubset' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ArraySubset.php',
        'PHPUnit_Framework_Constraint_Attribute' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Attribute.php',
        'PHPUnit_Framework_Constraint_Callback' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Callback.php',
        'PHPUnit_Framework_Constraint_ClassHasAttribute' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ClassHasAttribute.php',
        'PHPUnit_Framework_Constraint_ClassHasStaticAttribute' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ClassHasStaticAttribute.php',
        'PHPUnit_Framework_Constraint_Composite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Composite.php',
        'PHPUnit_Framework_Constraint_Count' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Count.php',
        'PHPUnit_Framework_Constraint_DirectoryExists' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/DirectoryExists.php',
        'PHPUnit_Framework_Constraint_Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Exception.php',
        'PHPUnit_Framework_Constraint_ExceptionCode' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ExceptionCode.php',
        'PHPUnit_Framework_Constraint_ExceptionMessage' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ExceptionMessage.php',
        'PHPUnit_Framework_Constraint_ExceptionMessageRegExp' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ExceptionMessageRegExp.php',
        'PHPUnit_Framework_Constraint_FileExists' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/FileExists.php',
        'PHPUnit_Framework_Constraint_GreaterThan' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/GreaterThan.php',
        'PHPUnit_Framework_Constraint_IsAnything' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsAnything.php',
        'PHPUnit_Framework_Constraint_IsEmpty' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsEmpty.php',
        'PHPUnit_Framework_Constraint_IsEqual' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsEqual.php',
        'PHPUnit_Framework_Constraint_IsFalse' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsFalse.php',
        'PHPUnit_Framework_Constraint_IsFinite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsFinite.php',
        'PHPUnit_Framework_Constraint_IsIdentical' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsIdentical.php',
        'PHPUnit_Framework_Constraint_IsInfinite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsInfinite.php',
        'PHPUnit_Framework_Constraint_IsInstanceOf' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsInstanceOf.php',
        'PHPUnit_Framework_Constraint_IsJson' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsJson.php',
        'PHPUnit_Framework_Constraint_IsNan' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsNan.php',
        'PHPUnit_Framework_Constraint_IsNull' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsNull.php',
        'PHPUnit_Framework_Constraint_IsReadable' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsReadable.php',
        'PHPUnit_Framework_Constraint_IsTrue' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsTrue.php',
        'PHPUnit_Framework_Constraint_IsType' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsType.php',
        'PHPUnit_Framework_Constraint_IsWritable' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsWritable.php',
        'PHPUnit_Framework_Constraint_JsonMatches' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/JsonMatches.php',
        'PHPUnit_Framework_Constraint_JsonMatches_ErrorMessageProvider' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/JsonMatches/ErrorMessageProvider.php',
        'PHPUnit_Framework_Constraint_LessThan' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/LessThan.php',
        'PHPUnit_Framework_Constraint_Not' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Not.php',
        'PHPUnit_Framework_Constraint_ObjectHasAttribute' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ObjectHasAttribute.php',
        'PHPUnit_Framework_Constraint_Or' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Or.php',
        'PHPUnit_Framework_Constraint_PCREMatch' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/PCREMatch.php',
        'PHPUnit_Framework_Constraint_SameSize' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/SameSize.php',
        'PHPUnit_Framework_Constraint_StringContains' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/StringContains.php',
        'PHPUnit_Framework_Constraint_StringEndsWith' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/StringEndsWith.php',
        'PHPUnit_Framework_Constraint_StringMatches' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/StringMatches.php',
        'PHPUnit_Framework_Constraint_StringStartsWith' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/StringStartsWith.php',
        'PHPUnit_Framework_Constraint_TraversableContains' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/TraversableContains.php',
        'PHPUnit_Framework_Constraint_TraversableContainsOnly' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/TraversableContainsOnly.php',
        'PHPUnit_Framework_Constraint_Xor' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Xor.php',
        'PHPUnit_Framework_CoveredCodeNotExecutedException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/CoveredCodeNotExecutedException.php',
        'PHPUnit_Framework_Error' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Error.php',
        'PHPUnit_Framework_Error_Deprecated' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Error/Deprecated.php',
        'PHPUnit_Framework_Error_Notice' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Error/Notice.php',
        'PHPUnit_Framework_Error_Warning' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Error/Warning.php',
        'PHPUnit_Framework_Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception.php',
        'PHPUnit_Framework_ExceptionWrapper' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/ExceptionWrapper.php',
        'PHPUnit_Framework_ExpectationFailedException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/ExpectationFailedException.php',
        'PHPUnit_Framework_IncompleteTest' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/IncompleteTest.php',
        'PHPUnit_Framework_IncompleteTestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/IncompleteTestCase.php',
        'PHPUnit_Framework_IncompleteTestError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/IncompleteTestError.php',
        'PHPUnit_Framework_InvalidCoversTargetException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/InvalidCoversTargetException.php',
        'PHPUnit_Framework_MissingCoversAnnotationException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/MissingCoversAnnotationException.php',
        'PHPUnit_Framework_MockObject_BadMethodCallException' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Exception/BadMethodCallException.php',
        'PHPUnit_Framework_MockObject_Builder_Identity' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Identity.php',
        'PHPUnit_Framework_MockObject_Builder_InvocationMocker' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/InvocationMocker.php',
        'PHPUnit_Framework_MockObject_Builder_Match' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Match.php',
        'PHPUnit_Framework_MockObject_Builder_MethodNameMatch' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/MethodNameMatch.php',
        'PHPUnit_Framework_MockObject_Builder_Namespace' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Namespace.php',
        'PHPUnit_Framework_MockObject_Builder_ParametersMatch' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/ParametersMatch.php',
        'PHPUnit_Framework_MockObject_Builder_Stub' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Stub.php',
        'PHPUnit_Framework_MockObject_Exception' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Exception/Exception.php',
        'PHPUnit_Framework_MockObject_Generator' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Generator.php',
        'PHPUnit_Framework_MockObject_Invocation' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invocation.php',
        'PHPUnit_Framework_MockObject_InvocationMocker' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/InvocationMocker.php',
        'PHPUnit_Framework_MockObject_Invocation_Object' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invocation/Object.php',
        'PHPUnit_Framework_MockObject_Invocation_Static' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invocation/Static.php',
        'PHPUnit_Framework_MockObject_Invokable' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invokable.php',
        'PHPUnit_Framework_MockObject_Matcher' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher.php',
        'PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/AnyInvokedCount.php',
        'PHPUnit_Framework_MockObject_Matcher_AnyParameters' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/AnyParameters.php',
        'PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/ConsecutiveParameters.php',
        'PHPUnit_Framework_MockObject_Matcher_Invocation' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/Invocation.php',
        'PHPUnit_Framework_MockObject_Matcher_InvokedAtIndex' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtIndex.php',
        'PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastCount' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtLeastCount.php',
        'PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastOnce' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtLeastOnce.php',
        'PHPUnit_Framework_MockObject_Matcher_InvokedAtMostCount' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtMostCount.php',
        'PHPUnit_Framework_MockObject_Matcher_InvokedCount' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedCount.php',
        'PHPUnit_Framework_MockObject_Matcher_InvokedRecorder' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedRecorder.php',
        'PHPUnit_Framework_MockObject_Matcher_MethodName' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/MethodName.php',
        'PHPUnit_Framework_MockObject_Matcher_Parameters' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/Parameters.php',
        'PHPUnit_Framework_MockObject_Matcher_StatelessInvocation' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/StatelessInvocation.php',
        'PHPUnit_Framework_MockObject_MockBuilder' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/MockBuilder.php',
        'PHPUnit_Framework_MockObject_MockObject' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/MockObject.php',
        'PHPUnit_Framework_MockObject_RuntimeException' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Exception/RuntimeException.php',
        'PHPUnit_Framework_MockObject_Stub' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub.php',
        'PHPUnit_Framework_MockObject_Stub_ConsecutiveCalls' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ConsecutiveCalls.php',
        'PHPUnit_Framework_MockObject_Stub_Exception' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/Exception.php',
        'PHPUnit_Framework_MockObject_Stub_MatcherCollection' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/MatcherCollection.php',
        'PHPUnit_Framework_MockObject_Stub_Return' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/Return.php',
        'PHPUnit_Framework_MockObject_Stub_ReturnArgument' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnArgument.php',
        'PHPUnit_Framework_MockObject_Stub_ReturnCallback' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnCallback.php',
        'PHPUnit_Framework_MockObject_Stub_ReturnReference' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnReference.php',
        'PHPUnit_Framework_MockObject_Stub_ReturnSelf' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnSelf.php',
        'PHPUnit_Framework_MockObject_Stub_ReturnValueMap' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnValueMap.php',
        'PHPUnit_Framework_MockObject_Verifiable' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Verifiable.php',
        'PHPUnit_Framework_OutputError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/OutputError.php',
        'PHPUnit_Framework_RiskyTest' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/RiskyTest.php',
        'PHPUnit_Framework_RiskyTestError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/RiskyTestError.php',
        'PHPUnit_Framework_SelfDescribing' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SelfDescribing.php',
        'PHPUnit_Framework_SkippedTest' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SkippedTest.php',
        'PHPUnit_Framework_SkippedTestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SkippedTestCase.php',
        'PHPUnit_Framework_SkippedTestError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SkippedTestError.php',
        'PHPUnit_Framework_SkippedTestSuiteError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SkippedTestSuiteError.php',
        'PHPUnit_Framework_SyntheticError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SyntheticError.php',
        'PHPUnit_Framework_Test' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Test.php',
        'PHPUnit_Framework_TestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestCase.php',
        'PHPUnit_Framework_TestFailure' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestFailure.php',
        'PHPUnit_Framework_TestListener' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestListener.php',
        'PHPUnit_Framework_TestResult' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestResult.php',
        'PHPUnit_Framework_TestSuite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestSuite.php',
        'PHPUnit_Framework_TestSuite_DataProvider' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestSuite/DataProvider.php',
        'PHPUnit_Framework_UnintentionallyCoveredCodeError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/UnintentionallyCoveredCodeError.php',
        'PHPUnit_Framework_Warning' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Warning.php',
        'PHPUnit_Framework_WarningTestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/WarningTestCase.php',
        'PHPUnit_Runner_BaseTestRunner' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/BaseTestRunner.php',
        'PHPUnit_Runner_Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Exception.php',
        'PHPUnit_Runner_Filter_Factory' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/Factory.php',
        'PHPUnit_Runner_Filter_GroupFilterIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/Group.php',
        'PHPUnit_Runner_Filter_Group_Exclude' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/Group/Exclude.php',
        'PHPUnit_Runner_Filter_Group_Include' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/Group/Include.php',
        'PHPUnit_Runner_Filter_Test' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/Test.php',
        'PHPUnit_Runner_StandardTestSuiteLoader' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/StandardTestSuiteLoader.php',
        'PHPUnit_Runner_TestSuiteLoader' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/TestSuiteLoader.php',
        'PHPUnit_Runner_Version' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Version.php',
        'PHPUnit_TextUI_Command' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/Command.php',
        'PHPUnit_TextUI_ResultPrinter' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/ResultPrinter.php',
        'PHPUnit_TextUI_TestRunner' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/TestRunner.php',
        'PHPUnit_Util_Blacklist' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Blacklist.php',
        'PHPUnit_Util_Configuration' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Configuration.php',
        'PHPUnit_Util_ConfigurationGenerator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/ConfigurationGenerator.php',
        'PHPUnit_Util_ErrorHandler' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/ErrorHandler.php',
        'PHPUnit_Util_Fileloader' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Fileloader.php',
        'PHPUnit_Util_Filesystem' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Filesystem.php',
        'PHPUnit_Util_Filter' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Filter.php',
        'PHPUnit_Util_Getopt' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Getopt.php',
        'PHPUnit_Util_GlobalState' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/GlobalState.php',
        'PHPUnit_Util_InvalidArgumentHelper' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/InvalidArgumentHelper.php',
        'PHPUnit_Util_Log_JSON' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Log/JSON.php',
        'PHPUnit_Util_Log_JUnit' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Log/JUnit.php',
        'PHPUnit_Util_Log_TAP' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Log/TAP.php',
        'PHPUnit_Util_Log_TeamCity' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Log/TeamCity.php',
        'PHPUnit_Util_PHP' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/PHP.php',
        'PHPUnit_Util_PHP_Default' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/PHP/Default.php',
        'PHPUnit_Util_PHP_Windows' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/PHP/Windows.php',
        'PHPUnit_Util_Printer' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Printer.php',
        'PHPUnit_Util_Regex' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Regex.php',
        'PHPUnit_Util_String' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/String.php',
        'PHPUnit_Util_Test' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Test.php',
        'PHPUnit_Util_TestDox_NamePrettifier' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/NamePrettifier.php',
        'PHPUnit_Util_TestDox_ResultPrinter' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/ResultPrinter.php',
        'PHPUnit_Util_TestDox_ResultPrinter_HTML' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/ResultPrinter/HTML.php',
        'PHPUnit_Util_TestDox_ResultPrinter_Text' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/ResultPrinter/Text.php',
        'PHPUnit_Util_TestDox_ResultPrinter_XML' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/ResultPrinter/XML.php',
        'PHPUnit_Util_TestSuiteIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestSuiteIterator.php',
        'PHPUnit_Util_Type' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Type.php',
        'PHPUnit_Util_XML' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/XML.php',
        'PHP_Timer' => __DIR__ . '/..' . '/phpunit/php-timer/src/Timer.php',
        'PHP_Token' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_TokenWithScope' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_TokenWithScopeAndVisibility' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_ABSTRACT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_AMPERSAND' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_AND_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_ARRAY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_ARRAY_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_AS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_ASYNC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_AT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_AWAIT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_BACKTICK' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_BAD_CHARACTER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_BOOLEAN_AND' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_BOOLEAN_OR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_BOOL_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_BREAK' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_CALLABLE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_CARET' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_CASE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_CATCH' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_CHARACTER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_CLASS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_CLASS_C' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_CLASS_NAME_CONSTANT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_CLONE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_CLOSE_BRACKET' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_CLOSE_CURLY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_CLOSE_SQUARE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_CLOSE_TAG' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_COALESCE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_COLON' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_COMMA' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_COMMENT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_COMPILER_HALT_OFFSET' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_CONCAT_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_CONST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_CONSTANT_ENCAPSED_STRING' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_CONTINUE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_CURLY_OPEN' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_DEC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_DECLARE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_DEFAULT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_DIR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_DIV' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_DIV_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_DNUMBER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_DO' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_DOC_COMMENT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_DOLLAR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_DOLLAR_OPEN_CURLY_BRACES' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_DOT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_DOUBLE_ARROW' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_DOUBLE_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_DOUBLE_COLON' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_DOUBLE_QUOTES' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_ECHO' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_ELLIPSIS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_ELSE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_ELSEIF' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_EMPTY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_ENCAPSED_AND_WHITESPACE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_ENDDECLARE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_ENDFOR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_ENDFOREACH' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_ENDIF' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_ENDSWITCH' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_ENDWHILE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_END_HEREDOC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_ENUM' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_EQUALS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_EVAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_EXCLAMATION_MARK' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_EXIT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_EXTENDS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_FILE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_FINAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_FINALLY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_FOR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_FOREACH' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_FUNCTION' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_FUNC_C' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_GLOBAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_GOTO' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_GT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_HALT_COMPILER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_IF' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_IMPLEMENTS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_IN' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_INC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_INCLUDE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_INCLUDE_ONCE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_INLINE_HTML' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_INSTANCEOF' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_INSTEADOF' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_INTERFACE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_INT_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_ISSET' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_IS_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_IS_GREATER_OR_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_IS_IDENTICAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_IS_NOT_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_IS_NOT_IDENTICAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_IS_SMALLER_OR_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_Includes' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_JOIN' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_LAMBDA_ARROW' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_LAMBDA_CP' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_LAMBDA_OP' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_LINE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_LIST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_LNUMBER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_LOGICAL_AND' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_LOGICAL_OR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_LOGICAL_XOR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_LT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_METHOD_C' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_MINUS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_MINUS_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_MOD_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_MULT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_MUL_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_NAMESPACE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_NEW' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_NS_C' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_NS_SEPARATOR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_NULLSAFE_OBJECT_OPERATOR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_NUM_STRING' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_OBJECT_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_OBJECT_OPERATOR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_ONUMBER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_OPEN_BRACKET' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_OPEN_CURLY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_OPEN_SQUARE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_OPEN_TAG' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_OPEN_TAG_WITH_ECHO' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_OR_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_PAAMAYIM_NEKUDOTAYIM' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_PERCENT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_PIPE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_PLUS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_PLUS_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_POW' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_POW_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_PRINT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_PRIVATE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_PROTECTED' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_PUBLIC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_QUESTION_MARK' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_REQUIRE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_REQUIRE_ONCE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_RETURN' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_SEMICOLON' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_SHAPE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_SL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_SL_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_SPACESHIP' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_SR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_SR_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_START_HEREDOC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_STATIC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_STRING' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_STRING_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_STRING_VARNAME' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_SUPER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_SWITCH' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_Stream' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token/Stream.php',
        'PHP_Token_Stream_CachingFactory' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token/Stream/CachingFactory.php',
        'PHP_Token_THROW' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_TILDE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_TRAIT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_TRAIT_C' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_TRY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_TYPE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_TYPELIST_GT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_TYPELIST_LT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_UNSET' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_UNSET_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_USE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_USE_FUNCTION' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_VAR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_VARIABLE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_WHERE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_WHILE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_WHITESPACE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_XHP_ATTRIBUTE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_XHP_CATEGORY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_XHP_CATEGORY_LABEL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_XHP_CHILDREN' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_XHP_LABEL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_XHP_REQUIRED' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_XHP_TAG_GT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_XHP_TAG_LT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_XHP_TEXT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_XOR_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_YIELD' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'PHP_Token_YIELD_FROM' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php',
        'Phinx\\Config\\Config' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Config/Config.php',
        'Phinx\\Config\\ConfigInterface' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Config/ConfigInterface.php',
        'Phinx\\Config\\NamespaceAwareInterface' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Config/NamespaceAwareInterface.php',
        'Phinx\\Config\\NamespaceAwareTrait' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Config/NamespaceAwareTrait.php',
        'Phinx\\Console\\Command\\AbstractCommand' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/AbstractCommand.php',
        'Phinx\\Console\\Command\\Breakpoint' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/Breakpoint.php',
        'Phinx\\Console\\Command\\Create' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/Create.php',
        'Phinx\\Console\\Command\\Init' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/Init.php',
        'Phinx\\Console\\Command\\Migrate' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/Migrate.php',
        'Phinx\\Console\\Command\\Rollback' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/Rollback.php',
        'Phinx\\Console\\Command\\SeedCreate' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/SeedCreate.php',
        'Phinx\\Console\\Command\\SeedRun' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/SeedRun.php',
        'Phinx\\Console\\Command\\Status' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/Status.php',
        'Phinx\\Console\\Command\\Test' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/Test.php',
        'Phinx\\Console\\PhinxApplication' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/PhinxApplication.php',
        'Phinx\\Db\\Adapter\\AbstractAdapter' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/AbstractAdapter.php',
        'Phinx\\Db\\Adapter\\AdapterFactory' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterFactory.php',
        'Phinx\\Db\\Adapter\\AdapterInterface' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterInterface.php',
        'Phinx\\Db\\Adapter\\AdapterWrapper' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterWrapper.php',
        'Phinx\\Db\\Adapter\\MysqlAdapter' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/MysqlAdapter.php',
        'Phinx\\Db\\Adapter\\PdoAdapter' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/PdoAdapter.php',
        'Phinx\\Db\\Adapter\\PostgresAdapter' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/PostgresAdapter.php',
        'Phinx\\Db\\Adapter\\ProxyAdapter' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/ProxyAdapter.php',
        'Phinx\\Db\\Adapter\\SQLiteAdapter' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/SQLiteAdapter.php',
        'Phinx\\Db\\Adapter\\SqlServerAdapter' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/SqlServerAdapter.php',
        'Phinx\\Db\\Adapter\\TablePrefixAdapter' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/TablePrefixAdapter.php',
        'Phinx\\Db\\Adapter\\TimedOutputAdapter' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/TimedOutputAdapter.php',
        'Phinx\\Db\\Adapter\\WrapperInterface' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/WrapperInterface.php',
        'Phinx\\Db\\Table' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Table.php',
        'Phinx\\Db\\Table\\Column' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Table/Column.php',
        'Phinx\\Db\\Table\\ForeignKey' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Table/ForeignKey.php',
        'Phinx\\Db\\Table\\Index' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Table/Index.php',
        'Phinx\\Migration\\AbstractMigration' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Migration/AbstractMigration.php',
        'Phinx\\Migration\\AbstractTemplateCreation' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Migration/AbstractTemplateCreation.php',
        'Phinx\\Migration\\CreationInterface' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Migration/CreationInterface.php',
        'Phinx\\Migration\\IrreversibleMigrationException' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Migration/IrreversibleMigrationException.php',
        'Phinx\\Migration\\Manager' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Migration/Manager.php',
        'Phinx\\Migration\\Manager\\Environment' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Migration/Manager/Environment.php',
        'Phinx\\Migration\\MigrationInterface' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Migration/MigrationInterface.php',
        'Phinx\\Seed\\AbstractSeed' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Seed/AbstractSeed.php',
        'Phinx\\Seed\\SeedInterface' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Seed/SeedInterface.php',
        'Phinx\\Util\\Util' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Util/Util.php',
        'Phinx\\Wrapper\\TextWrapper' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Wrapper/TextWrapper.php',
        'PhpAmqpLib\\Channel\\AMQPChannel' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Channel/AMQPChannel.php',
        'PhpAmqpLib\\Channel\\AbstractChannel' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Channel/AbstractChannel.php',
        'PhpAmqpLib\\Connection\\AMQPConnection' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AMQPConnection.php',
        'PhpAmqpLib\\Connection\\AMQPLazyConnection' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AMQPLazyConnection.php',
        'PhpAmqpLib\\Connection\\AMQPLazySocketConnection' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AMQPLazySocketConnection.php',
        'PhpAmqpLib\\Connection\\AMQPSSLConnection' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AMQPSSLConnection.php',
        'PhpAmqpLib\\Connection\\AMQPSocketConnection' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AMQPSocketConnection.php',
        'PhpAmqpLib\\Connection\\AMQPStreamConnection' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AMQPStreamConnection.php',
        'PhpAmqpLib\\Connection\\AbstractConnection' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Connection/AbstractConnection.php',
        'PhpAmqpLib\\Exception\\AMQPBasicCancelException' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPBasicCancelException.php',
        'PhpAmqpLib\\Exception\\AMQPChannelClosedException' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPChannelClosedException.php',
        'PhpAmqpLib\\Exception\\AMQPChannelException' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPChannelException.php',
        'PhpAmqpLib\\Exception\\AMQPConnectionClosedException' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPConnectionClosedException.php',
        'PhpAmqpLib\\Exception\\AMQPConnectionException' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPConnectionException.php',
        'PhpAmqpLib\\Exception\\AMQPDataReadException' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPDataReadException.php',
        'PhpAmqpLib\\Exception\\AMQPException' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPException.php',
        'PhpAmqpLib\\Exception\\AMQPExceptionInterface' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPExceptionInterface.php',
        'PhpAmqpLib\\Exception\\AMQPHeartbeatMissedException' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPHeartbeatMissedException.php',
        'PhpAmqpLib\\Exception\\AMQPIOException' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPIOException.php',
        'PhpAmqpLib\\Exception\\AMQPIOWaitException' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPIOWaitException.php',
        'PhpAmqpLib\\Exception\\AMQPInvalidArgumentException' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPInvalidArgumentException.php',
        'PhpAmqpLib\\Exception\\AMQPInvalidFrameException' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPInvalidFrameException.php',
        'PhpAmqpLib\\Exception\\AMQPLogicException' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPLogicException.php',
        'PhpAmqpLib\\Exception\\AMQPNoDataException' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPNoDataException.php',
        'PhpAmqpLib\\Exception\\AMQPNotImplementedException' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPNotImplementedException.php',
        'PhpAmqpLib\\Exception\\AMQPOutOfBoundsException' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPOutOfBoundsException.php',
        'PhpAmqpLib\\Exception\\AMQPOutOfRangeException' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPOutOfRangeException.php',
        'PhpAmqpLib\\Exception\\AMQPProtocolChannelException' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPProtocolChannelException.php',
        'PhpAmqpLib\\Exception\\AMQPProtocolConnectionException' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPProtocolConnectionException.php',
        'PhpAmqpLib\\Exception\\AMQPProtocolException' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPProtocolException.php',
        'PhpAmqpLib\\Exception\\AMQPRuntimeException' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPRuntimeException.php',
        'PhpAmqpLib\\Exception\\AMQPSocketException' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPSocketException.php',
        'PhpAmqpLib\\Exception\\AMQPTimeoutException' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Exception/AMQPTimeoutException.php',
        'PhpAmqpLib\\Exchange\\AMQPExchangeType' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Exchange/AMQPExchangeType.php',
        'PhpAmqpLib\\Helper\\DebugHelper' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Helper/DebugHelper.php',
        'PhpAmqpLib\\Helper\\MiscHelper' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Helper/MiscHelper.php',
        'PhpAmqpLib\\Helper\\Protocol\\MethodMap080' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Helper/Protocol/MethodMap080.php',
        'PhpAmqpLib\\Helper\\Protocol\\MethodMap091' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Helper/Protocol/MethodMap091.php',
        'PhpAmqpLib\\Helper\\Protocol\\Protocol080' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Helper/Protocol/Protocol080.php',
        'PhpAmqpLib\\Helper\\Protocol\\Protocol091' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Helper/Protocol/Protocol091.php',
        'PhpAmqpLib\\Helper\\Protocol\\Wait080' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Helper/Protocol/Wait080.php',
        'PhpAmqpLib\\Helper\\Protocol\\Wait091' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Helper/Protocol/Wait091.php',
        'PhpAmqpLib\\Message\\AMQPMessage' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Message/AMQPMessage.php',
        'PhpAmqpLib\\Wire\\AMQPAbstractCollection' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Wire/AMQPAbstractCollection.php',
        'PhpAmqpLib\\Wire\\AMQPArray' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Wire/AMQPArray.php',
        'PhpAmqpLib\\Wire\\AMQPDecimal' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Wire/AMQPDecimal.php',
        'PhpAmqpLib\\Wire\\AMQPReader' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Wire/AMQPReader.php',
        'PhpAmqpLib\\Wire\\AMQPTable' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Wire/AMQPTable.php',
        'PhpAmqpLib\\Wire\\AMQPWriter' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Wire/AMQPWriter.php',
        'PhpAmqpLib\\Wire\\AbstractClient' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Wire/AbstractClient.php',
        'PhpAmqpLib\\Wire\\Constants080' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Wire/Constants080.php',
        'PhpAmqpLib\\Wire\\Constants091' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Wire/Constants091.php',
        'PhpAmqpLib\\Wire\\GenericContent' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Wire/GenericContent.php',
        'PhpAmqpLib\\Wire\\IO\\AbstractIO' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Wire/IO/AbstractIO.php',
        'PhpAmqpLib\\Wire\\IO\\SocketIO' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Wire/IO/SocketIO.php',
        'PhpAmqpLib\\Wire\\IO\\StreamIO' => __DIR__ . '/..' . '/php-amqplib/php-amqplib/PhpAmqpLib/Wire/IO/StreamIO.php',
        'Prophecy\\Argument' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument.php',
        'Prophecy\\Argument\\ArgumentsWildcard' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/ArgumentsWildcard.php',
        'Prophecy\\Argument\\Token\\AnyValueToken' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/AnyValueToken.php',
        'Prophecy\\Argument\\Token\\AnyValuesToken' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/AnyValuesToken.php',
        'Prophecy\\Argument\\Token\\ApproximateValueToken' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/ApproximateValueToken.php',
        'Prophecy\\Argument\\Token\\ArrayCountToken' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/ArrayCountToken.php',
        'Prophecy\\Argument\\Token\\ArrayEntryToken' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/ArrayEntryToken.php',
        'Prophecy\\Argument\\Token\\ArrayEveryEntryToken' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/ArrayEveryEntryToken.php',
        'Prophecy\\Argument\\Token\\CallbackToken' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/CallbackToken.php',
        'Prophecy\\Argument\\Token\\ExactValueToken' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/ExactValueToken.php',
        'Prophecy\\Argument\\Token\\IdenticalValueToken' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/IdenticalValueToken.php',
        'Prophecy\\Argument\\Token\\LogicalAndToken' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/LogicalAndToken.php',
        'Prophecy\\Argument\\Token\\LogicalNotToken' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/LogicalNotToken.php',
        'Prophecy\\Argument\\Token\\ObjectStateToken' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/ObjectStateToken.php',
        'Prophecy\\Argument\\Token\\StringContainsToken' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/StringContainsToken.php',
        'Prophecy\\Argument\\Token\\TokenInterface' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/TokenInterface.php',
        'Prophecy\\Argument\\Token\\TypeToken' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Argument/Token/TypeToken.php',
        'Prophecy\\Call\\Call' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Call/Call.php',
        'Prophecy\\Call\\CallCenter' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Call/CallCenter.php',
        'Prophecy\\Comparator\\ClosureComparator' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Comparator/ClosureComparator.php',
        'Prophecy\\Comparator\\Factory' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Comparator/Factory.php',
        'Prophecy\\Comparator\\ProphecyComparator' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Comparator/ProphecyComparator.php',
        'Prophecy\\Doubler\\CachedDoubler' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/CachedDoubler.php',
        'Prophecy\\Doubler\\ClassPatch\\ClassPatchInterface' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ClassPatchInterface.php',
        'Prophecy\\Doubler\\ClassPatch\\DisableConstructorPatch' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/DisableConstructorPatch.php',
        'Prophecy\\Doubler\\ClassPatch\\HhvmExceptionPatch' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/HhvmExceptionPatch.php',
        'Prophecy\\Doubler\\ClassPatch\\KeywordPatch' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/KeywordPatch.php',
        'Prophecy\\Doubler\\ClassPatch\\MagicCallPatch' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/MagicCallPatch.php',
        'Prophecy\\Doubler\\ClassPatch\\ProphecySubjectPatch' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ProphecySubjectPatch.php',
        'Prophecy\\Doubler\\ClassPatch\\ReflectionClassNewInstancePatch' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ReflectionClassNewInstancePatch.php',
        'Prophecy\\Doubler\\ClassPatch\\SplFileInfoPatch' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/SplFileInfoPatch.php',
        'Prophecy\\Doubler\\ClassPatch\\ThrowablePatch' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ThrowablePatch.php',
        'Prophecy\\Doubler\\ClassPatch\\TraversablePatch' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/TraversablePatch.php',
        'Prophecy\\Doubler\\DoubleInterface' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/DoubleInterface.php',
        'Prophecy\\Doubler\\Doubler' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/Doubler.php',
        'Prophecy\\Doubler\\Generator\\ClassCodeGenerator' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassCodeGenerator.php',
        'Prophecy\\Doubler\\Generator\\ClassCreator' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassCreator.php',
        'Prophecy\\Doubler\\Generator\\ClassMirror' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassMirror.php',
        'Prophecy\\Doubler\\Generator\\Node\\ArgumentNode' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ArgumentNode.php',
        'Prophecy\\Doubler\\Generator\\Node\\ClassNode' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ClassNode.php',
        'Prophecy\\Doubler\\Generator\\Node\\MethodNode' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/MethodNode.php',
        'Prophecy\\Doubler\\Generator\\ReflectionInterface' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/ReflectionInterface.php',
        'Prophecy\\Doubler\\Generator\\TypeHintReference' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/Generator/TypeHintReference.php',
        'Prophecy\\Doubler\\LazyDouble' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/LazyDouble.php',
        'Prophecy\\Doubler\\NameGenerator' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Doubler/NameGenerator.php',
        'Prophecy\\Exception\\Call\\UnexpectedCallException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Call/UnexpectedCallException.php',
        'Prophecy\\Exception\\Doubler\\ClassCreatorException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/ClassCreatorException.php',
        'Prophecy\\Exception\\Doubler\\ClassMirrorException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/ClassMirrorException.php',
        'Prophecy\\Exception\\Doubler\\ClassNotFoundException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/ClassNotFoundException.php',
        'Prophecy\\Exception\\Doubler\\DoubleException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/DoubleException.php',
        'Prophecy\\Exception\\Doubler\\DoublerException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/DoublerException.php',
        'Prophecy\\Exception\\Doubler\\InterfaceNotFoundException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/InterfaceNotFoundException.php',
        'Prophecy\\Exception\\Doubler\\MethodNotExtendableException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/MethodNotExtendableException.php',
        'Prophecy\\Exception\\Doubler\\MethodNotFoundException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/MethodNotFoundException.php',
        'Prophecy\\Exception\\Doubler\\ReturnByReferenceException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Doubler/ReturnByReferenceException.php',
        'Prophecy\\Exception\\Exception' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Exception.php',
        'Prophecy\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/InvalidArgumentException.php',
        'Prophecy\\Exception\\Prediction\\AggregateException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Prediction/AggregateException.php',
        'Prophecy\\Exception\\Prediction\\FailedPredictionException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Prediction/FailedPredictionException.php',
        'Prophecy\\Exception\\Prediction\\NoCallsException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Prediction/NoCallsException.php',
        'Prophecy\\Exception\\Prediction\\PredictionException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Prediction/PredictionException.php',
        'Prophecy\\Exception\\Prediction\\UnexpectedCallsCountException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Prediction/UnexpectedCallsCountException.php',
        'Prophecy\\Exception\\Prediction\\UnexpectedCallsException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Prediction/UnexpectedCallsException.php',
        'Prophecy\\Exception\\Prophecy\\MethodProphecyException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Prophecy/MethodProphecyException.php',
        'Prophecy\\Exception\\Prophecy\\ObjectProphecyException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Prophecy/ObjectProphecyException.php',
        'Prophecy\\Exception\\Prophecy\\ProphecyException' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Exception/Prophecy/ProphecyException.php',
        'Prophecy\\PhpDocumentor\\ClassAndInterfaceTagRetriever' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/PhpDocumentor/ClassAndInterfaceTagRetriever.php',
        'Prophecy\\PhpDocumentor\\ClassTagRetriever' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/PhpDocumentor/ClassTagRetriever.php',
        'Prophecy\\PhpDocumentor\\LegacyClassTagRetriever' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/PhpDocumentor/LegacyClassTagRetriever.php',
        'Prophecy\\PhpDocumentor\\MethodTagRetrieverInterface' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/PhpDocumentor/MethodTagRetrieverInterface.php',
        'Prophecy\\Prediction\\CallPrediction' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Prediction/CallPrediction.php',
        'Prophecy\\Prediction\\CallTimesPrediction' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Prediction/CallTimesPrediction.php',
        'Prophecy\\Prediction\\CallbackPrediction' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Prediction/CallbackPrediction.php',
        'Prophecy\\Prediction\\NoCallsPrediction' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Prediction/NoCallsPrediction.php',
        'Prophecy\\Prediction\\PredictionInterface' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Prediction/PredictionInterface.php',
        'Prophecy\\Promise\\CallbackPromise' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Promise/CallbackPromise.php',
        'Prophecy\\Promise\\PromiseInterface' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Promise/PromiseInterface.php',
        'Prophecy\\Promise\\ReturnArgumentPromise' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Promise/ReturnArgumentPromise.php',
        'Prophecy\\Promise\\ReturnPromise' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Promise/ReturnPromise.php',
        'Prophecy\\Promise\\ThrowPromise' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Promise/ThrowPromise.php',
        'Prophecy\\Prophecy\\MethodProphecy' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Prophecy/MethodProphecy.php',
        'Prophecy\\Prophecy\\ObjectProphecy' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Prophecy/ObjectProphecy.php',
        'Prophecy\\Prophecy\\ProphecyInterface' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Prophecy/ProphecyInterface.php',
        'Prophecy\\Prophecy\\ProphecySubjectInterface' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Prophecy/ProphecySubjectInterface.php',
        'Prophecy\\Prophecy\\Revealer' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Prophecy/Revealer.php',
        'Prophecy\\Prophecy\\RevealerInterface' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Prophecy/RevealerInterface.php',
        'Prophecy\\Prophet' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Prophet.php',
        'Prophecy\\Util\\ExportUtil' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Util/ExportUtil.php',
        'Prophecy\\Util\\StringUtil' => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy/Util/StringUtil.php',
        'Psr\\Log\\AbstractLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/AbstractLogger.php',
        'Psr\\Log\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/log/Psr/Log/InvalidArgumentException.php',
        'Psr\\Log\\LogLevel' => __DIR__ . '/..' . '/psr/log/Psr/Log/LogLevel.php',
        'Psr\\Log\\LoggerAwareInterface' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerAwareInterface.php',
        'Psr\\Log\\LoggerAwareTrait' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerAwareTrait.php',
        'Psr\\Log\\LoggerInterface' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerInterface.php',
        'Psr\\Log\\LoggerTrait' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerTrait.php',
        'Psr\\Log\\NullLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/NullLogger.php',
        'Psr\\Log\\Test\\DummyTest' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php',
        'Psr\\Log\\Test\\LoggerInterfaceTest' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php',
        'Psr\\Log\\Test\\TestLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/TestLogger.php',
        'React\\Promise\\CancellablePromiseInterface' => __DIR__ . '/..' . '/react/promise/src/CancellablePromiseInterface.php',
        'React\\Promise\\CancellationQueue' => __DIR__ . '/..' . '/react/promise/src/CancellationQueue.php',
        'React\\Promise\\Deferred' => __DIR__ . '/..' . '/react/promise/src/Deferred.php',
        'React\\Promise\\Exception\\LengthException' => __DIR__ . '/..' . '/react/promise/src/Exception/LengthException.php',
        'React\\Promise\\ExtendedPromiseInterface' => __DIR__ . '/..' . '/react/promise/src/ExtendedPromiseInterface.php',
        'React\\Promise\\FulfilledPromise' => __DIR__ . '/..' . '/react/promise/src/FulfilledPromise.php',
        'React\\Promise\\LazyPromise' => __DIR__ . '/..' . '/react/promise/src/LazyPromise.php',
        'React\\Promise\\Promise' => __DIR__ . '/..' . '/react/promise/src/Promise.php',
        'React\\Promise\\PromiseInterface' => __DIR__ . '/..' . '/react/promise/src/PromiseInterface.php',
        'React\\Promise\\PromisorInterface' => __DIR__ . '/..' . '/react/promise/src/PromisorInterface.php',
        'React\\Promise\\RejectedPromise' => __DIR__ . '/..' . '/react/promise/src/RejectedPromise.php',
        'React\\Promise\\UnhandledRejectionException' => __DIR__ . '/..' . '/react/promise/src/UnhandledRejectionException.php',
        'SebastianBergmann\\CodeCoverage\\CodeCoverage' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage.php',
        'SebastianBergmann\\CodeCoverage\\CoveredCodeNotExecutedException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/CoveredCodeNotExecutedException.php',
        'SebastianBergmann\\CodeCoverage\\Driver\\Driver' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Driver/Driver.php',
        'SebastianBergmann\\CodeCoverage\\Driver\\HHVM' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Driver/HHVM.php',
        'SebastianBergmann\\CodeCoverage\\Driver\\PHPDBG' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Driver/PHPDBG.php',
        'SebastianBergmann\\CodeCoverage\\Driver\\Xdebug' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Driver/Xdebug.php',
        'SebastianBergmann\\CodeCoverage\\Exception' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/Exception.php',
        'SebastianBergmann\\CodeCoverage\\Filter' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Filter.php',
        'SebastianBergmann\\CodeCoverage\\InvalidArgumentException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/InvalidArgumentException.php',
        'SebastianBergmann\\CodeCoverage\\MissingCoversAnnotationException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/MissingCoversAnnotationException.php',
        'SebastianBergmann\\CodeCoverage\\Node\\AbstractNode' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Node/AbstractNode.php',
        'SebastianBergmann\\CodeCoverage\\Node\\Builder' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Node/Builder.php',
        'SebastianBergmann\\CodeCoverage\\Node\\Directory' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Node/Directory.php',
        'SebastianBergmann\\CodeCoverage\\Node\\File' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Node/File.php',
        'SebastianBergmann\\CodeCoverage\\Node\\Iterator' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Node/Iterator.php',
        'SebastianBergmann\\CodeCoverage\\Report\\Clover' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Clover.php',
        'SebastianBergmann\\CodeCoverage\\Report\\Crap4j' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Crap4j.php',
        'SebastianBergmann\\CodeCoverage\\Report\\Html\\Dashboard' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Html/Renderer/Dashboard.php',
        'SebastianBergmann\\CodeCoverage\\Report\\Html\\Directory' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Html/Renderer/Directory.php',
        'SebastianBergmann\\CodeCoverage\\Report\\Html\\Facade' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Html/Facade.php',
        'SebastianBergmann\\CodeCoverage\\Report\\Html\\File' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Html/Renderer/File.php',
        'SebastianBergmann\\CodeCoverage\\Report\\Html\\Renderer' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Html/Renderer.php',
        'SebastianBergmann\\CodeCoverage\\Report\\PHP' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/PHP.php',
        'SebastianBergmann\\CodeCoverage\\Report\\Text' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Text.php',
        'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Coverage' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Coverage.php',
        'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Directory' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Directory.php',
        'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Facade' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Facade.php',
        'SebastianBergmann\\CodeCoverage\\Report\\Xml\\File' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/File.php',
        'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Method' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Method.php',
        'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Node' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Node.php',
        'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Project' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Project.php',
        'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Report' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Report.php',
        'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Tests' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Tests.php',
        'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Totals' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Totals.php',
        'SebastianBergmann\\CodeCoverage\\Report\\Xml\\Unit' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Report/Xml/Unit.php',
        'SebastianBergmann\\CodeCoverage\\RuntimeException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/RuntimeException.php',
        'SebastianBergmann\\CodeCoverage\\UnintentionallyCoveredCodeException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/UnintentionallyCoveredCodeException.php',
        'SebastianBergmann\\CodeCoverage\\Util' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Util.php',
        'SebastianBergmann\\CodeUnitReverseLookup\\Wizard' => __DIR__ . '/..' . '/sebastian/code-unit-reverse-lookup/src/Wizard.php',
        'SebastianBergmann\\Comparator\\ArrayComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ArrayComparator.php',
        'SebastianBergmann\\Comparator\\Comparator' => __DIR__ . '/..' . '/sebastian/comparator/src/Comparator.php',
        'SebastianBergmann\\Comparator\\ComparisonFailure' => __DIR__ . '/..' . '/sebastian/comparator/src/ComparisonFailure.php',
        'SebastianBergmann\\Comparator\\DOMNodeComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/DOMNodeComparator.php',
        'SebastianBergmann\\Comparator\\DateTimeComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/DateTimeComparator.php',
        'SebastianBergmann\\Comparator\\DoubleComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/DoubleComparator.php',
        'SebastianBergmann\\Comparator\\ExceptionComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ExceptionComparator.php',
        'SebastianBergmann\\Comparator\\Factory' => __DIR__ . '/..' . '/sebastian/comparator/src/Factory.php',
        'SebastianBergmann\\Comparator\\MockObjectComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/MockObjectComparator.php',
        'SebastianBergmann\\Comparator\\NumericComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/NumericComparator.php',
        'SebastianBergmann\\Comparator\\ObjectComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ObjectComparator.php',
        'SebastianBergmann\\Comparator\\ResourceComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ResourceComparator.php',
        'SebastianBergmann\\Comparator\\ScalarComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ScalarComparator.php',
        'SebastianBergmann\\Comparator\\SplObjectStorageComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/SplObjectStorageComparator.php',
        'SebastianBergmann\\Comparator\\TypeComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/TypeComparator.php',
        'SebastianBergmann\\Diff\\Chunk' => __DIR__ . '/..' . '/sebastian/diff/src/Chunk.php',
        'SebastianBergmann\\Diff\\Diff' => __DIR__ . '/..' . '/sebastian/diff/src/Diff.php',
        'SebastianBergmann\\Diff\\Differ' => __DIR__ . '/..' . '/sebastian/diff/src/Differ.php',
        'SebastianBergmann\\Diff\\LCS\\LongestCommonSubsequence' => __DIR__ . '/..' . '/sebastian/diff/src/LCS/LongestCommonSubsequence.php',
        'SebastianBergmann\\Diff\\LCS\\MemoryEfficientImplementation' => __DIR__ . '/..' . '/sebastian/diff/src/LCS/MemoryEfficientLongestCommonSubsequenceImplementation.php',
        'SebastianBergmann\\Diff\\LCS\\TimeEfficientImplementation' => __DIR__ . '/..' . '/sebastian/diff/src/LCS/TimeEfficientLongestCommonSubsequenceImplementation.php',
        'SebastianBergmann\\Diff\\Line' => __DIR__ . '/..' . '/sebastian/diff/src/Line.php',
        'SebastianBergmann\\Diff\\Parser' => __DIR__ . '/..' . '/sebastian/diff/src/Parser.php',
        'SebastianBergmann\\Environment\\Console' => __DIR__ . '/..' . '/sebastian/environment/src/Console.php',
        'SebastianBergmann\\Environment\\Runtime' => __DIR__ . '/..' . '/sebastian/environment/src/Runtime.php',
        'SebastianBergmann\\Exporter\\Exporter' => __DIR__ . '/..' . '/sebastian/exporter/src/Exporter.php',
        'SebastianBergmann\\GlobalState\\Blacklist' => __DIR__ . '/..' . '/sebastian/global-state/src/Blacklist.php',
        'SebastianBergmann\\GlobalState\\CodeExporter' => __DIR__ . '/..' . '/sebastian/global-state/src/CodeExporter.php',
        'SebastianBergmann\\GlobalState\\Exception' => __DIR__ . '/..' . '/sebastian/global-state/src/Exception.php',
        'SebastianBergmann\\GlobalState\\Restorer' => __DIR__ . '/..' . '/sebastian/global-state/src/Restorer.php',
        'SebastianBergmann\\GlobalState\\RuntimeException' => __DIR__ . '/..' . '/sebastian/global-state/src/RuntimeException.php',
        'SebastianBergmann\\GlobalState\\Snapshot' => __DIR__ . '/..' . '/sebastian/global-state/src/Snapshot.php',
        'SebastianBergmann\\ObjectEnumerator\\Enumerator' => __DIR__ . '/..' . '/sebastian/object-enumerator/src/Enumerator.php',
        'SebastianBergmann\\ObjectEnumerator\\Exception' => __DIR__ . '/..' . '/sebastian/object-enumerator/src/Exception.php',
        'SebastianBergmann\\ObjectEnumerator\\InvalidArgumentException' => __DIR__ . '/..' . '/sebastian/object-enumerator/src/InvalidArgumentException.php',
        'SebastianBergmann\\RecursionContext\\Context' => __DIR__ . '/..' . '/sebastian/recursion-context/src/Context.php',
        'SebastianBergmann\\RecursionContext\\Exception' => __DIR__ . '/..' . '/sebastian/recursion-context/src/Exception.php',
        'SebastianBergmann\\RecursionContext\\InvalidArgumentException' => __DIR__ . '/..' . '/sebastian/recursion-context/src/InvalidArgumentException.php',
        'SebastianBergmann\\ResourceOperations\\ResourceOperations' => __DIR__ . '/..' . '/sebastian/resource-operations/src/ResourceOperations.php',
        'SebastianBergmann\\Version' => __DIR__ . '/..' . '/sebastian/version/src/Version.php',
        'Symfony\\Component\\Config\\ConfigCache' => __DIR__ . '/..' . '/symfony/config/ConfigCache.php',
        'Symfony\\Component\\Config\\ConfigCacheFactory' => __DIR__ . '/..' . '/symfony/config/ConfigCacheFactory.php',
        'Symfony\\Component\\Config\\ConfigCacheFactoryInterface' => __DIR__ . '/..' . '/symfony/config/ConfigCacheFactoryInterface.php',
        'Symfony\\Component\\Config\\ConfigCacheInterface' => __DIR__ . '/..' . '/symfony/config/ConfigCacheInterface.php',
        'Symfony\\Component\\Config\\Definition\\ArrayNode' => __DIR__ . '/..' . '/symfony/config/Definition/ArrayNode.php',
        'Symfony\\Component\\Config\\Definition\\BaseNode' => __DIR__ . '/..' . '/symfony/config/Definition/BaseNode.php',
        'Symfony\\Component\\Config\\Definition\\BooleanNode' => __DIR__ . '/..' . '/symfony/config/Definition/BooleanNode.php',
        'Symfony\\Component\\Config\\Definition\\Builder\\ArrayNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ArrayNodeDefinition.php',
        'Symfony\\Component\\Config\\Definition\\Builder\\BooleanNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/BooleanNodeDefinition.php',
        'Symfony\\Component\\Config\\Definition\\Builder\\BuilderAwareInterface' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/BuilderAwareInterface.php',
        'Symfony\\Component\\Config\\Definition\\Builder\\EnumNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/EnumNodeDefinition.php',
        'Symfony\\Component\\Config\\Definition\\Builder\\ExprBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ExprBuilder.php',
        'Symfony\\Component\\Config\\Definition\\Builder\\FloatNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/FloatNodeDefinition.php',
        'Symfony\\Component\\Config\\Definition\\Builder\\IntegerNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/IntegerNodeDefinition.php',
        'Symfony\\Component\\Config\\Definition\\Builder\\MergeBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/MergeBuilder.php',
        'Symfony\\Component\\Config\\Definition\\Builder\\NodeBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/NodeBuilder.php',
        'Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/NodeDefinition.php',
        'Symfony\\Component\\Config\\Definition\\Builder\\NodeParentInterface' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/NodeParentInterface.php',
        'Symfony\\Component\\Config\\Definition\\Builder\\NormalizationBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/NormalizationBuilder.php',
        'Symfony\\Component\\Config\\Definition\\Builder\\NumericNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/NumericNodeDefinition.php',
        'Symfony\\Component\\Config\\Definition\\Builder\\ParentNodeDefinitionInterface' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php',
        'Symfony\\Component\\Config\\Definition\\Builder\\ScalarNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ScalarNodeDefinition.php',
        'Symfony\\Component\\Config\\Definition\\Builder\\TreeBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/TreeBuilder.php',
        'Symfony\\Component\\Config\\Definition\\Builder\\ValidationBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ValidationBuilder.php',
        'Symfony\\Component\\Config\\Definition\\Builder\\VariableNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/VariableNodeDefinition.php',
        'Symfony\\Component\\Config\\Definition\\ConfigurationInterface' => __DIR__ . '/..' . '/symfony/config/Definition/ConfigurationInterface.php',
        'Symfony\\Component\\Config\\Definition\\Dumper\\XmlReferenceDumper' => __DIR__ . '/..' . '/symfony/config/Definition/Dumper/XmlReferenceDumper.php',
        'Symfony\\Component\\Config\\Definition\\Dumper\\YamlReferenceDumper' => __DIR__ . '/..' . '/symfony/config/Definition/Dumper/YamlReferenceDumper.php',
        'Symfony\\Component\\Config\\Definition\\EnumNode' => __DIR__ . '/..' . '/symfony/config/Definition/EnumNode.php',
        'Symfony\\Component\\Config\\Definition\\Exception\\DuplicateKeyException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/DuplicateKeyException.php',
        'Symfony\\Component\\Config\\Definition\\Exception\\Exception' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/Exception.php',
        'Symfony\\Component\\Config\\Definition\\Exception\\ForbiddenOverwriteException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/ForbiddenOverwriteException.php',
        'Symfony\\Component\\Config\\Definition\\Exception\\InvalidConfigurationException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/InvalidConfigurationException.php',
        'Symfony\\Component\\Config\\Definition\\Exception\\InvalidDefinitionException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/InvalidDefinitionException.php',
        'Symfony\\Component\\Config\\Definition\\Exception\\InvalidTypeException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/InvalidTypeException.php',
        'Symfony\\Component\\Config\\Definition\\Exception\\TreeWithoutRootNodeException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/TreeWithoutRootNodeException.php',
        'Symfony\\Component\\Config\\Definition\\Exception\\UnsetKeyException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/UnsetKeyException.php',
        'Symfony\\Component\\Config\\Definition\\FloatNode' => __DIR__ . '/..' . '/symfony/config/Definition/FloatNode.php',
        'Symfony\\Component\\Config\\Definition\\IntegerNode' => __DIR__ . '/..' . '/symfony/config/Definition/IntegerNode.php',
        'Symfony\\Component\\Config\\Definition\\NodeInterface' => __DIR__ . '/..' . '/symfony/config/Definition/NodeInterface.php',
        'Symfony\\Component\\Config\\Definition\\NumericNode' => __DIR__ . '/..' . '/symfony/config/Definition/NumericNode.php',
        'Symfony\\Component\\Config\\Definition\\Processor' => __DIR__ . '/..' . '/symfony/config/Definition/Processor.php',
        'Symfony\\Component\\Config\\Definition\\PrototypeNodeInterface' => __DIR__ . '/..' . '/symfony/config/Definition/PrototypeNodeInterface.php',
        'Symfony\\Component\\Config\\Definition\\PrototypedArrayNode' => __DIR__ . '/..' . '/symfony/config/Definition/PrototypedArrayNode.php',
        'Symfony\\Component\\Config\\Definition\\ScalarNode' => __DIR__ . '/..' . '/symfony/config/Definition/ScalarNode.php',
        'Symfony\\Component\\Config\\Definition\\VariableNode' => __DIR__ . '/..' . '/symfony/config/Definition/VariableNode.php',
        'Symfony\\Component\\Config\\Exception\\FileLoaderImportCircularReferenceException' => __DIR__ . '/..' . '/symfony/config/Exception/FileLoaderImportCircularReferenceException.php',
        'Symfony\\Component\\Config\\Exception\\FileLoaderLoadException' => __DIR__ . '/..' . '/symfony/config/Exception/FileLoaderLoadException.php',
        'Symfony\\Component\\Config\\Exception\\FileLocatorFileNotFoundException' => __DIR__ . '/..' . '/symfony/config/Exception/FileLocatorFileNotFoundException.php',
        'Symfony\\Component\\Config\\Exception\\LoaderLoadException' => __DIR__ . '/..' . '/symfony/config/Exception/LoaderLoadException.php',
        'Symfony\\Component\\Config\\FileLocator' => __DIR__ . '/..' . '/symfony/config/FileLocator.php',
        'Symfony\\Component\\Config\\FileLocatorInterface' => __DIR__ . '/..' . '/symfony/config/FileLocatorInterface.php',
        'Symfony\\Component\\Config\\Loader\\DelegatingLoader' => __DIR__ . '/..' . '/symfony/config/Loader/DelegatingLoader.php',
        'Symfony\\Component\\Config\\Loader\\FileLoader' => __DIR__ . '/..' . '/symfony/config/Loader/FileLoader.php',
        'Symfony\\Component\\Config\\Loader\\GlobFileLoader' => __DIR__ . '/..' . '/symfony/config/Loader/GlobFileLoader.php',
        'Symfony\\Component\\Config\\Loader\\Loader' => __DIR__ . '/..' . '/symfony/config/Loader/Loader.php',
        'Symfony\\Component\\Config\\Loader\\LoaderInterface' => __DIR__ . '/..' . '/symfony/config/Loader/LoaderInterface.php',
        'Symfony\\Component\\Config\\Loader\\LoaderResolver' => __DIR__ . '/..' . '/symfony/config/Loader/LoaderResolver.php',
        'Symfony\\Component\\Config\\Loader\\LoaderResolverInterface' => __DIR__ . '/..' . '/symfony/config/Loader/LoaderResolverInterface.php',
        'Symfony\\Component\\Config\\ResourceCheckerConfigCache' => __DIR__ . '/..' . '/symfony/config/ResourceCheckerConfigCache.php',
        'Symfony\\Component\\Config\\ResourceCheckerConfigCacheFactory' => __DIR__ . '/..' . '/symfony/config/ResourceCheckerConfigCacheFactory.php',
        'Symfony\\Component\\Config\\ResourceCheckerInterface' => __DIR__ . '/..' . '/symfony/config/ResourceCheckerInterface.php',
        'Symfony\\Component\\Config\\Resource\\ClassExistenceResource' => __DIR__ . '/..' . '/symfony/config/Resource/ClassExistenceResource.php',
        'Symfony\\Component\\Config\\Resource\\ComposerResource' => __DIR__ . '/..' . '/symfony/config/Resource/ComposerResource.php',
        'Symfony\\Component\\Config\\Resource\\DirectoryResource' => __DIR__ . '/..' . '/symfony/config/Resource/DirectoryResource.php',
        'Symfony\\Component\\Config\\Resource\\FileExistenceResource' => __DIR__ . '/..' . '/symfony/config/Resource/FileExistenceResource.php',
        'Symfony\\Component\\Config\\Resource\\FileResource' => __DIR__ . '/..' . '/symfony/config/Resource/FileResource.php',
        'Symfony\\Component\\Config\\Resource\\GlobResource' => __DIR__ . '/..' . '/symfony/config/Resource/GlobResource.php',
        'Symfony\\Component\\Config\\Resource\\ReflectionClassResource' => __DIR__ . '/..' . '/symfony/config/Resource/ReflectionClassResource.php',
        'Symfony\\Component\\Config\\Resource\\ResourceInterface' => __DIR__ . '/..' . '/symfony/config/Resource/ResourceInterface.php',
        'Symfony\\Component\\Config\\Resource\\SelfCheckingResourceChecker' => __DIR__ . '/..' . '/symfony/config/Resource/SelfCheckingResourceChecker.php',
        'Symfony\\Component\\Config\\Resource\\SelfCheckingResourceInterface' => __DIR__ . '/..' . '/symfony/config/Resource/SelfCheckingResourceInterface.php',
        'Symfony\\Component\\Config\\Util\\Exception\\InvalidXmlException' => __DIR__ . '/..' . '/symfony/config/Util/Exception/InvalidXmlException.php',
        'Symfony\\Component\\Config\\Util\\Exception\\XmlParsingException' => __DIR__ . '/..' . '/symfony/config/Util/Exception/XmlParsingException.php',
        'Symfony\\Component\\Config\\Util\\XmlUtils' => __DIR__ . '/..' . '/symfony/config/Util/XmlUtils.php',
        'Symfony\\Component\\Console\\Application' => __DIR__ . '/..' . '/symfony/console/Application.php',
        'Symfony\\Component\\Console\\CommandLoader\\CommandLoaderInterface' => __DIR__ . '/..' . '/symfony/console/CommandLoader/CommandLoaderInterface.php',
        'Symfony\\Component\\Console\\CommandLoader\\ContainerCommandLoader' => __DIR__ . '/..' . '/symfony/console/CommandLoader/ContainerCommandLoader.php',
        'Symfony\\Component\\Console\\CommandLoader\\FactoryCommandLoader' => __DIR__ . '/..' . '/symfony/console/CommandLoader/FactoryCommandLoader.php',
        'Symfony\\Component\\Console\\Command\\Command' => __DIR__ . '/..' . '/symfony/console/Command/Command.php',
        'Symfony\\Component\\Console\\Command\\HelpCommand' => __DIR__ . '/..' . '/symfony/console/Command/HelpCommand.php',
        'Symfony\\Component\\Console\\Command\\ListCommand' => __DIR__ . '/..' . '/symfony/console/Command/ListCommand.php',
        'Symfony\\Component\\Console\\Command\\LockableTrait' => __DIR__ . '/..' . '/symfony/console/Command/LockableTrait.php',
        'Symfony\\Component\\Console\\ConsoleEvents' => __DIR__ . '/..' . '/symfony/console/ConsoleEvents.php',
        'Symfony\\Component\\Console\\DependencyInjection\\AddConsoleCommandPass' => __DIR__ . '/..' . '/symfony/console/DependencyInjection/AddConsoleCommandPass.php',
        'Symfony\\Component\\Console\\Descriptor\\ApplicationDescription' => __DIR__ . '/..' . '/symfony/console/Descriptor/ApplicationDescription.php',
        'Symfony\\Component\\Console\\Descriptor\\Descriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/Descriptor.php',
        'Symfony\\Component\\Console\\Descriptor\\DescriptorInterface' => __DIR__ . '/..' . '/symfony/console/Descriptor/DescriptorInterface.php',
        'Symfony\\Component\\Console\\Descriptor\\JsonDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/JsonDescriptor.php',
        'Symfony\\Component\\Console\\Descriptor\\MarkdownDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/MarkdownDescriptor.php',
        'Symfony\\Component\\Console\\Descriptor\\TextDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/TextDescriptor.php',
        'Symfony\\Component\\Console\\Descriptor\\XmlDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/XmlDescriptor.php',
        'Symfony\\Component\\Console\\EventListener\\ErrorListener' => __DIR__ . '/..' . '/symfony/console/EventListener/ErrorListener.php',
        'Symfony\\Component\\Console\\Event\\ConsoleCommandEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleCommandEvent.php',
        'Symfony\\Component\\Console\\Event\\ConsoleErrorEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleErrorEvent.php',
        'Symfony\\Component\\Console\\Event\\ConsoleEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleEvent.php',
        'Symfony\\Component\\Console\\Event\\ConsoleExceptionEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleExceptionEvent.php',
        'Symfony\\Component\\Console\\Event\\ConsoleTerminateEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleTerminateEvent.php',
        'Symfony\\Component\\Console\\Exception\\CommandNotFoundException' => __DIR__ . '/..' . '/symfony/console/Exception/CommandNotFoundException.php',
        'Symfony\\Component\\Console\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/console/Exception/ExceptionInterface.php',
        'Symfony\\Component\\Console\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/console/Exception/InvalidArgumentException.php',
        'Symfony\\Component\\Console\\Exception\\InvalidOptionException' => __DIR__ . '/..' . '/symfony/console/Exception/InvalidOptionException.php',
        'Symfony\\Component\\Console\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/console/Exception/LogicException.php',
        'Symfony\\Component\\Console\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/console/Exception/RuntimeException.php',
        'Symfony\\Component\\Console\\Formatter\\OutputFormatter' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatter.php',
        'Symfony\\Component\\Console\\Formatter\\OutputFormatterInterface' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterInterface.php',
        'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyle' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyle.php',
        'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleInterface' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyleInterface.php',
        'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleStack' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyleStack.php',
        'Symfony\\Component\\Console\\Helper\\DebugFormatterHelper' => __DIR__ . '/..' . '/symfony/console/Helper/DebugFormatterHelper.php',
        'Symfony\\Component\\Console\\Helper\\DescriptorHelper' => __DIR__ . '/..' . '/symfony/console/Helper/DescriptorHelper.php',
        'Symfony\\Component\\Console\\Helper\\FormatterHelper' => __DIR__ . '/..' . '/symfony/console/Helper/FormatterHelper.php',
        'Symfony\\Component\\Console\\Helper\\Helper' => __DIR__ . '/..' . '/symfony/console/Helper/Helper.php',
        'Symfony\\Component\\Console\\Helper\\HelperInterface' => __DIR__ . '/..' . '/symfony/console/Helper/HelperInterface.php',
        'Symfony\\Component\\Console\\Helper\\HelperSet' => __DIR__ . '/..' . '/symfony/console/Helper/HelperSet.php',
        'Symfony\\Component\\Console\\Helper\\InputAwareHelper' => __DIR__ . '/..' . '/symfony/console/Helper/InputAwareHelper.php',
        'Symfony\\Component\\Console\\Helper\\ProcessHelper' => __DIR__ . '/..' . '/symfony/console/Helper/ProcessHelper.php',
        'Symfony\\Component\\Console\\Helper\\ProgressBar' => __DIR__ . '/..' . '/symfony/console/Helper/ProgressBar.php',
        'Symfony\\Component\\Console\\Helper\\ProgressIndicator' => __DIR__ . '/..' . '/symfony/console/Helper/ProgressIndicator.php',
        'Symfony\\Component\\Console\\Helper\\QuestionHelper' => __DIR__ . '/..' . '/symfony/console/Helper/QuestionHelper.php',
        'Symfony\\Component\\Console\\Helper\\SymfonyQuestionHelper' => __DIR__ . '/..' . '/symfony/console/Helper/SymfonyQuestionHelper.php',
        'Symfony\\Component\\Console\\Helper\\Table' => __DIR__ . '/..' . '/symfony/console/Helper/Table.php',
        'Symfony\\Component\\Console\\Helper\\TableCell' => __DIR__ . '/..' . '/symfony/console/Helper/TableCell.php',
        'Symfony\\Component\\Console\\Helper\\TableSeparator' => __DIR__ . '/..' . '/symfony/console/Helper/TableSeparator.php',
        'Symfony\\Component\\Console\\Helper\\TableStyle' => __DIR__ . '/..' . '/symfony/console/Helper/TableStyle.php',
        'Symfony\\Component\\Console\\Input\\ArgvInput' => __DIR__ . '/..' . '/symfony/console/Input/ArgvInput.php',
        'Symfony\\Component\\Console\\Input\\ArrayInput' => __DIR__ . '/..' . '/symfony/console/Input/ArrayInput.php',
        'Symfony\\Component\\Console\\Input\\Input' => __DIR__ . '/..' . '/symfony/console/Input/Input.php',
        'Symfony\\Component\\Console\\Input\\InputArgument' => __DIR__ . '/..' . '/symfony/console/Input/InputArgument.php',
        'Symfony\\Component\\Console\\Input\\InputAwareInterface' => __DIR__ . '/..' . '/symfony/console/Input/InputAwareInterface.php',
        'Symfony\\Component\\Console\\Input\\InputDefinition' => __DIR__ . '/..' . '/symfony/console/Input/InputDefinition.php',
        'Symfony\\Component\\Console\\Input\\InputInterface' => __DIR__ . '/..' . '/symfony/console/Input/InputInterface.php',
        'Symfony\\Component\\Console\\Input\\InputOption' => __DIR__ . '/..' . '/symfony/console/Input/InputOption.php',
        'Symfony\\Component\\Console\\Input\\StreamableInputInterface' => __DIR__ . '/..' . '/symfony/console/Input/StreamableInputInterface.php',
        'Symfony\\Component\\Console\\Input\\StringInput' => __DIR__ . '/..' . '/symfony/console/Input/StringInput.php',
        'Symfony\\Component\\Console\\Logger\\ConsoleLogger' => __DIR__ . '/..' . '/symfony/console/Logger/ConsoleLogger.php',
        'Symfony\\Component\\Console\\Output\\BufferedOutput' => __DIR__ . '/..' . '/symfony/console/Output/BufferedOutput.php',
        'Symfony\\Component\\Console\\Output\\ConsoleOutput' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleOutput.php',
        'Symfony\\Component\\Console\\Output\\ConsoleOutputInterface' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleOutputInterface.php',
        'Symfony\\Component\\Console\\Output\\NullOutput' => __DIR__ . '/..' . '/symfony/console/Output/NullOutput.php',
        'Symfony\\Component\\Console\\Output\\Output' => __DIR__ . '/..' . '/symfony/console/Output/Output.php',
        'Symfony\\Component\\Console\\Output\\OutputInterface' => __DIR__ . '/..' . '/symfony/console/Output/OutputInterface.php',
        'Symfony\\Component\\Console\\Output\\StreamOutput' => __DIR__ . '/..' . '/symfony/console/Output/StreamOutput.php',
        'Symfony\\Component\\Console\\Question\\ChoiceQuestion' => __DIR__ . '/..' . '/symfony/console/Question/ChoiceQuestion.php',
        'Symfony\\Component\\Console\\Question\\ConfirmationQuestion' => __DIR__ . '/..' . '/symfony/console/Question/ConfirmationQuestion.php',
        'Symfony\\Component\\Console\\Question\\Question' => __DIR__ . '/..' . '/symfony/console/Question/Question.php',
        'Symfony\\Component\\Console\\Style\\OutputStyle' => __DIR__ . '/..' . '/symfony/console/Style/OutputStyle.php',
        'Symfony\\Component\\Console\\Style\\StyleInterface' => __DIR__ . '/..' . '/symfony/console/Style/StyleInterface.php',
        'Symfony\\Component\\Console\\Style\\SymfonyStyle' => __DIR__ . '/..' . '/symfony/console/Style/SymfonyStyle.php',
        'Symfony\\Component\\Console\\Terminal' => __DIR__ . '/..' . '/symfony/console/Terminal.php',
        'Symfony\\Component\\Console\\Tester\\ApplicationTester' => __DIR__ . '/..' . '/symfony/console/Tester/ApplicationTester.php',
        'Symfony\\Component\\Console\\Tester\\CommandTester' => __DIR__ . '/..' . '/symfony/console/Tester/CommandTester.php',
        'Symfony\\Component\\Debug\\BufferingLogger' => __DIR__ . '/..' . '/symfony/debug/BufferingLogger.php',
        'Symfony\\Component\\Debug\\Debug' => __DIR__ . '/..' . '/symfony/debug/Debug.php',
        'Symfony\\Component\\Debug\\DebugClassLoader' => __DIR__ . '/..' . '/symfony/debug/DebugClassLoader.php',
        'Symfony\\Component\\Debug\\ErrorHandler' => __DIR__ . '/..' . '/symfony/debug/ErrorHandler.php',
        'Symfony\\Component\\Debug\\ExceptionHandler' => __DIR__ . '/..' . '/symfony/debug/ExceptionHandler.php',
        'Symfony\\Component\\Debug\\Exception\\ClassNotFoundException' => __DIR__ . '/..' . '/symfony/debug/Exception/ClassNotFoundException.php',
        'Symfony\\Component\\Debug\\Exception\\FatalErrorException' => __DIR__ . '/..' . '/symfony/debug/Exception/FatalErrorException.php',
        'Symfony\\Component\\Debug\\Exception\\FatalThrowableError' => __DIR__ . '/..' . '/symfony/debug/Exception/FatalThrowableError.php',
        'Symfony\\Component\\Debug\\Exception\\FlattenException' => __DIR__ . '/..' . '/symfony/debug/Exception/FlattenException.php',
        'Symfony\\Component\\Debug\\Exception\\OutOfMemoryException' => __DIR__ . '/..' . '/symfony/debug/Exception/OutOfMemoryException.php',
        'Symfony\\Component\\Debug\\Exception\\SilencedErrorContext' => __DIR__ . '/..' . '/symfony/debug/Exception/SilencedErrorContext.php',
        'Symfony\\Component\\Debug\\Exception\\UndefinedFunctionException' => __DIR__ . '/..' . '/symfony/debug/Exception/UndefinedFunctionException.php',
        'Symfony\\Component\\Debug\\Exception\\UndefinedMethodException' => __DIR__ . '/..' . '/symfony/debug/Exception/UndefinedMethodException.php',
        'Symfony\\Component\\Debug\\FatalErrorHandler\\ClassNotFoundFatalErrorHandler' => __DIR__ . '/..' . '/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php',
        'Symfony\\Component\\Debug\\FatalErrorHandler\\FatalErrorHandlerInterface' => __DIR__ . '/..' . '/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php',
        'Symfony\\Component\\Debug\\FatalErrorHandler\\UndefinedFunctionFatalErrorHandler' => __DIR__ . '/..' . '/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php',
        'Symfony\\Component\\Debug\\FatalErrorHandler\\UndefinedMethodFatalErrorHandler' => __DIR__ . '/..' . '/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php',
        'Symfony\\Component\\Dotenv\\Dotenv' => __DIR__ . '/..' . '/symfony/dotenv/Dotenv.php',
        'Symfony\\Component\\Dotenv\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/dotenv/Exception/ExceptionInterface.php',
        'Symfony\\Component\\Dotenv\\Exception\\FormatException' => __DIR__ . '/..' . '/symfony/dotenv/Exception/FormatException.php',
        'Symfony\\Component\\Dotenv\\Exception\\FormatExceptionContext' => __DIR__ . '/..' . '/symfony/dotenv/Exception/FormatExceptionContext.php',
        'Symfony\\Component\\Dotenv\\Exception\\PathException' => __DIR__ . '/..' . '/symfony/dotenv/Exception/PathException.php',
        'Symfony\\Component\\Filesystem\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/filesystem/Exception/ExceptionInterface.php',
        'Symfony\\Component\\Filesystem\\Exception\\FileNotFoundException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/FileNotFoundException.php',
        'Symfony\\Component\\Filesystem\\Exception\\IOException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/IOException.php',
        'Symfony\\Component\\Filesystem\\Exception\\IOExceptionInterface' => __DIR__ . '/..' . '/symfony/filesystem/Exception/IOExceptionInterface.php',
        'Symfony\\Component\\Filesystem\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/InvalidArgumentException.php',
        'Symfony\\Component\\Filesystem\\Filesystem' => __DIR__ . '/..' . '/symfony/filesystem/Filesystem.php',
        'Symfony\\Component\\Yaml\\Command\\LintCommand' => __DIR__ . '/..' . '/symfony/yaml/Command/LintCommand.php',
        'Symfony\\Component\\Yaml\\Dumper' => __DIR__ . '/..' . '/symfony/yaml/Dumper.php',
        'Symfony\\Component\\Yaml\\Escaper' => __DIR__ . '/..' . '/symfony/yaml/Escaper.php',
        'Symfony\\Component\\Yaml\\Exception\\DumpException' => __DIR__ . '/..' . '/symfony/yaml/Exception/DumpException.php',
        'Symfony\\Component\\Yaml\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/yaml/Exception/ExceptionInterface.php',
        'Symfony\\Component\\Yaml\\Exception\\ParseException' => __DIR__ . '/..' . '/symfony/yaml/Exception/ParseException.php',
        'Symfony\\Component\\Yaml\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/yaml/Exception/RuntimeException.php',
        'Symfony\\Component\\Yaml\\Inline' => __DIR__ . '/..' . '/symfony/yaml/Inline.php',
        'Symfony\\Component\\Yaml\\Parser' => __DIR__ . '/..' . '/symfony/yaml/Parser.php',
        'Symfony\\Component\\Yaml\\Tag\\TaggedValue' => __DIR__ . '/..' . '/symfony/yaml/Tag/TaggedValue.php',
        'Symfony\\Component\\Yaml\\Unescaper' => __DIR__ . '/..' . '/symfony/yaml/Unescaper.php',
        'Symfony\\Component\\Yaml\\Yaml' => __DIR__ . '/..' . '/symfony/yaml/Yaml.php',
        'Symfony\\Polyfill\\Ctype\\Ctype' => __DIR__ . '/..' . '/symfony/polyfill-ctype/Ctype.php',
        'Symfony\\Polyfill\\Mbstring\\Mbstring' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/Mbstring.php',
        'Text_Template' => __DIR__ . '/..' . '/phpunit/php-text-template/src/Template.php',
        'Webmozart\\Assert\\Assert' => __DIR__ . '/..' . '/webmozart/assert/src/Assert.php',
        'com\\linways\\base\\cache\\LinCache' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/cache/LinCache.php',
        'com\\linways\\base\\connection\\IMySqlConnector' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/connection/IMySqlConnector.php',
        'com\\linways\\base\\connection\\MySqlConnector' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/connection/MySqlConnector.php',
        'com\\linways\\base\\connection\\MySqlQuery' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/connection/MySqlQuery.php',
        'com\\linways\\base\\constant\\UserType' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/constant/UserType.php',
        'com\\linways\\base\\dto\\AjaxPaginator' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/dto/AjaxPaginator.php',
        'com\\linways\\base\\dto\\BaseDTO' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/dto/BaseDTO.php',
        'com\\linways\\base\\dto\\Email' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/dto/Email.php',
        'com\\linways\\base\\dto\\EmailAttachment' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/dto/EmailAttachment.php',
        'com\\linways\\base\\dto\\EmailCC' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/dto/EmailTo.php',
        'com\\linways\\base\\dto\\EmailFrom' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/dto/EmailCC.php',
        'com\\linways\\base\\dto\\EmailTo' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/dto/EmailFrom.php',
        'com\\linways\\base\\dto\\MySqlResult' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/dto/MySqlResult.php',
        'com\\linways\\base\\dto\\PageSupport' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/dto/PageSupport.php',
        'com\\linways\\base\\dto\\Toggle' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/dto/Toggle.php',
        'com\\linways\\base\\dto\\sms\\SMS' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/dto/sms/SMS.php',
        'com\\linways\\base\\dto\\sms\\SMSProvider' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/dto/sms/SMSProvider.php',
        'com\\linways\\base\\dto\\sms\\SMSProviderAttribute' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/dto/sms/SMSProviderAttribute.php',
        'com\\linways\\base\\dto\\sms\\SMSProviderAttributeValue' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/dto/sms/SMSProviderAttributeValue.php',
        'com\\linways\\base\\dto\\sms\\SMSProviderModule' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/dto/sms/SMSProviderModule.php',
        'com\\linways\\base\\dto\\sms\\SMSResponse' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/dto/sms/SMSResponse.php',
        'com\\linways\\base\\email\\PHPMailer' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/email/PHPMailer.php',
        'com\\linways\\base\\email\\SMTP' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/email/SMTP.php',
        'com\\linways\\base\\exception\\CoreException' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/exception/CoreException.php',
        'com\\linways\\base\\exception\\EmailException' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/exception/EmailException.php',
        'com\\linways\\base\\exception\\MapperException' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/exception/MapperException.php',
        'com\\linways\\base\\exception\\SMSException' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/exception/SMSException.php',
        'com\\linways\\base\\exception\\SQLException' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/exception/SQLException.php',
        'com\\linways\\base\\exception\\TaskQueueException' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/exception/TaskQueueException.php',
        'com\\linways\\base\\helper\\ResultHandler' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/helper/ResultHandler.php',
        'com\\linways\\base\\hooks\\LinwaysHooks' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/hooks/LinwaysHooks.php',
        'com\\linways\\base\\logging\\BaseEntity' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/logging/BaseEntity.php',
        'com\\linways\\base\\logging\\ElasticHandler' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/logging/ElasticHandler.php',
        'com\\linways\\base\\logging\\Logger' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/logging/Logger.php',
        'com\\linways\\base\\mapper\\IMapper' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/mapper/IMapper.php',
        'com\\linways\\base\\mapper\\Result' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/mapper/Result.php',
        'com\\linways\\base\\mapper\\ResultMap' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/mapper/ResultMap.php',
        'com\\linways\\base\\mcrypt\\CryptAES' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/mcrypt/CryptAES.php',
        'com\\linways\\base\\messages\\CommonMessages' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/messages/CommonMessages.php',
        'com\\linways\\base\\queue\\Queue' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/queue/Queue.php',
        'com\\linways\\base\\request\\BaseRequest' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/request/BaseRequest.php',
        'com\\linways\\base\\request\\ScheduleRequest' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/request/ScheduleRequest.php',
        'com\\linways\\base\\response\\BaseResponse' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/response/BaseResponse.php',
        'com\\linways\\base\\service\\EmailService' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/service/EmailService.php',
        'com\\linways\\base\\soap\\XMLSchema' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/soap/XMLSchema.php',
        'com\\linways\\base\\soap\\nusoap_base' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/soap/nusoap_base.php',
        'com\\linways\\base\\soap\\nusoap_client' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/soap/nusoap_client.php',
        'com\\linways\\base\\soap\\nusoap_fault' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/soap/nusoap_fault.php',
        'com\\linways\\base\\soap\\nusoap_parser' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/soap/nusoap_parser.php',
        'com\\linways\\base\\soap\\nusoap_server' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/soap/nusoap_server.php',
        'com\\linways\\base\\soap\\nusoap_xmlschema' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/soap/nusoap_xmlschema.php',
        'com\\linways\\base\\soap\\soap_fault' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/soap/soap_fault.php',
        'com\\linways\\base\\soap\\soap_parser' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/soap/soap_parser.php',
        'com\\linways\\base\\soap\\soap_server' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/soap/soap_server.php',
        'com\\linways\\base\\soap\\soap_transport_http' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/soap/soap_transport_http.php',
        'com\\linways\\base\\soap\\soapclient' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/soap/soapclient.php',
        'com\\linways\\base\\soap\\soapval' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/soap/soapval.php',
        'com\\linways\\base\\soap\\wsdl' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/soap/wsdl.php',
        'com\\linways\\base\\test\\BaseTestCase' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/testcase/BaseTestCase.php',
        'com\\linways\\base\\toggles\\AbstractToggleMigration' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/toggles/AbstractToggleMigration.php',
        'com\\linways\\base\\toggles\\FeatureToggle' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/toggles/FeatureToggle.php',
        'com\\linways\\base\\toggles\\FeatureToggleInterface' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/toggles/FeatureToggleInterface.php',
        'com\\linways\\base\\util\\CalendarUtil' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/util/CalendarUtil.php',
        'com\\linways\\base\\util\\CommonUtil' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/util/CommonUtil.php',
        'com\\linways\\base\\util\\MakeSingletonTrait' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/util/MakeSingletonTrait.php',
        'com\\linways\\base\\util\\MapperUtil' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/util/MapperUtil.php',
        'com\\linways\\base\\util\\MySqlUtil' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/util/MySqlUtil.php',
        'com\\linways\\base\\util\\ObjectUtil' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/util/ObjectUtil.php',
        'com\\linways\\base\\util\\PhoneUtil' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/util/PhoneUtil.php',
        'com\\linways\\base\\util\\RequestUtil' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/util/RequestUtil.php',
        'com\\linways\\base\\util\\SecurityUtils' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/util/SecurityUtils.php',
        'com\\linways\\base\\util\\StringUtil' => __DIR__ . '/..' . '/linways/linways-base/src/com/linways/base/util/StringUtil.php',
        'com\\linways\\nucleus\\core\\constants\\PermissionConstants' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/constants/PermissionConstants.php',
        'com\\linways\\nucleus\\core\\constants\\Scope' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/constants/Scope.php',
        'com\\linways\\nucleus\\core\\constants\\SingleSignonConstant' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/constants/SingleSignonConstant.php',
        'com\\linways\\nucleus\\core\\dto\\Addon' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/Addon.php',
        'com\\linways\\nucleus\\core\\dto\\AddonAttribute' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/AddonAttribute.php',
        'com\\linways\\nucleus\\core\\dto\\AddonAttributeValue' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/AddonAttributeValue.php',
        'com\\linways\\nucleus\\core\\dto\\AddonDataStore' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/AddonDataStore.php',
        'com\\linways\\nucleus\\core\\dto\\AddonEnabled' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/AddonEnabled.php',
        'com\\linways\\nucleus\\core\\dto\\ApiOrganizationDetails' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/ApiOrganizationDetails.php',
        'com\\linways\\nucleus\\core\\dto\\Client' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/Client.php',
        'com\\linways\\nucleus\\core\\dto\\College' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/College.php',
        'com\\linways\\nucleus\\core\\dto\\Expense' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/Expense.php',
        'com\\linways\\nucleus\\core\\dto\\ExpenseCategory' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/ExpenseCategory.php',
        'com\\linways\\nucleus\\core\\dto\\ExpenseRecurring' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/ExpenseRecurring.php',
        'com\\linways\\nucleus\\core\\dto\\ExpenseRecurringAmount' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/ExpenseRecurringAmount.php',
        'com\\linways\\nucleus\\core\\dto\\ProductVariant' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/ProductVariant.php',
        'com\\linways\\nucleus\\core\\dto\\ScheduledTask' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/ScheduledTask.php',
        'com\\linways\\nucleus\\core\\dto\\SingleSignonLog' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/SingleSignonLog.php',
        'com\\linways\\nucleus\\core\\dto\\user\\User' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/dto/user/User.php',
        'com\\linways\\nucleus\\core\\exception\\NucleusException' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/exception/NucleusException.php',
        'com\\linways\\nucleus\\core\\exception\\SchedulerException' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/exception/SchedulerException.php',
        'com\\linways\\nucleus\\core\\exception\\TaskQueueWorkerException' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/exception/TaskQueueWorkerException.php',
        'com\\linways\\nucleus\\core\\mapper\\AddonDataStoreMapper' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/mapper/AddonDataStoreMapper.php',
        'com\\linways\\nucleus\\core\\mapper\\AddonServiceMapper' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/mapper/AddonServiceMapper.php',
        'com\\linways\\nucleus\\core\\mapper\\ClientServiceMapper' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/mapper/ClientServiceMapper.php',
        'com\\linways\\nucleus\\core\\mapper\\CollegeServiceMapper' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/mapper/CollegeServiceMapper.php',
        'com\\linways\\nucleus\\core\\mapper\\ExpenseServiceMapper' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/mapper/ExpenseServiceMapper.php',
        'com\\linways\\nucleus\\core\\mapper\\ProductVariantServiceMapper' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/mapper/ProductVariantServiceMapper.php',
        'com\\linways\\nucleus\\core\\mapper\\RecurringExpenseServiceMapper' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/mapper/RecurringExpenseServiceMapper.php',
        'com\\linways\\nucleus\\core\\mapper\\SchedulerServiceMapper' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/mapper/ScheduleServiceMapper.php',
        'com\\linways\\nucleus\\core\\service\\APIAccessTokenService' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/service/APIAccessTokenService.php',
        'com\\linways\\nucleus\\core\\service\\AddonAttributeService' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/service/AddonAttributeService.php',
        'com\\linways\\nucleus\\core\\service\\AddonDataStoreSevice' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/service/AddonDataStoreSevice.php',
        'com\\linways\\nucleus\\core\\service\\AddonService' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/service/AddonService.php',
        'com\\linways\\nucleus\\core\\service\\BaseService' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/service/BaseService.php',
        'com\\linways\\nucleus\\core\\service\\ClientService' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/service/ClientService.php',
        'com\\linways\\nucleus\\core\\service\\CollegeService' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/service/CollegeService.php',
        'com\\linways\\nucleus\\core\\service\\ConfigurationService' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/service/ConfigurationService.php',
        'com\\linways\\nucleus\\core\\service\\ExpenseCategoryService' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/service/ExpenseCategoryService.php',
        'com\\linways\\nucleus\\core\\service\\ExpenseService' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/service/ExpenseService.php',
        'com\\linways\\nucleus\\core\\service\\PermissionService' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/service/PermissionService.php',
        'com\\linways\\nucleus\\core\\service\\PermissionToggleService' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/service/PermissionToggleService.php',
        'com\\linways\\nucleus\\core\\service\\ProductVariantService' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/service/ProductVariantService.php',
        'com\\linways\\nucleus\\core\\service\\RecurringExpenseService' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/service/RecurringExpenseService.php',
        'com\\linways\\nucleus\\core\\service\\ReportService' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/service/ReportService.php',
        'com\\linways\\nucleus\\core\\service\\SchedulerService' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/service/SchedulerService.php',
        'com\\linways\\nucleus\\core\\service\\SingleSignonService' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/service/SingleSignonService.php',
        'com\\linways\\nucleus\\core\\service\\user\\UserService' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/service/user/UserService.php',
        'com\\linways\\nucleus\\core\\utils\\DateUtils' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/utils/DateUtils.php',
        'com\\linways\\nucleus\\core\\utils\\JWTUtils' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/utils/JWTUtils.php',
        'com\\linways\\nucleus\\core\\utils\\StringUtils' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/utils/StringUtils.php',
        'com\\linways\\nucleus\\core\\utils\\user\\UserServiceUtils' => __DIR__ . '/..' . '/linways/nucleus-core/src/com/linways/nucleus/core/utils/user/UserServiceUtils.php',
        'linways\\cli\\command\\db\\CreateMigrationCommand' => __DIR__ . '/../..' . '/src/commands/db/CreateMigrationCommand.php',
        'linways\\cli\\command\\db\\MigrateCommand' => __DIR__ . '/../..' . '/src/commands/db/MigrateCommand.php',
        'linways\\cli\\command\\db\\MigrateFakeCommand' => __DIR__ . '/../..' . '/src/commands/db/MigrateFakeCommand.php',
        'linways\\cli\\command\\git\\UpdateStyleGuideCommand' => __DIR__ . '/../..' . '/src/commands/git/UpdateStyleGuideCommand.php',
        'linways\\cli\\commands\\toggles\\CreatePermissionToggleCommand' => __DIR__ . '/../..' . '/src/commands/toggles/CreatePermissionToggleCommand.php',
        'linways\\cli\\commands\\toggles\\MigrateTogglesCommand' => __DIR__ . '/../..' . '/src/commands/toggles/MigrateTogglesCommand.php',
        'linways\\cli\\service\\ToggleManagerService' => __DIR__ . '/../..' . '/src/service/toggles/ToggleManagerService.php',
        'linways\\cli\\service\\db\\MigrateFakeService' => __DIR__ . '/../..' . '/src/service/db/MigrateFakeService.php',
        'linways\\cli\\service\\db\\MigrateService' => __DIR__ . '/../..' . '/src/service/db/MigrateService.php',
        'linways\\cli\\utils\\FileNameUtils' => __DIR__ . '/../..' . '/src/utils/FileNameUtils.php',
        'linways\\cli\\utls\\MigrationUtils' => __DIR__ . '/../..' . '/src/utils/MigrationUtils.php',
        'phpDocumentor\\Reflection\\DocBlock' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock.php',
        'phpDocumentor\\Reflection\\DocBlockFactory' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlockFactory.php',
        'phpDocumentor\\Reflection\\DocBlockFactoryInterface' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlockFactoryInterface.php',
        'phpDocumentor\\Reflection\\DocBlock\\Description' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Description.php',
        'phpDocumentor\\Reflection\\DocBlock\\DescriptionFactory' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.php',
        'phpDocumentor\\Reflection\\DocBlock\\ExampleFinder' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.php',
        'phpDocumentor\\Reflection\\DocBlock\\Serializer' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.php',
        'phpDocumentor\\Reflection\\DocBlock\\StandardTagFactory' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tag' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tag.php',
        'phpDocumentor\\Reflection\\DocBlock\\TagFactory' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/TagFactory.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Author' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\BaseTag' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Covers' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Deprecated' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Example' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Factory\\StaticMethod' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/StaticMethod.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Factory\\Strategy' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/Strategy.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Formatter' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Formatter\\AlignFormatter' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/AlignFormatter.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Formatter\\PassthroughFormatter' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Generic' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Link' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Method' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Param' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Property' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\PropertyRead' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\PropertyWrite' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Reference\\Fqsen' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Fqsen.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Reference\\Reference' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Reference.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Reference\\Url' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Url.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Return_' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\See' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Since' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Source' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Throws' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Uses' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Var_' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.php',
        'phpDocumentor\\Reflection\\DocBlock\\Tags\\Version' => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.php',
        'phpDocumentor\\Reflection\\Element' => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src/Element.php',
        'phpDocumentor\\Reflection\\File' => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src/File.php',
        'phpDocumentor\\Reflection\\Fqsen' => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src/Fqsen.php',
        'phpDocumentor\\Reflection\\FqsenResolver' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/FqsenResolver.php',
        'phpDocumentor\\Reflection\\Location' => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src/Location.php',
        'phpDocumentor\\Reflection\\Project' => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src/Project.php',
        'phpDocumentor\\Reflection\\ProjectFactory' => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src/ProjectFactory.php',
        'phpDocumentor\\Reflection\\Type' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Type.php',
        'phpDocumentor\\Reflection\\TypeResolver' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/TypeResolver.php',
        'phpDocumentor\\Reflection\\Types\\Array_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Array_.php',
        'phpDocumentor\\Reflection\\Types\\Boolean' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Boolean.php',
        'phpDocumentor\\Reflection\\Types\\Callable_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Callable_.php',
        'phpDocumentor\\Reflection\\Types\\Compound' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Compound.php',
        'phpDocumentor\\Reflection\\Types\\Context' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Context.php',
        'phpDocumentor\\Reflection\\Types\\ContextFactory' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/ContextFactory.php',
        'phpDocumentor\\Reflection\\Types\\Float_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Float_.php',
        'phpDocumentor\\Reflection\\Types\\Integer' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Integer.php',
        'phpDocumentor\\Reflection\\Types\\Iterable_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Iterable_.php',
        'phpDocumentor\\Reflection\\Types\\Mixed_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Mixed_.php',
        'phpDocumentor\\Reflection\\Types\\Null_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Null_.php',
        'phpDocumentor\\Reflection\\Types\\Nullable' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Nullable.php',
        'phpDocumentor\\Reflection\\Types\\Object_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Object_.php',
        'phpDocumentor\\Reflection\\Types\\Parent_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Parent_.php',
        'phpDocumentor\\Reflection\\Types\\Resource_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Resource_.php',
        'phpDocumentor\\Reflection\\Types\\Scalar' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Scalar.php',
        'phpDocumentor\\Reflection\\Types\\Self_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Self_.php',
        'phpDocumentor\\Reflection\\Types\\Static_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Static_.php',
        'phpDocumentor\\Reflection\\Types\\String_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/String_.php',
        'phpDocumentor\\Reflection\\Types\\This' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/This.php',
        'phpDocumentor\\Reflection\\Types\\Void_' => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src/Types/Void_.php',
        'voku\\helper\\Hooks' => __DIR__ . '/..' . '/voku/php-hooks/src/voku/helper/Hooks.php',
    );

    public static function getInitializer(ClassLoader $loader)
    {
        return \Closure::bind(function () use ($loader) {
            $loader->prefixLengthsPsr4 = ComposerStaticInit555f246cb7db6869b6fb9f48d5e5f3d2::$prefixLengthsPsr4;
            $loader->prefixDirsPsr4 = ComposerStaticInit555f246cb7db6869b6fb9f48d5e5f3d2::$prefixDirsPsr4;
            $loader->prefixesPsr0 = ComposerStaticInit555f246cb7db6869b6fb9f48d5e5f3d2::$prefixesPsr0;
            $loader->classMap = ComposerStaticInit555f246cb7db6869b6fb9f48d5e5f3d2::$classMap;

        }, null, ClassLoader::class);
    }
}
<?php

// autoload_real.php @generated by Composer

class ComposerAutoloaderInit555f246cb7db6869b6fb9f48d5e5f3d2
{
    private static $loader;

    public static function loadClassLoader($class)
    {
        if ('Composer\Autoload\ClassLoader' === $class) {
            require __DIR__ . '/ClassLoader.php';
        }
    }

    public static function getLoader()
    {
        if (null !== self::$loader) {
            return self::$loader;
        }

        spl_autoload_register(array('ComposerAutoloaderInit555f246cb7db6869b6fb9f48d5e5f3d2', 'loadClassLoader'), true, true);
        self::$loader = $loader = new \Composer\Autoload\ClassLoader();
        spl_autoload_unregister(array('ComposerAutoloaderInit555f246cb7db6869b6fb9f48d5e5f3d2', 'loadClassLoader'));

        $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
        if ($useStaticLoader) {
            require_once __DIR__ . '/autoload_static.php';

            call_user_func(\Composer\Autoload\ComposerStaticInit555f246cb7db6869b6fb9f48d5e5f3d2::getInitializer($loader));
        } else {
            $map = require __DIR__ . '/autoload_namespaces.php';
            foreach ($map as $namespace => $path) {
                $loader->set($namespace, $path);
            }

            $map = require __DIR__ . '/autoload_psr4.php';
            foreach ($map as $namespace => $path) {
                $loader->setPsr4($namespace, $path);
            }

            $classMap = require __DIR__ . '/autoload_classmap.php';
            if ($classMap) {
                $loader->addClassMap($classMap);
            }
        }

        $loader->register(true);

        if ($useStaticLoader) {
            $includeFiles = Composer\Autoload\ComposerStaticInit555f246cb7db6869b6fb9f48d5e5f3d2::$files;
        } else {
            $includeFiles = require __DIR__ . '/autoload_files.php';
        }
        foreach ($includeFiles as $fileIdentifier => $file) {
            composerRequire555f246cb7db6869b6fb9f48d5e5f3d2($fileIdentifier, $file);
        }

        return $loader;
    }
}

function composerRequire555f246cb7db6869b6fb9f48d5e5f3d2($fileIdentifier, $file)
{
    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
        require $file;

        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
    }
}
[
    {
        "name": "doctrine/instantiator",
        "version": "1.2.0",
        "version_normalized": "1.2.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/doctrine/instantiator.git",
            "reference": "a2c590166b2133a4633738648b6b064edae0814a"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a",
            "reference": "a2c590166b2133a4633738648b6b064edae0814a",
            "shasum": ""
        },
        "require": {
            "php": "^7.1"
        },
        "require-dev": {
            "doctrine/coding-standard": "^6.0",
            "ext-pdo": "*",
            "ext-phar": "*",
            "phpbench/phpbench": "^0.13",
            "phpstan/phpstan-phpunit": "^0.11",
            "phpstan/phpstan-shim": "^0.11",
            "phpunit/phpunit": "^7.0"
        },
        "time": "2019-03-17T17:37:11+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.2.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Marco Pivetta",
                "email": "ocramius@gmail.com",
                "homepage": "http://ocramius.github.com/"
            }
        ],
        "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
        "homepage": "https://www.doctrine-project.org/projects/instantiator.html",
        "keywords": [
            "constructor",
            "instantiate"
        ]
    },
    {
        "name": "dragonmantank/cron-expression",
        "version": "v1.2.1",
        "version_normalized": "1.2.1.0",
        "source": {
            "type": "git",
            "url": "https://github.com/dragonmantank/cron-expression.git",
            "reference": "9504fa9ea681b586028adaaa0877db4aecf32bad"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/9504fa9ea681b586028adaaa0877db4aecf32bad",
            "reference": "9504fa9ea681b586028adaaa0877db4aecf32bad",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.2"
        },
        "require-dev": {
            "phpunit/phpunit": "~4.0|~5.0"
        },
        "time": "2017-01-23T04:29:33+00:00",
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Cron\\": "src/Cron/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Michael Dowling",
                "email": "mtdowling@gmail.com",
                "homepage": "https://github.com/mtdowling"
            }
        ],
        "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due",
        "keywords": [
            "cron",
            "schedule"
        ]
    },
    {
        "name": "elasticsearch/elasticsearch",
        "version": "v5.4.0",
        "version_normalized": "5.4.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/elastic/elasticsearch-php.git",
            "reference": "d3c5b55ad94f5053ca76c48585b4cde2cdc6bc59"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/d3c5b55ad94f5053ca76c48585b4cde2cdc6bc59",
            "reference": "d3c5b55ad94f5053ca76c48585b4cde2cdc6bc59",
            "shasum": ""
        },
        "require": {
            "guzzlehttp/ringphp": "~1.0",
            "php": "^5.6|^7.0",
            "psr/log": "~1.0"
        },
        "require-dev": {
            "cpliakas/git-wrapper": "~1.0",
            "doctrine/inflector": "^1.1",
            "mockery/mockery": "0.9.4",
            "phpunit/phpunit": "^4.7|^5.4",
            "sami/sami": "~3.2",
            "symfony/finder": "^2.8",
            "symfony/yaml": "^2.8"
        },
        "suggest": {
            "ext-curl": "*",
            "monolog/monolog": "Allows for client-level logging and tracing"
        },
        "time": "2019-01-08T18:57:00+00:00",
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Elasticsearch\\": "src/Elasticsearch/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "Apache-2.0"
        ],
        "authors": [
            {
                "name": "Zachary Tong"
            }
        ],
        "description": "PHP Client for Elasticsearch",
        "keywords": [
            "client",
            "elasticsearch",
            "search"
        ]
    },
    {
        "name": "ezyang/htmlpurifier",
        "version": "v4.10.0",
        "version_normalized": "4.10.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/ezyang/htmlpurifier.git",
            "reference": "d85d39da4576a6934b72480be6978fb10c860021"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/d85d39da4576a6934b72480be6978fb10c860021",
            "reference": "d85d39da4576a6934b72480be6978fb10c860021",
            "shasum": ""
        },
        "require": {
            "php": ">=5.2"
        },
        "require-dev": {
            "simpletest/simpletest": "^1.1"
        },
        "time": "2018-02-23T01:58:20+00:00",
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "psr-0": {
                "HTMLPurifier": "library/"
            },
            "files": [
                "library/HTMLPurifier.composer.php"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "LGPL"
        ],
        "authors": [
            {
                "name": "Edward Z. Yang",
                "email": "admin@htmlpurifier.org",
                "homepage": "http://ezyang.com"
            }
        ],
        "description": "Standards compliant HTML filter written in PHP",
        "homepage": "http://htmlpurifier.org/",
        "keywords": [
            "html"
        ]
    },
    {
        "name": "firebase/php-jwt",
        "version": "v5.0.0",
        "version_normalized": "5.0.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/firebase/php-jwt.git",
            "reference": "9984a4d3a32ae7673d6971ea00bae9d0a1abba0e"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/firebase/php-jwt/zipball/9984a4d3a32ae7673d6971ea00bae9d0a1abba0e",
            "reference": "9984a4d3a32ae7673d6971ea00bae9d0a1abba0e",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.0"
        },
        "require-dev": {
            "phpunit/phpunit": " 4.8.35"
        },
        "time": "2017-06-27T22:17:23+00:00",
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Firebase\\JWT\\": "src"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "BSD-3-Clause"
        ],
        "authors": [
            {
                "name": "Neuman Vong",
                "email": "neuman+pear@twilio.com",
                "role": "Developer"
            },
            {
                "name": "Anant Narayanan",
                "email": "anant@php.net",
                "role": "Developer"
            }
        ],
        "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
        "homepage": "https://github.com/firebase/php-jwt"
    },
    {
        "name": "guzzlehttp/ringphp",
        "version": "1.1.1",
        "version_normalized": "1.1.1.0",
        "source": {
            "type": "git",
            "url": "https://github.com/guzzle/RingPHP.git",
            "reference": "5e2a174052995663dd68e6b5ad838afd47dd615b"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/guzzle/RingPHP/zipball/5e2a174052995663dd68e6b5ad838afd47dd615b",
            "reference": "5e2a174052995663dd68e6b5ad838afd47dd615b",
            "shasum": ""
        },
        "require": {
            "guzzlehttp/streams": "~3.0",
            "php": ">=5.4.0",
            "react/promise": "~2.0"
        },
        "require-dev": {
            "ext-curl": "*",
            "phpunit/phpunit": "~4.0"
        },
        "suggest": {
            "ext-curl": "Guzzle will use specific adapters if cURL is present"
        },
        "time": "2018-07-31T13:22:33+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.1-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "GuzzleHttp\\Ring\\": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Michael Dowling",
                "email": "mtdowling@gmail.com",
                "homepage": "https://github.com/mtdowling"
            }
        ],
        "description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function."
    },
    {
        "name": "guzzlehttp/streams",
        "version": "3.0.0",
        "version_normalized": "3.0.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/guzzle/streams.git",
            "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/guzzle/streams/zipball/47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5",
            "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5",
            "shasum": ""
        },
        "require": {
            "php": ">=5.4.0"
        },
        "require-dev": {
            "phpunit/phpunit": "~4.0"
        },
        "time": "2014-10-12T19:18:40+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "3.0-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "GuzzleHttp\\Stream\\": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Michael Dowling",
                "email": "mtdowling@gmail.com",
                "homepage": "https://github.com/mtdowling"
            }
        ],
        "description": "Provides a simple abstraction over streams of data",
        "homepage": "http://guzzlephp.org/",
        "keywords": [
            "Guzzle",
            "stream"
        ]
    },
    {
        "name": "linways/linways-base",
        "version": "1.0.40",
        "version_normalized": "1.0.40.0",
        "source": {
            "type": "git",
            "url": "git@gitlab.com:linways/linways-base.git",
            "reference": "328b2fa202215ca63036cb8ee206d4700f71d179"
        },
        "dist": {
            "type": "zip",
            "url": "https://gitlab.com/api/v4/projects/linways%2Flinways-base/repository/archive.zip?sha=328b2fa202215ca63036cb8ee206d4700f71d179",
            "reference": "328b2fa202215ca63036cb8ee206d4700f71d179",
            "shasum": ""
        },
        "require": {
            "elasticsearch/elasticsearch": "^5.3",
            "ezyang/htmlpurifier": "^4.10",
            "monolog/monolog": "^1.23",
            "php": ">=5.4.0",
            "php-amqplib/php-amqplib": "^2.7",
            "voku/php-hooks": "^0.2.11"
        },
        "require-dev": {
            "phpunit/phpunit": "^5.7"
        },
        "time": "2019-04-27T05:01:10+00:00",
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "com\\linways\\base\\": "src/com/linways/base"
            }
        },
        "autoload-dev": {
            "psr-4": {
                "test\\": "test"
            }
        },
        "description": "Linways base"
    },
    {
        "name": "linways/nucleus-core",
        "version": "v1.0.25",
        "version_normalized": "1.0.25.0",
        "source": {
            "type": "git",
            "url": "git@gitlab.com:linways/nucleus-core.git",
            "reference": "a3922eeb920a80ad18a873edfebc6907242d8863"
        },
        "dist": {
            "type": "zip",
            "url": "https://gitlab.com/api/v4/projects/linways%2Fnucleus-core/repository/archive.zip?sha=a3922eeb920a80ad18a873edfebc6907242d8863",
            "reference": "a3922eeb920a80ad18a873edfebc6907242d8863",
            "shasum": ""
        },
        "require": {
            "dragonmantank/cron-expression": "^1.2",
            "firebase/php-jwt": "^5.0",
            "linways/linways-base": "^1.0.0",
            "robmorgan/phinx": "^0.9.1"
        },
        "require-dev": {
            "phpunit/phpunit": "^5.7"
        },
        "time": "2019-05-06T10:51:38+00:00",
        "type": "project",
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "com\\linways\\": "src/com/linways"
            }
        },
        "autoload-dev": {
            "psr-4": {
                "test\\": "test/"
            }
        },
        "description": "Project for tenant and api access management for professional"
    },
    {
        "name": "monolog/monolog",
        "version": "1.24.0",
        "version_normalized": "1.24.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/Seldaek/monolog.git",
            "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266",
            "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.0",
            "psr/log": "~1.0"
        },
        "provide": {
            "psr/log-implementation": "1.0.0"
        },
        "require-dev": {
            "aws/aws-sdk-php": "^2.4.9 || ^3.0",
            "doctrine/couchdb": "~1.0@dev",
            "graylog2/gelf-php": "~1.0",
            "jakub-onderka/php-parallel-lint": "0.9",
            "php-amqplib/php-amqplib": "~2.4",
            "php-console/php-console": "^3.1.3",
            "phpunit/phpunit": "~4.5",
            "phpunit/phpunit-mock-objects": "2.3.0",
            "ruflin/elastica": ">=0.90 <3.0",
            "sentry/sentry": "^0.13",
            "swiftmailer/swiftmailer": "^5.3|^6.0"
        },
        "suggest": {
            "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
            "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
            "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
            "ext-mongo": "Allow sending log messages to a MongoDB server",
            "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
            "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
            "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
            "php-console/php-console": "Allow sending log messages to Google Chrome",
            "rollbar/rollbar": "Allow sending log messages to Rollbar",
            "ruflin/elastica": "Allow sending log messages to an Elastic Search server",
            "sentry/sentry": "Allow sending log messages to a Sentry server"
        },
        "time": "2018-11-05T09:00:11+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "2.0.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Monolog\\": "src/Monolog"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Jordi Boggiano",
                "email": "j.boggiano@seld.be",
                "homepage": "http://seld.be"
            }
        ],
        "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
        "homepage": "http://github.com/Seldaek/monolog",
        "keywords": [
            "log",
            "logging",
            "psr-3"
        ]
    },
    {
        "name": "myclabs/deep-copy",
        "version": "1.9.1",
        "version_normalized": "1.9.1.0",
        "source": {
            "type": "git",
            "url": "https://github.com/myclabs/DeepCopy.git",
            "reference": "e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72",
            "reference": "e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72",
            "shasum": ""
        },
        "require": {
            "php": "^7.1"
        },
        "replace": {
            "myclabs/deep-copy": "self.version"
        },
        "require-dev": {
            "doctrine/collections": "^1.0",
            "doctrine/common": "^2.6",
            "phpunit/phpunit": "^7.1"
        },
        "time": "2019-04-07T13:18:21+00:00",
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "DeepCopy\\": "src/DeepCopy/"
            },
            "files": [
                "src/DeepCopy/deep_copy.php"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "description": "Create deep copies (clones) of your objects",
        "keywords": [
            "clone",
            "copy",
            "duplicate",
            "object",
            "object graph"
        ]
    },
    {
        "name": "php-amqplib/php-amqplib",
        "version": "v2.9.2",
        "version_normalized": "2.9.2.0",
        "source": {
            "type": "git",
            "url": "https://github.com/php-amqplib/php-amqplib.git",
            "reference": "76faddcd668dabb8d4f7c00e86b8a9decd781a59"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/php-amqplib/php-amqplib/zipball/76faddcd668dabb8d4f7c00e86b8a9decd781a59",
            "reference": "76faddcd668dabb8d4f7c00e86b8a9decd781a59",
            "shasum": ""
        },
        "require": {
            "ext-bcmath": "*",
            "ext-sockets": "*",
            "php": ">=5.4.0"
        },
        "replace": {
            "videlalvaro/php-amqplib": "self.version"
        },
        "require-dev": {
            "ext-curl": "*",
            "nategood/httpful": "^0.2.20",
            "phpdocumentor/phpdocumentor": "^2.9",
            "phpunit/phpunit": "^4.8",
            "squizlabs/php_codesniffer": "^2.5"
        },
        "time": "2019-04-24T15:36:21+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "2.8-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "PhpAmqpLib\\": "PhpAmqpLib/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "LGPL-2.1-or-later"
        ],
        "authors": [
            {
                "name": "Alvaro Videla",
                "role": "Original Maintainer"
            },
            {
                "name": "John Kelly",
                "email": "johnmkelly86@gmail.com",
                "role": "Maintainer"
            },
            {
                "name": "RaÃºl Araya",
                "email": "nubeiro@gmail.com",
                "role": "Maintainer"
            },
            {
                "name": "Luke Bakken",
                "email": "luke@bakken.io",
                "role": "Maintainer"
            }
        ],
        "description": "Formerly videlalvaro/php-amqplib.  This library is a pure PHP implementation of the AMQP protocol. It's been tested against RabbitMQ.",
        "homepage": "https://github.com/php-amqplib/php-amqplib/",
        "keywords": [
            "message",
            "queue",
            "rabbitmq"
        ]
    },
    {
        "name": "phpdocumentor/reflection-common",
        "version": "1.0.1",
        "version_normalized": "1.0.1.0",
        "source": {
            "type": "git",
            "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
            "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
            "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
            "shasum": ""
        },
        "require": {
            "php": ">=5.5"
        },
        "require-dev": {
            "phpunit/phpunit": "^4.6"
        },
        "time": "2017-09-11T18:02:19+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.0.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "phpDocumentor\\Reflection\\": [
                    "src"
                ]
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Jaap van Otterdijk",
                "email": "opensource@ijaap.nl"
            }
        ],
        "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
        "homepage": "http://www.phpdoc.org",
        "keywords": [
            "FQSEN",
            "phpDocumentor",
            "phpdoc",
            "reflection",
            "static analysis"
        ]
    },
    {
        "name": "phpdocumentor/reflection-docblock",
        "version": "4.3.1",
        "version_normalized": "4.3.1.0",
        "source": {
            "type": "git",
            "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
            "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c",
            "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c",
            "shasum": ""
        },
        "require": {
            "php": "^7.0",
            "phpdocumentor/reflection-common": "^1.0.0",
            "phpdocumentor/type-resolver": "^0.4.0",
            "webmozart/assert": "^1.0"
        },
        "require-dev": {
            "doctrine/instantiator": "~1.0.5",
            "mockery/mockery": "^1.0",
            "phpunit/phpunit": "^6.4"
        },
        "time": "2019-04-30T17:48:53+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "4.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "phpDocumentor\\Reflection\\": [
                    "src/"
                ]
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Mike van Riel",
                "email": "me@mikevanriel.com"
            }
        ],
        "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock."
    },
    {
        "name": "phpdocumentor/type-resolver",
        "version": "0.4.0",
        "version_normalized": "0.4.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/phpDocumentor/TypeResolver.git",
            "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7",
            "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7",
            "shasum": ""
        },
        "require": {
            "php": "^5.5 || ^7.0",
            "phpdocumentor/reflection-common": "^1.0"
        },
        "require-dev": {
            "mockery/mockery": "^0.9.4",
            "phpunit/phpunit": "^5.2||^4.8.24"
        },
        "time": "2017-07-14T14:27:02+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.0.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "phpDocumentor\\Reflection\\": [
                    "src/"
                ]
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Mike van Riel",
                "email": "me@mikevanriel.com"
            }
        ]
    },
    {
        "name": "phpspec/prophecy",
        "version": "1.8.0",
        "version_normalized": "1.8.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/phpspec/prophecy.git",
            "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06",
            "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06",
            "shasum": ""
        },
        "require": {
            "doctrine/instantiator": "^1.0.2",
            "php": "^5.3|^7.0",
            "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0",
            "sebastian/comparator": "^1.1|^2.0|^3.0",
            "sebastian/recursion-context": "^1.0|^2.0|^3.0"
        },
        "require-dev": {
            "phpspec/phpspec": "^2.5|^3.2",
            "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1"
        },
        "time": "2018-08-05T17:53:17+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.8.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-0": {
                "Prophecy\\": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Konstantin Kudryashov",
                "email": "ever.zet@gmail.com",
                "homepage": "http://everzet.com"
            },
            {
                "name": "Marcello Duarte",
                "email": "marcello.duarte@gmail.com"
            }
        ],
        "description": "Highly opinionated mocking framework for PHP 5.3+",
        "homepage": "https://github.com/phpspec/prophecy",
        "keywords": [
            "Double",
            "Dummy",
            "fake",
            "mock",
            "spy",
            "stub"
        ]
    },
    {
        "name": "phpunit/php-code-coverage",
        "version": "4.0.8",
        "version_normalized": "4.0.8.0",
        "source": {
            "type": "git",
            "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
            "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d",
            "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d",
            "shasum": ""
        },
        "require": {
            "ext-dom": "*",
            "ext-xmlwriter": "*",
            "php": "^5.6 || ^7.0",
            "phpunit/php-file-iterator": "^1.3",
            "phpunit/php-text-template": "^1.2",
            "phpunit/php-token-stream": "^1.4.2 || ^2.0",
            "sebastian/code-unit-reverse-lookup": "^1.0",
            "sebastian/environment": "^1.3.2 || ^2.0",
            "sebastian/version": "^1.0 || ^2.0"
        },
        "require-dev": {
            "ext-xdebug": "^2.1.4",
            "phpunit/phpunit": "^5.7"
        },
        "suggest": {
            "ext-xdebug": "^2.5.1"
        },
        "time": "2017-04-02T07:44:40+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "4.0.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "classmap": [
                "src/"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "BSD-3-Clause"
        ],
        "authors": [
            {
                "name": "Sebastian Bergmann",
                "email": "sb@sebastian-bergmann.de",
                "role": "lead"
            }
        ],
        "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
        "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
        "keywords": [
            "coverage",
            "testing",
            "xunit"
        ]
    },
    {
        "name": "phpunit/php-file-iterator",
        "version": "1.4.5",
        "version_normalized": "1.4.5.0",
        "source": {
            "type": "git",
            "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
            "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4",
            "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.3"
        },
        "time": "2017-11-27T13:52:08+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.4.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "classmap": [
                "src/"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "BSD-3-Clause"
        ],
        "authors": [
            {
                "name": "Sebastian Bergmann",
                "email": "sb@sebastian-bergmann.de",
                "role": "lead"
            }
        ],
        "description": "FilterIterator implementation that filters files based on a list of suffixes.",
        "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
        "keywords": [
            "filesystem",
            "iterator"
        ]
    },
    {
        "name": "phpunit/php-text-template",
        "version": "1.2.1",
        "version_normalized": "1.2.1.0",
        "source": {
            "type": "git",
            "url": "https://github.com/sebastianbergmann/php-text-template.git",
            "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
            "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.3"
        },
        "time": "2015-06-21T13:50:34+00:00",
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "classmap": [
                "src/"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "BSD-3-Clause"
        ],
        "authors": [
            {
                "name": "Sebastian Bergmann",
                "email": "sebastian@phpunit.de",
                "role": "lead"
            }
        ],
        "description": "Simple template engine.",
        "homepage": "https://github.com/sebastianbergmann/php-text-template/",
        "keywords": [
            "template"
        ]
    },
    {
        "name": "phpunit/php-timer",
        "version": "1.0.9",
        "version_normalized": "1.0.9.0",
        "source": {
            "type": "git",
            "url": "https://github.com/sebastianbergmann/php-timer.git",
            "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
            "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
            "shasum": ""
        },
        "require": {
            "php": "^5.3.3 || ^7.0"
        },
        "require-dev": {
            "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
        },
        "time": "2017-02-26T11:10:40+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.0-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "classmap": [
                "src/"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "BSD-3-Clause"
        ],
        "authors": [
            {
                "name": "Sebastian Bergmann",
                "email": "sb@sebastian-bergmann.de",
                "role": "lead"
            }
        ],
        "description": "Utility class for timing",
        "homepage": "https://github.com/sebastianbergmann/php-timer/",
        "keywords": [
            "timer"
        ]
    },
    {
        "name": "phpunit/php-token-stream",
        "version": "2.0.2",
        "version_normalized": "2.0.2.0",
        "source": {
            "type": "git",
            "url": "https://github.com/sebastianbergmann/php-token-stream.git",
            "reference": "791198a2c6254db10131eecfe8c06670700904db"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db",
            "reference": "791198a2c6254db10131eecfe8c06670700904db",
            "shasum": ""
        },
        "require": {
            "ext-tokenizer": "*",
            "php": "^7.0"
        },
        "require-dev": {
            "phpunit/phpunit": "^6.2.4"
        },
        "time": "2017-11-27T05:48:46+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "2.0-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "classmap": [
                "src/"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "BSD-3-Clause"
        ],
        "authors": [
            {
                "name": "Sebastian Bergmann",
                "email": "sebastian@phpunit.de"
            }
        ],
        "description": "Wrapper around PHP's tokenizer extension.",
        "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
        "keywords": [
            "tokenizer"
        ]
    },
    {
        "name": "phpunit/phpunit",
        "version": "5.7.27",
        "version_normalized": "5.7.27.0",
        "source": {
            "type": "git",
            "url": "https://github.com/sebastianbergmann/phpunit.git",
            "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c",
            "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c",
            "shasum": ""
        },
        "require": {
            "ext-dom": "*",
            "ext-json": "*",
            "ext-libxml": "*",
            "ext-mbstring": "*",
            "ext-xml": "*",
            "myclabs/deep-copy": "~1.3",
            "php": "^5.6 || ^7.0",
            "phpspec/prophecy": "^1.6.2",
            "phpunit/php-code-coverage": "^4.0.4",
            "phpunit/php-file-iterator": "~1.4",
            "phpunit/php-text-template": "~1.2",
            "phpunit/php-timer": "^1.0.6",
            "phpunit/phpunit-mock-objects": "^3.2",
            "sebastian/comparator": "^1.2.4",
            "sebastian/diff": "^1.4.3",
            "sebastian/environment": "^1.3.4 || ^2.0",
            "sebastian/exporter": "~2.0",
            "sebastian/global-state": "^1.1",
            "sebastian/object-enumerator": "~2.0",
            "sebastian/resource-operations": "~1.0",
            "sebastian/version": "^1.0.6|^2.0.1",
            "symfony/yaml": "~2.1|~3.0|~4.0"
        },
        "conflict": {
            "phpdocumentor/reflection-docblock": "3.0.2"
        },
        "require-dev": {
            "ext-pdo": "*"
        },
        "suggest": {
            "ext-xdebug": "*",
            "phpunit/php-invoker": "~1.1"
        },
        "time": "2018-02-01T05:50:59+00:00",
        "bin": [
            "phpunit"
        ],
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "5.7.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "classmap": [
                "src/"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "BSD-3-Clause"
        ],
        "authors": [
            {
                "name": "Sebastian Bergmann",
                "email": "sebastian@phpunit.de",
                "role": "lead"
            }
        ],
        "description": "The PHP Unit Testing framework.",
        "homepage": "https://phpunit.de/",
        "keywords": [
            "phpunit",
            "testing",
            "xunit"
        ]
    },
    {
        "name": "phpunit/phpunit-mock-objects",
        "version": "3.4.4",
        "version_normalized": "3.4.4.0",
        "source": {
            "type": "git",
            "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
            "reference": "a23b761686d50a560cc56233b9ecf49597cc9118"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/a23b761686d50a560cc56233b9ecf49597cc9118",
            "reference": "a23b761686d50a560cc56233b9ecf49597cc9118",
            "shasum": ""
        },
        "require": {
            "doctrine/instantiator": "^1.0.2",
            "php": "^5.6 || ^7.0",
            "phpunit/php-text-template": "^1.2",
            "sebastian/exporter": "^1.2 || ^2.0"
        },
        "conflict": {
            "phpunit/phpunit": "<5.4.0"
        },
        "require-dev": {
            "phpunit/phpunit": "^5.4"
        },
        "suggest": {
            "ext-soap": "*"
        },
        "time": "2017-06-30T09:13:00+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "3.2.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "classmap": [
                "src/"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "BSD-3-Clause"
        ],
        "authors": [
            {
                "name": "Sebastian Bergmann",
                "email": "sb@sebastian-bergmann.de",
                "role": "lead"
            }
        ],
        "description": "Mock Object library for PHPUnit",
        "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
        "keywords": [
            "mock",
            "xunit"
        ],
        "abandoned": true
    },
    {
        "name": "psr/log",
        "version": "1.1.0",
        "version_normalized": "1.1.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/php-fig/log.git",
            "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
            "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.0"
        },
        "time": "2018-11-20T15:27:04+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.0.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Psr\\Log\\": "Psr/Log/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "PHP-FIG",
                "homepage": "http://www.php-fig.org/"
            }
        ],
        "description": "Common interface for logging libraries",
        "homepage": "https://github.com/php-fig/log",
        "keywords": [
            "log",
            "psr",
            "psr-3"
        ]
    },
    {
        "name": "react/promise",
        "version": "v2.7.1",
        "version_normalized": "2.7.1.0",
        "source": {
            "type": "git",
            "url": "https://github.com/reactphp/promise.git",
            "reference": "31ffa96f8d2ed0341a57848cbb84d88b89dd664d"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/reactphp/promise/zipball/31ffa96f8d2ed0341a57848cbb84d88b89dd664d",
            "reference": "31ffa96f8d2ed0341a57848cbb84d88b89dd664d",
            "shasum": ""
        },
        "require": {
            "php": ">=5.4.0"
        },
        "require-dev": {
            "phpunit/phpunit": "~4.8"
        },
        "time": "2019-01-07T21:25:54+00:00",
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "React\\Promise\\": "src/"
            },
            "files": [
                "src/functions_include.php"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Jan Sorgalla",
                "email": "jsorgalla@gmail.com"
            }
        ],
        "description": "A lightweight implementation of CommonJS Promises/A for PHP",
        "keywords": [
            "promise",
            "promises"
        ]
    },
    {
        "name": "robmorgan/phinx",
        "version": "0.9.2",
        "version_normalized": "0.9.2.0",
        "source": {
            "type": "git",
            "url": "https://github.com/cakephp/phinx.git",
            "reference": "e1698319ad55157c233b658c08f7a10617e797ca"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/cakephp/phinx/zipball/e1698319ad55157c233b658c08f7a10617e797ca",
            "reference": "e1698319ad55157c233b658c08f7a10617e797ca",
            "shasum": ""
        },
        "require": {
            "php": ">=5.4",
            "symfony/config": "^2.8|^3.0|^4.0",
            "symfony/console": "^2.8|^3.0|^4.0",
            "symfony/yaml": "^2.8|^3.0|^4.0"
        },
        "require-dev": {
            "cakephp/cakephp-codesniffer": "^3.0",
            "phpunit/phpunit": "^4.8.35|^5.7|^6.5"
        },
        "time": "2017-12-23T06:48:51+00:00",
        "bin": [
            "bin/phinx"
        ],
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Phinx\\": "src/Phinx"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Woody Gilk",
                "email": "woody.gilk@gmail.com",
                "homepage": "http://shadowhand.me",
                "role": "Developer"
            },
            {
                "name": "Rob Morgan",
                "email": "robbym@gmail.com",
                "homepage": "https://robmorgan.id.au",
                "role": "Lead Developer"
            },
            {
                "name": "Richard Quadling",
                "email": "rquadling@gmail.com",
                "role": "Developer"
            },
            {
                "name": "CakePHP Community",
                "homepage": "https://github.com/cakephp/phinx/graphs/contributors"
            }
        ],
        "description": "Phinx makes it ridiculously easy to manage the database migrations for your PHP app.",
        "homepage": "https://phinx.org",
        "keywords": [
            "database",
            "database migrations",
            "db",
            "migrations",
            "phinx"
        ]
    },
    {
        "name": "sebastian/code-unit-reverse-lookup",
        "version": "1.0.1",
        "version_normalized": "1.0.1.0",
        "source": {
            "type": "git",
            "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
            "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
            "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
            "shasum": ""
        },
        "require": {
            "php": "^5.6 || ^7.0"
        },
        "require-dev": {
            "phpunit/phpunit": "^5.7 || ^6.0"
        },
        "time": "2017-03-04T06:30:41+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.0.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "classmap": [
                "src/"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "BSD-3-Clause"
        ],
        "authors": [
            {
                "name": "Sebastian Bergmann",
                "email": "sebastian@phpunit.de"
            }
        ],
        "description": "Looks up which function or method a line of code belongs to",
        "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/"
    },
    {
        "name": "sebastian/comparator",
        "version": "1.2.4",
        "version_normalized": "1.2.4.0",
        "source": {
            "type": "git",
            "url": "https://github.com/sebastianbergmann/comparator.git",
            "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
            "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.3",
            "sebastian/diff": "~1.2",
            "sebastian/exporter": "~1.2 || ~2.0"
        },
        "require-dev": {
            "phpunit/phpunit": "~4.4"
        },
        "time": "2017-01-29T09:50:25+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.2.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "classmap": [
                "src/"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "BSD-3-Clause"
        ],
        "authors": [
            {
                "name": "Jeff Welch",
                "email": "whatthejeff@gmail.com"
            },
            {
                "name": "Volker Dusch",
                "email": "github@wallbash.com"
            },
            {
                "name": "Bernhard Schussek",
                "email": "bschussek@2bepublished.at"
            },
            {
                "name": "Sebastian Bergmann",
                "email": "sebastian@phpunit.de"
            }
        ],
        "description": "Provides the functionality to compare PHP values for equality",
        "homepage": "http://www.github.com/sebastianbergmann/comparator",
        "keywords": [
            "comparator",
            "compare",
            "equality"
        ]
    },
    {
        "name": "sebastian/diff",
        "version": "1.4.3",
        "version_normalized": "1.4.3.0",
        "source": {
            "type": "git",
            "url": "https://github.com/sebastianbergmann/diff.git",
            "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4",
            "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4",
            "shasum": ""
        },
        "require": {
            "php": "^5.3.3 || ^7.0"
        },
        "require-dev": {
            "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
        },
        "time": "2017-05-22T07:24:03+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.4-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "classmap": [
                "src/"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "BSD-3-Clause"
        ],
        "authors": [
            {
                "name": "Kore Nordmann",
                "email": "mail@kore-nordmann.de"
            },
            {
                "name": "Sebastian Bergmann",
                "email": "sebastian@phpunit.de"
            }
        ],
        "description": "Diff implementation",
        "homepage": "https://github.com/sebastianbergmann/diff",
        "keywords": [
            "diff"
        ]
    },
    {
        "name": "sebastian/environment",
        "version": "2.0.0",
        "version_normalized": "2.0.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/sebastianbergmann/environment.git",
            "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac",
            "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac",
            "shasum": ""
        },
        "require": {
            "php": "^5.6 || ^7.0"
        },
        "require-dev": {
            "phpunit/phpunit": "^5.0"
        },
        "time": "2016-11-26T07:53:53+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "2.0.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "classmap": [
                "src/"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "BSD-3-Clause"
        ],
        "authors": [
            {
                "name": "Sebastian Bergmann",
                "email": "sebastian@phpunit.de"
            }
        ],
        "description": "Provides functionality to handle HHVM/PHP environments",
        "homepage": "http://www.github.com/sebastianbergmann/environment",
        "keywords": [
            "Xdebug",
            "environment",
            "hhvm"
        ]
    },
    {
        "name": "sebastian/exporter",
        "version": "2.0.0",
        "version_normalized": "2.0.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/sebastianbergmann/exporter.git",
            "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4",
            "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.3",
            "sebastian/recursion-context": "~2.0"
        },
        "require-dev": {
            "ext-mbstring": "*",
            "phpunit/phpunit": "~4.4"
        },
        "time": "2016-11-19T08:54:04+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "2.0.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "classmap": [
                "src/"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "BSD-3-Clause"
        ],
        "authors": [
            {
                "name": "Jeff Welch",
                "email": "whatthejeff@gmail.com"
            },
            {
                "name": "Volker Dusch",
                "email": "github@wallbash.com"
            },
            {
                "name": "Bernhard Schussek",
                "email": "bschussek@2bepublished.at"
            },
            {
                "name": "Sebastian Bergmann",
                "email": "sebastian@phpunit.de"
            },
            {
                "name": "Adam Harvey",
                "email": "aharvey@php.net"
            }
        ],
        "description": "Provides the functionality to export PHP variables for visualization",
        "homepage": "http://www.github.com/sebastianbergmann/exporter",
        "keywords": [
            "export",
            "exporter"
        ]
    },
    {
        "name": "sebastian/global-state",
        "version": "1.1.1",
        "version_normalized": "1.1.1.0",
        "source": {
            "type": "git",
            "url": "https://github.com/sebastianbergmann/global-state.git",
            "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
            "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.3"
        },
        "require-dev": {
            "phpunit/phpunit": "~4.2"
        },
        "suggest": {
            "ext-uopz": "*"
        },
        "time": "2015-10-12T03:26:01+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.0-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "classmap": [
                "src/"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "BSD-3-Clause"
        ],
        "authors": [
            {
                "name": "Sebastian Bergmann",
                "email": "sebastian@phpunit.de"
            }
        ],
        "description": "Snapshotting of global state",
        "homepage": "http://www.github.com/sebastianbergmann/global-state",
        "keywords": [
            "global state"
        ]
    },
    {
        "name": "sebastian/object-enumerator",
        "version": "2.0.1",
        "version_normalized": "2.0.1.0",
        "source": {
            "type": "git",
            "url": "https://github.com/sebastianbergmann/object-enumerator.git",
            "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7",
            "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7",
            "shasum": ""
        },
        "require": {
            "php": ">=5.6",
            "sebastian/recursion-context": "~2.0"
        },
        "require-dev": {
            "phpunit/phpunit": "~5"
        },
        "time": "2017-02-18T15:18:39+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "2.0.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "classmap": [
                "src/"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "BSD-3-Clause"
        ],
        "authors": [
            {
                "name": "Sebastian Bergmann",
                "email": "sebastian@phpunit.de"
            }
        ],
        "description": "Traverses array structures and object graphs to enumerate all referenced objects",
        "homepage": "https://github.com/sebastianbergmann/object-enumerator/"
    },
    {
        "name": "sebastian/recursion-context",
        "version": "2.0.0",
        "version_normalized": "2.0.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/sebastianbergmann/recursion-context.git",
            "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a",
            "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.3"
        },
        "require-dev": {
            "phpunit/phpunit": "~4.4"
        },
        "time": "2016-11-19T07:33:16+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "2.0.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "classmap": [
                "src/"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "BSD-3-Clause"
        ],
        "authors": [
            {
                "name": "Jeff Welch",
                "email": "whatthejeff@gmail.com"
            },
            {
                "name": "Sebastian Bergmann",
                "email": "sebastian@phpunit.de"
            },
            {
                "name": "Adam Harvey",
                "email": "aharvey@php.net"
            }
        ],
        "description": "Provides functionality to recursively process PHP variables",
        "homepage": "http://www.github.com/sebastianbergmann/recursion-context"
    },
    {
        "name": "sebastian/resource-operations",
        "version": "1.0.0",
        "version_normalized": "1.0.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/sebastianbergmann/resource-operations.git",
            "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52",
            "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52",
            "shasum": ""
        },
        "require": {
            "php": ">=5.6.0"
        },
        "time": "2015-07-28T20:34:47+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.0.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "classmap": [
                "src/"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "BSD-3-Clause"
        ],
        "authors": [
            {
                "name": "Sebastian Bergmann",
                "email": "sebastian@phpunit.de"
            }
        ],
        "description": "Provides a list of PHP built-in functions that operate on resources",
        "homepage": "https://www.github.com/sebastianbergmann/resource-operations"
    },
    {
        "name": "sebastian/version",
        "version": "2.0.1",
        "version_normalized": "2.0.1.0",
        "source": {
            "type": "git",
            "url": "https://github.com/sebastianbergmann/version.git",
            "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019",
            "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019",
            "shasum": ""
        },
        "require": {
            "php": ">=5.6"
        },
        "time": "2016-10-03T07:35:21+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "2.0.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "classmap": [
                "src/"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "BSD-3-Clause"
        ],
        "authors": [
            {
                "name": "Sebastian Bergmann",
                "email": "sebastian@phpunit.de",
                "role": "lead"
            }
        ],
        "description": "Library that helps with managing the version number of Git-hosted PHP projects",
        "homepage": "https://github.com/sebastianbergmann/version"
    },
    {
        "name": "symfony/config",
        "version": "v4.2.8",
        "version_normalized": "4.2.8.0",
        "source": {
            "type": "git",
            "url": "https://github.com/symfony/config.git",
            "reference": "0e745ead307d5dcd4e163e94a47ec04b1428943f"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/symfony/config/zipball/0e745ead307d5dcd4e163e94a47ec04b1428943f",
            "reference": "0e745ead307d5dcd4e163e94a47ec04b1428943f",
            "shasum": ""
        },
        "require": {
            "php": "^7.1.3",
            "symfony/filesystem": "~3.4|~4.0",
            "symfony/polyfill-ctype": "~1.8"
        },
        "conflict": {
            "symfony/finder": "<3.4"
        },
        "require-dev": {
            "symfony/dependency-injection": "~3.4|~4.0",
            "symfony/event-dispatcher": "~3.4|~4.0",
            "symfony/finder": "~3.4|~4.0",
            "symfony/yaml": "~3.4|~4.0"
        },
        "suggest": {
            "symfony/yaml": "To use the yaml reference dumper"
        },
        "time": "2019-04-01T14:03:25+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "4.2-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Symfony\\Component\\Config\\": ""
            },
            "exclude-from-classmap": [
                "/Tests/"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Fabien Potencier",
                "email": "fabien@symfony.com"
            },
            {
                "name": "Symfony Community",
                "homepage": "https://symfony.com/contributors"
            }
        ],
        "description": "Symfony Config Component",
        "homepage": "https://symfony.com"
    },
    {
        "name": "symfony/console",
        "version": "v3.4.27",
        "version_normalized": "3.4.27.0",
        "source": {
            "type": "git",
            "url": "https://github.com/symfony/console.git",
            "reference": "15a9104356436cb26e08adab97706654799d31d8"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/symfony/console/zipball/15a9104356436cb26e08adab97706654799d31d8",
            "reference": "15a9104356436cb26e08adab97706654799d31d8",
            "shasum": ""
        },
        "require": {
            "php": "^5.5.9|>=7.0.8",
            "symfony/debug": "~2.8|~3.0|~4.0",
            "symfony/polyfill-mbstring": "~1.0"
        },
        "conflict": {
            "symfony/dependency-injection": "<3.4",
            "symfony/process": "<3.3"
        },
        "provide": {
            "psr/log-implementation": "1.0"
        },
        "require-dev": {
            "psr/log": "~1.0",
            "symfony/config": "~3.3|~4.0",
            "symfony/dependency-injection": "~3.4|~4.0",
            "symfony/event-dispatcher": "~2.8|~3.0|~4.0",
            "symfony/lock": "~3.4|~4.0",
            "symfony/process": "~3.3|~4.0"
        },
        "suggest": {
            "psr/log": "For using the console logger",
            "symfony/event-dispatcher": "",
            "symfony/lock": "",
            "symfony/process": ""
        },
        "time": "2019-04-08T09:29:13+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "3.4-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Symfony\\Component\\Console\\": ""
            },
            "exclude-from-classmap": [
                "/Tests/"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Fabien Potencier",
                "email": "fabien@symfony.com"
            },
            {
                "name": "Symfony Community",
                "homepage": "https://symfony.com/contributors"
            }
        ],
        "description": "Symfony Console Component",
        "homepage": "https://symfony.com"
    },
    {
        "name": "symfony/debug",
        "version": "v4.2.8",
        "version_normalized": "4.2.8.0",
        "source": {
            "type": "git",
            "url": "https://github.com/symfony/debug.git",
            "reference": "2d279b6bb1d582dd5740d4d3251ae8c18812ed37"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/symfony/debug/zipball/2d279b6bb1d582dd5740d4d3251ae8c18812ed37",
            "reference": "2d279b6bb1d582dd5740d4d3251ae8c18812ed37",
            "shasum": ""
        },
        "require": {
            "php": "^7.1.3",
            "psr/log": "~1.0"
        },
        "conflict": {
            "symfony/http-kernel": "<3.4"
        },
        "require-dev": {
            "symfony/http-kernel": "~3.4|~4.0"
        },
        "time": "2019-04-11T11:27:41+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "4.2-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Symfony\\Component\\Debug\\": ""
            },
            "exclude-from-classmap": [
                "/Tests/"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Fabien Potencier",
                "email": "fabien@symfony.com"
            },
            {
                "name": "Symfony Community",
                "homepage": "https://symfony.com/contributors"
            }
        ],
        "description": "Symfony Debug Component",
        "homepage": "https://symfony.com"
    },
    {
        "name": "symfony/dotenv",
        "version": "v3.4.27",
        "version_normalized": "3.4.27.0",
        "source": {
            "type": "git",
            "url": "https://github.com/symfony/dotenv.git",
            "reference": "7b33c7b6f497898a173e4b9d6a7698cd789d54ce"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/symfony/dotenv/zipball/7b33c7b6f497898a173e4b9d6a7698cd789d54ce",
            "reference": "7b33c7b6f497898a173e4b9d6a7698cd789d54ce",
            "shasum": ""
        },
        "require": {
            "php": "^5.5.9|>=7.0.8"
        },
        "require-dev": {
            "symfony/process": "~3.2|~4.0"
        },
        "time": "2019-04-01T07:08:40+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "3.4-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Symfony\\Component\\Dotenv\\": ""
            },
            "exclude-from-classmap": [
                "/Tests/"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Fabien Potencier",
                "email": "fabien@symfony.com"
            },
            {
                "name": "Symfony Community",
                "homepage": "https://symfony.com/contributors"
            }
        ],
        "description": "Registers environment variables from a .env file",
        "homepage": "https://symfony.com",
        "keywords": [
            "dotenv",
            "env",
            "environment"
        ]
    },
    {
        "name": "symfony/filesystem",
        "version": "v4.2.8",
        "version_normalized": "4.2.8.0",
        "source": {
            "type": "git",
            "url": "https://github.com/symfony/filesystem.git",
            "reference": "e16b9e471703b2c60b95f14d31c1239f68f11601"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/symfony/filesystem/zipball/e16b9e471703b2c60b95f14d31c1239f68f11601",
            "reference": "e16b9e471703b2c60b95f14d31c1239f68f11601",
            "shasum": ""
        },
        "require": {
            "php": "^7.1.3",
            "symfony/polyfill-ctype": "~1.8"
        },
        "time": "2019-02-07T11:40:08+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "4.2-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Symfony\\Component\\Filesystem\\": ""
            },
            "exclude-from-classmap": [
                "/Tests/"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Fabien Potencier",
                "email": "fabien@symfony.com"
            },
            {
                "name": "Symfony Community",
                "homepage": "https://symfony.com/contributors"
            }
        ],
        "description": "Symfony Filesystem Component",
        "homepage": "https://symfony.com"
    },
    {
        "name": "symfony/polyfill-ctype",
        "version": "v1.11.0",
        "version_normalized": "1.11.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/symfony/polyfill-ctype.git",
            "reference": "82ebae02209c21113908c229e9883c419720738a"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a",
            "reference": "82ebae02209c21113908c229e9883c419720738a",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.3"
        },
        "suggest": {
            "ext-ctype": "For best performance"
        },
        "time": "2019-02-06T07:57:58+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.11-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Symfony\\Polyfill\\Ctype\\": ""
            },
            "files": [
                "bootstrap.php"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Symfony Community",
                "homepage": "https://symfony.com/contributors"
            },
            {
                "name": "Gert de Pagter",
                "email": "BackEndTea@gmail.com"
            }
        ],
        "description": "Symfony polyfill for ctype functions",
        "homepage": "https://symfony.com",
        "keywords": [
            "compatibility",
            "ctype",
            "polyfill",
            "portable"
        ]
    },
    {
        "name": "symfony/polyfill-mbstring",
        "version": "v1.11.0",
        "version_normalized": "1.11.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/symfony/polyfill-mbstring.git",
            "reference": "fe5e94c604826c35a32fa832f35bd036b6799609"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609",
            "reference": "fe5e94c604826c35a32fa832f35bd036b6799609",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.3"
        },
        "suggest": {
            "ext-mbstring": "For best performance"
        },
        "time": "2019-02-06T07:57:58+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.11-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Symfony\\Polyfill\\Mbstring\\": ""
            },
            "files": [
                "bootstrap.php"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Nicolas Grekas",
                "email": "p@tchwork.com"
            },
            {
                "name": "Symfony Community",
                "homepage": "https://symfony.com/contributors"
            }
        ],
        "description": "Symfony polyfill for the Mbstring extension",
        "homepage": "https://symfony.com",
        "keywords": [
            "compatibility",
            "mbstring",
            "polyfill",
            "portable",
            "shim"
        ]
    },
    {
        "name": "symfony/yaml",
        "version": "v3.4.27",
        "version_normalized": "3.4.27.0",
        "source": {
            "type": "git",
            "url": "https://github.com/symfony/yaml.git",
            "reference": "212a27b731e5bfb735679d1ffaac82bd6a1dc996"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/symfony/yaml/zipball/212a27b731e5bfb735679d1ffaac82bd6a1dc996",
            "reference": "212a27b731e5bfb735679d1ffaac82bd6a1dc996",
            "shasum": ""
        },
        "require": {
            "php": "^5.5.9|>=7.0.8",
            "symfony/polyfill-ctype": "~1.8"
        },
        "conflict": {
            "symfony/console": "<3.4"
        },
        "require-dev": {
            "symfony/console": "~3.4|~4.0"
        },
        "suggest": {
            "symfony/console": "For validating YAML files using the lint command"
        },
        "time": "2019-03-25T07:48:46+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "3.4-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Symfony\\Component\\Yaml\\": ""
            },
            "exclude-from-classmap": [
                "/Tests/"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Fabien Potencier",
                "email": "fabien@symfony.com"
            },
            {
                "name": "Symfony Community",
                "homepage": "https://symfony.com/contributors"
            }
        ],
        "description": "Symfony Yaml Component",
        "homepage": "https://symfony.com"
    },
    {
        "name": "voku/php-hooks",
        "version": "0.2.11",
        "version_normalized": "0.2.11.0",
        "source": {
            "type": "git",
            "url": "https://github.com/voku/php-hooks.git",
            "reference": "ace0dd3ff6f1a98f8ba9b3e940265d541d78eb8f"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/voku/php-hooks/zipball/ace0dd3ff6f1a98f8ba9b3e940265d541d78eb8f",
            "reference": "ace0dd3ff6f1a98f8ba9b3e940265d541d78eb8f",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.0"
        },
        "require-dev": {
            "phpunit/phpunit": "4.*"
        },
        "time": "2017-05-11T12:59:53+00:00",
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "voku\\helper\\": "src/voku/helper/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "GPL-3.0"
        ],
        "authors": [
            {
                "name": "Lars Moelleken",
                "homepage": "http://www.moelleken.org/",
                "role": "Developer"
            },
            {
                "name": "Ohad Raz",
                "homepage": "https://github.com/bainternet",
                "role": "Developer"
            }
        ],
        "description": "A fork of the WordPress filters hook system rolled in to a class to be ported into any PHP-based system",
        "homepage": "http://github.com/voku/php-hooks/",
        "keywords": [
            "actions",
            "filters",
            "filters-system",
            "hook-system",
            "hooks"
        ]
    },
    {
        "name": "webmozart/assert",
        "version": "1.4.0",
        "version_normalized": "1.4.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/webmozart/assert.git",
            "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/webmozart/assert/zipball/83e253c8e0be5b0257b881e1827274667c5c17a9",
            "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9",
            "shasum": ""
        },
        "require": {
            "php": "^5.3.3 || ^7.0",
            "symfony/polyfill-ctype": "^1.8"
        },
        "require-dev": {
            "phpunit/phpunit": "^4.6",
            "sebastian/version": "^1.0.1"
        },
        "time": "2018-12-25T11:19:39+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.3-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Webmozart\\Assert\\": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Bernhard Schussek",
                "email": "bschussek@gmail.com"
            }
        ],
        "description": "Assertions to validate method input/output with nice error messages.",
        "keywords": [
            "assert",
            "check",
            "validate"
        ]
    }
]
<?php

// autoload_files.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
    '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
    'ad155f8f1cf0d418fe49e248db8c661b' => $vendorDir . '/react/promise/src/functions_include.php',
    '2cffec82183ee1cea088009cef9a6fc3' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
    '6124b4c8570aa390c21fafd04a26c69f' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php',
);
{
    "name": "phpdocumentor/reflection-common",
    "keywords": ["phpdoc", "phpDocumentor", "reflection", "static analysis", "FQSEN"],
    "homepage": "http://www.phpdoc.org",
    "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
    "license": "MIT",
    "authors": [
        {
            "name": "Jaap van Otterdijk",
            "email": "opensource@ijaap.nl"
        }
    ],
    "require": {
        "php": ">=5.5"
    },
    "autoload" : {
      "psr-4" : {
        "phpDocumentor\\Reflection\\": ["src"]
      }
    },
    "require-dev": {
        "phpunit/phpunit": "^4.6"
    },
    "extra": {
        "branch-alias": {
          "dev-master": "1.0.x-dev"
        }
    }
}
<?php
/**
 * phpDocumentor
 *
 * PHP Version 5.5
 *
 * @copyright 2010-2015 Mike van Riel / Naenius (http://www.naenius.com)
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection;

/**
 * Interface for Api Elements
 */
interface Element
{
    /**
     * Returns the Fqsen of the element.
     *
     * @return Fqsen
     */
    public function getFqsen();

    /**
     * Returns the name of the element.
     *
     * @return string
     */
    public function getName();
}<?php
/**
 * phpDocumentor
 *
 * PHP Version 5.5
 *
 * @copyright 2010-2015 Mike van Riel / Naenius (http://www.naenius.com)
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */
namespace phpDocumentor\Reflection;

/**
 * Interface for project factories. A project factory shall convert a set of files
 * into an object implementing the Project interface.
 */
interface ProjectFactory
{
    /**
     * Creates a project from the set of files.
     *
     * @param string $name
     * @param File[] $files
     * @return Project
     */
    public function create($name, array $files);
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection;

/**
 * Interface for files processed by the ProjectFactory
 */
interface File
{
    /**
     * Returns the content of the file as a string.
     *
     * @return string
     */
    public function getContents();

    /**
     * Returns md5 hash of the file.
     *
     * @return string
     */
    public function md5();

    /**
     * Returns an relative path to the file.
     *
     * @return string
     */
    public function path();
}
<?php
/**
 * phpDocumentor
 *
 * PHP Version 5.5
 *
 * @copyright 2010-2015 Mike van Riel / Naenius (http://www.naenius.com)
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection;

/**
 * Interface for project. Since the definition of a project can be different per factory this interface will be small.
 */
interface Project
{
    /**
     * Returns the name of the project.
     *
     * @return string
     */
    public function getName();
}
<?php
/**
 * phpDocumentor
 *
 * PHP Version 5.5
 *
 * @copyright 2010-2015 Mike van Riel / Naenius (http://www.naenius.com)
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection;

/**
 * Value Object for Fqsen.
 *
 * @link https://github.com/phpDocumentor/fig-standards/blob/master/proposed/phpdoc-meta.md
 */
final class Fqsen
{
    /**
     * @var string full quallified class name
     */
    private $fqsen;

    /**
     * @var string name of the element without path.
     */
    private $name;

    /**
     * Initializes the object.
     *
     * @param string $fqsen
     *
     * @throws \InvalidArgumentException when $fqsen is not matching the format.
     */
    public function __construct($fqsen)
    {
        $matches = array();
        $result = preg_match(
            '/^\\\\([a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff\\\\]*)?(?:[:]{2}\\$?([a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*))?(?:\\(\\))?$/',
                $fqsen,
                $matches
        );

        if ($result === 0) {
            throw new \InvalidArgumentException(
                sprintf('"%s" is not a valid Fqsen.', $fqsen)
            );
        }

        $this->fqsen = $fqsen;

        if (isset($matches[2])) {
            $this->name = $matches[2];
        } else {
            $matches = explode('\\', $fqsen);
            $this->name = trim(end($matches), '()');
        }
    }

    /**
     * converts this class to string.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->fqsen;
    }

    /**
     * Returns the name of the element without path.
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection;

/**
 * The location where an element occurs within a file.
 */
final class Location
{
    /** @var int  */
    private $lineNumber = 0;

    /** @var int */
    private $columnNumber = 0;

    /**
     * Initializes the location for an element using its line number in the file and optionally the column number.
     *
     * @param int $lineNumber
     * @param int $columnNumber
     */
    public function __construct($lineNumber, $columnNumber = 0)
    {
        $this->lineNumber   = $lineNumber;
        $this->columnNumber = $columnNumber;
    }

    /**
     * Returns the line number that is covered by this location.
     *
     * @return integer
     */
    public function getLineNumber()
    {
        return $this->lineNumber;
    }

    /**
     * Returns the column number (character position on a line) for this location object.
     *
     * @return integer
     */
    public function getColumnNumber()
    {
        return $this->columnNumber;
    }
}
{
    "name":    "phpdocumentor/type-resolver",
    "type":    "library",
    "license": "MIT",
    "authors": [
        {"name": "Mike van Riel", "email": "me@mikevanriel.com"}
    ],
    "require": {
        "php": "^5.5 || ^7.0",
        "phpdocumentor/reflection-common": "^1.0"
    },
    "autoload": {
        "psr-4": {"phpDocumentor\\Reflection\\": ["src/"]}
    },
    "autoload-dev": {
        "psr-4": {"phpDocumentor\\Reflection\\": ["tests/unit"]}
    },
    "require-dev": {
        "phpunit/phpunit": "^5.2||^4.8.24",
        "mockery/mockery": "^0.9.4"
    },
    "extra": {
        "branch-alias": {
            "dev-master": "1.0.x-dev"
        }
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection;

use phpDocumentor\Reflection\Types\Array_;
use phpDocumentor\Reflection\Types\Compound;
use phpDocumentor\Reflection\Types\Context;
use phpDocumentor\Reflection\Types\Iterable_;
use phpDocumentor\Reflection\Types\Nullable;
use phpDocumentor\Reflection\Types\Object_;

final class TypeResolver
{
    /** @var string Definition of the ARRAY operator for types */
    const OPERATOR_ARRAY = '[]';

    /** @var string Definition of the NAMESPACE operator in PHP */
    const OPERATOR_NAMESPACE = '\\';

    /** @var string[] List of recognized keywords and unto which Value Object they map */
    private $keywords = array(
        'string' => Types\String_::class,
        'int' => Types\Integer::class,
        'integer' => Types\Integer::class,
        'bool' => Types\Boolean::class,
        'boolean' => Types\Boolean::class,
        'float' => Types\Float_::class,
        'double' => Types\Float_::class,
        'object' => Object_::class,
        'mixed' => Types\Mixed_::class,
        'array' => Array_::class,
        'resource' => Types\Resource_::class,
        'void' => Types\Void_::class,
        'null' => Types\Null_::class,
        'scalar' => Types\Scalar::class,
        'callback' => Types\Callable_::class,
        'callable' => Types\Callable_::class,
        'false' => Types\Boolean::class,
        'true' => Types\Boolean::class,
        'self' => Types\Self_::class,
        '$this' => Types\This::class,
        'static' => Types\Static_::class,
        'parent' => Types\Parent_::class,
        'iterable' => Iterable_::class,
    );

    /** @var FqsenResolver */
    private $fqsenResolver;

    /**
     * Initializes this TypeResolver with the means to create and resolve Fqsen objects.
     *
     * @param FqsenResolver $fqsenResolver
     */
    public function __construct(FqsenResolver $fqsenResolver = null)
    {
        $this->fqsenResolver = $fqsenResolver ?: new FqsenResolver();
    }

    /**
     * Analyzes the given type and returns the FQCN variant.
     *
     * When a type is provided this method checks whether it is not a keyword or
     * Fully Qualified Class Name. If so it will use the given namespace and
     * aliases to expand the type to a FQCN representation.
     *
     * This method only works as expected if the namespace and aliases are set;
     * no dynamic reflection is being performed here.
     *
     * @param string $type     The relative or absolute type.
     * @param Context $context
     *
     * @uses Context::getNamespace()        to determine with what to prefix the type name.
     * @uses Context::getNamespaceAliases() to check whether the first part of the relative type name should not be
     *     replaced with another namespace.
     *
     * @return Type|null
     */
    public function resolve($type, Context $context = null)
    {
        if (!is_string($type)) {
            throw new \InvalidArgumentException(
                'Attempted to resolve type but it appeared not to be a string, received: ' . var_export($type, true)
            );
        }

        $type = trim($type);
        if (!$type) {
            throw new \InvalidArgumentException('Attempted to resolve "' . $type . '" but it appears to be empty');
        }

        if ($context === null) {
            $context = new Context('');
        }

        switch (true) {
            case $this->isNullableType($type):
                return $this->resolveNullableType($type, $context);
            case $this->isKeyword($type):
                return $this->resolveKeyword($type);
            case ($this->isCompoundType($type)):
                return $this->resolveCompoundType($type, $context);
            case $this->isTypedArray($type):
                return $this->resolveTypedArray($type, $context);
            case $this->isFqsen($type):
                return $this->resolveTypedObject($type);
            case $this->isPartialStructuralElementName($type):
                return $this->resolveTypedObject($type, $context);
            // @codeCoverageIgnoreStart
            default:
                // I haven't got the foggiest how the logic would come here but added this as a defense.
                throw new \RuntimeException(
                    'Unable to resolve type "' . $type . '", there is no known method to resolve it'
                );
        }
        // @codeCoverageIgnoreEnd
    }

    /**
     * Adds a keyword to the list of Keywords and associates it with a specific Value Object.
     *
     * @param string $keyword
     * @param string $typeClassName
     *
     * @return void
     */
    public function addKeyword($keyword, $typeClassName)
    {
        if (!class_exists($typeClassName)) {
            throw new \InvalidArgumentException(
                'The Value Object that needs to be created with a keyword "' . $keyword . '" must be an existing class'
                . ' but we could not find the class ' . $typeClassName
            );
        }

        if (!in_array(Type::class, class_implements($typeClassName))) {
            throw new \InvalidArgumentException(
                'The class "' . $typeClassName . '" must implement the interface "phpDocumentor\Reflection\Type"'
            );
        }

        $this->keywords[$keyword] = $typeClassName;
    }

    /**
     * Detects whether the given type represents an array.
     *
     * @param string $type A relative or absolute type as defined in the phpDocumentor documentation.
     *
     * @return bool
     */
    private function isTypedArray($type)
    {
        return substr($type, -2) === self::OPERATOR_ARRAY;
    }

    /**
     * Detects whether the given type represents a PHPDoc keyword.
     *
     * @param string $type A relative or absolute type as defined in the phpDocumentor documentation.
     *
     * @return bool
     */
    private function isKeyword($type)
    {
        return in_array(strtolower($type), array_keys($this->keywords), true);
    }

    /**
     * Detects whether the given type represents a relative structural element name.
     *
     * @param string $type A relative or absolute type as defined in the phpDocumentor documentation.
     *
     * @return bool
     */
    private function isPartialStructuralElementName($type)
    {
        return ($type[0] !== self::OPERATOR_NAMESPACE) && !$this->isKeyword($type);
    }

    /**
     * Tests whether the given type is a Fully Qualified Structural Element Name.
     *
     * @param string $type
     *
     * @return bool
     */
    private function isFqsen($type)
    {
        return strpos($type, self::OPERATOR_NAMESPACE) === 0;
    }

    /**
     * Tests whether the given type is a compound type (i.e. `string|int`).
     *
     * @param string $type
     *
     * @return bool
     */
    private function isCompoundType($type)
    {
        return strpos($type, '|') !== false;
    }

    /**
     * Test whether the given type is a nullable type (i.e. `?string`)
     *
     * @param string $type
     *
     * @return bool
     */
    private function isNullableType($type)
    {
        return $type[0] === '?';
    }

    /**
     * Resolves the given typed array string (i.e. `string[]`) into an Array object with the right types set.
     *
     * @param string $type
     * @param Context $context
     *
     * @return Array_
     */
    private function resolveTypedArray($type, Context $context)
    {
        return new Array_($this->resolve(substr($type, 0, -2), $context));
    }

    /**
     * Resolves the given keyword (such as `string`) into a Type object representing that keyword.
     *
     * @param string $type
     *
     * @return Type
     */
    private function resolveKeyword($type)
    {
        $className = $this->keywords[strtolower($type)];

        return new $className();
    }

    /**
     * Resolves the given FQSEN string into an FQSEN object.
     *
     * @param string $type
     * @param Context|null $context
     *
     * @return Object_
     */
    private function resolveTypedObject($type, Context $context = null)
    {
        return new Object_($this->fqsenResolver->resolve($type, $context));
    }

    /**
     * Resolves a compound type (i.e. `string|int`) into the appropriate Type objects or FQSEN.
     *
     * @param string $type
     * @param Context $context
     *
     * @return Compound
     */
    private function resolveCompoundType($type, Context $context)
    {
        $types = [];

        foreach (explode('|', $type) as $part) {
            $types[] = $this->resolve($part, $context);
        }

        return new Compound($types);
    }

    /**
     * Resolve nullable types (i.e. `?string`) into a Nullable type wrapper
     *
     * @param string $type
     * @param Context $context
     *
     * @return Nullable
     */
    private function resolveNullableType($type, Context $context)
    {
        return new Nullable($this->resolve(ltrim($type, '?'), $context));
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing a Boolean type.
 */
final class Boolean implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return 'bool';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

final class Integer implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return 'int';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing the 'self' type.
 *
 * Self, as a Type, represents the class in which the associated element was defined.
 */
final class Self_ implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return 'self';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing the pseudo-type 'void'.
 *
 * Void is generally only used when working with return types as it signifies that the method intentionally does not
 * return any value.
 */
final class Void_ implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return 'void';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing a null value or type.
 */
final class Null_ implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return 'null';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing the 'resource' Type.
 */
final class Resource_ implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return 'resource';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing a Float.
 */
final class Float_ implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return 'float';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Type;

/**
 * Value Object representing an object.
 *
 * An object can be either typed or untyped. When an object is typed it means that it has an identifier, the FQSEN,
 * pointing to an element in PHP. Object types that are untyped do not refer to a specific class but represent objects
 * in general.
 */
final class Object_ implements Type
{
    /** @var Fqsen|null */
    private $fqsen;

    /**
     * Initializes this object with an optional FQSEN, if not provided this object is considered 'untyped'.
     *
     * @param Fqsen $fqsen
     * @throws \InvalidArgumentException when provided $fqsen is not a valid type.
     */
    public function __construct(Fqsen $fqsen = null)
    {
        if (strpos((string)$fqsen, '::') !== false || strpos((string)$fqsen, '()') !== false) {
            throw new \InvalidArgumentException(
                'Object types can only refer to a class, interface or trait but a method, function, constant or '
                . 'property was received: ' . (string)$fqsen
            );
        }

        $this->fqsen = $fqsen;
    }

    /**
     * Returns the FQSEN associated with this object.
     *
     * @return Fqsen|null
     */
    public function getFqsen()
    {
        return $this->fqsen;
    }

    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        if ($this->fqsen) {
            return (string)$this->fqsen;
        }

        return 'object';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing the type 'string'.
 */
final class String_ implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return 'string';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2017 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing a nullable type. The real type is wrapped.
 */
final class Nullable implements Type
{
    /**
     * @var Type
     */
    private $realType;

    /**
     * Initialises this nullable type using the real type embedded
     *
     * @param Type $realType
     */
    public function __construct(Type $realType)
    {
        $this->realType = $realType;
    }

    /**
     * Provide access to the actual type directly, if needed.
     *
     * @return Type
     */
    public function getActualType()
    {
        return $this->realType;
    }

    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return '?' . $this->realType->__toString();
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing a Callable type.
 */
final class Callable_ implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return 'callable';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2017 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing iterable type
 */
final class Iterable_ implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return 'iterable';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing the 'static' type.
 *
 * Self, as a Type, represents the class in which the associated element was called. This differs from self as self does
 * not take inheritance into account but static means that the return type is always that of the class of the called
 * element.
 *
 * See the documentation on late static binding in the PHP Documentation for more information on the difference between
 * static and self.
 */
final class Static_ implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return 'static';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing the 'parent' type.
 *
 * Parent, as a Type, represents the parent class of class in which the associated element was defined.
 */
final class Parent_ implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return 'parent';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing the 'scalar' pseudo-type, which is either a string, integer, float or boolean.
 */
final class Scalar implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return 'scalar';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

/**
 * Provides information about the Context in which the DocBlock occurs that receives this context.
 *
 * A DocBlock does not know of its own accord in which namespace it occurs and which namespace aliases are applicable
 * for the block of code in which it is in. This information is however necessary to resolve Class names in tags since
 * you can provide a short form or make use of namespace aliases.
 *
 * The phpDocumentor Reflection component knows how to create this class but if you use the DocBlock parser from your
 * own application it is possible to generate a Context class using the ContextFactory; this will analyze the file in
 * which an associated class resides for its namespace and imports.
 *
 * @see ContextFactory::createFromClassReflector()
 * @see ContextFactory::createForNamespace()
 */
final class Context
{
    /** @var string The current namespace. */
    private $namespace;

    /** @var array List of namespace aliases => Fully Qualified Namespace. */
    private $namespaceAliases;

    /**
     * Initializes the new context and normalizes all passed namespaces to be in Qualified Namespace Name (QNN)
     * format (without a preceding `\`).
     *
     * @param string $namespace The namespace where this DocBlock resides in.
     * @param array $namespaceAliases List of namespace aliases => Fully Qualified Namespace.
     */
    public function __construct($namespace, array $namespaceAliases = [])
    {
        $this->namespace = ('global' !== $namespace && 'default' !== $namespace)
            ? trim((string)$namespace, '\\')
            : '';

        foreach ($namespaceAliases as $alias => $fqnn) {
            if ($fqnn[0] === '\\') {
                $fqnn = substr($fqnn, 1);
            }
            if ($fqnn[strlen($fqnn) - 1] === '\\') {
                $fqnn = substr($fqnn, 0, -1);
            }

            $namespaceAliases[$alias] = $fqnn;
        }

        $this->namespaceAliases = $namespaceAliases;
    }

    /**
     * Returns the Qualified Namespace Name (thus without `\` in front) where the associated element is in.
     *
     * @return string
     */
    public function getNamespace()
    {
        return $this->namespace;
    }

    /**
     * Returns a list of Qualified Namespace Names (thus without `\` in front) that are imported, the keys represent
     * the alias for the imported Namespace.
     *
     * @return string[]
     */
    public function getNamespaceAliases()
    {
        return $this->namespaceAliases;
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

/**
 * Convenience class to create a Context for DocBlocks when not using the Reflection Component of phpDocumentor.
 *
 * For a DocBlock to be able to resolve types that use partial namespace names or rely on namespace imports we need to
 * provide a bit of context so that the DocBlock can read that and based on it decide how to resolve the types to
 * Fully Qualified names.
 *
 * @see Context for more information.
 */
final class ContextFactory
{
    /** The literal used at the end of a use statement. */
    const T_LITERAL_END_OF_USE = ';';

    /** The literal used between sets of use statements */
    const T_LITERAL_USE_SEPARATOR = ',';

    /**
     * Build a Context given a Class Reflection.
     *
     * @param \Reflector $reflector
     *
     * @see Context for more information on Contexts.
     *
     * @return Context
     */
    public function createFromReflector(\Reflector $reflector)
    {
        if (method_exists($reflector, 'getDeclaringClass')) {
            $reflector = $reflector->getDeclaringClass();
        }

        $fileName = $reflector->getFileName();
        $namespace = $reflector->getNamespaceName();

        if (file_exists($fileName)) {
            return $this->createForNamespace($namespace, file_get_contents($fileName));
        }

        return new Context($namespace, []);
    }

    /**
     * Build a Context for a namespace in the provided file contents.
     *
     * @param string $namespace It does not matter if a `\` precedes the namespace name, this method first normalizes.
     * @param string $fileContents the file's contents to retrieve the aliases from with the given namespace.
     *
     * @see Context for more information on Contexts.
     *
     * @return Context
     */
    public function createForNamespace($namespace, $fileContents)
    {
        $namespace = trim($namespace, '\\');
        $useStatements = [];
        $currentNamespace = '';
        $tokens = new \ArrayIterator(token_get_all($fileContents));

        while ($tokens->valid()) {
            switch ($tokens->current()[0]) {
                case T_NAMESPACE:
                    $currentNamespace = $this->parseNamespace($tokens);
                    break;
                case T_CLASS:
                    // Fast-forward the iterator through the class so that any
                    // T_USE tokens found within are skipped - these are not
                    // valid namespace use statements so should be ignored.
                    $braceLevel = 0;
                    $firstBraceFound = false;
                    while ($tokens->valid() && ($braceLevel > 0 || !$firstBraceFound)) {
                        if ($tokens->current() === '{'
                            || $tokens->current()[0] === T_CURLY_OPEN
                            || $tokens->current()[0] === T_DOLLAR_OPEN_CURLY_BRACES) {
                            if (!$firstBraceFound) {
                                $firstBraceFound = true;
                            }
                            $braceLevel++;
                        }

                        if ($tokens->current() === '}') {
                            $braceLevel--;
                        }
                        $tokens->next();
                    }
                    break;
                case T_USE:
                    if ($currentNamespace === $namespace) {
                        $useStatements = array_merge($useStatements, $this->parseUseStatement($tokens));
                    }
                    break;
            }
            $tokens->next();
        }

        return new Context($namespace, $useStatements);
    }

    /**
     * Deduce the name from tokens when we are at the T_NAMESPACE token.
     *
     * @param \ArrayIterator $tokens
     *
     * @return string
     */
    private function parseNamespace(\ArrayIterator $tokens)
    {
        // skip to the first string or namespace separator
        $this->skipToNextStringOrNamespaceSeparator($tokens);

        $name = '';
        while ($tokens->valid() && ($tokens->current()[0] === T_STRING || $tokens->current()[0] === T_NS_SEPARATOR)
        ) {
            $name .= $tokens->current()[1];
            $tokens->next();
        }

        return $name;
    }

    /**
     * Deduce the names of all imports when we are at the T_USE token.
     *
     * @param \ArrayIterator $tokens
     *
     * @return string[]
     */
    private function parseUseStatement(\ArrayIterator $tokens)
    {
        $uses = [];
        $continue = true;

        while ($continue) {
            $this->skipToNextStringOrNamespaceSeparator($tokens);

            list($alias, $fqnn) = $this->extractUseStatement($tokens);
            $uses[$alias] = $fqnn;
            if ($tokens->current()[0] === self::T_LITERAL_END_OF_USE) {
                $continue = false;
            }
        }

        return $uses;
    }

    /**
     * Fast-forwards the iterator as longs as we don't encounter a T_STRING or T_NS_SEPARATOR token.
     *
     * @param \ArrayIterator $tokens
     *
     * @return void
     */
    private function skipToNextStringOrNamespaceSeparator(\ArrayIterator $tokens)
    {
        while ($tokens->valid() && ($tokens->current()[0] !== T_STRING) && ($tokens->current()[0] !== T_NS_SEPARATOR)) {
            $tokens->next();
        }
    }

    /**
     * Deduce the namespace name and alias of an import when we are at the T_USE token or have not reached the end of
     * a USE statement yet.
     *
     * @param \ArrayIterator $tokens
     *
     * @return string
     */
    private function extractUseStatement(\ArrayIterator $tokens)
    {
        $result = [''];
        while ($tokens->valid()
            && ($tokens->current()[0] !== self::T_LITERAL_USE_SEPARATOR)
            && ($tokens->current()[0] !== self::T_LITERAL_END_OF_USE)
        ) {
            if ($tokens->current()[0] === T_AS) {
                $result[] = '';
            }
            if ($tokens->current()[0] === T_STRING || $tokens->current()[0] === T_NS_SEPARATOR) {
                $result[count($result) - 1] .= $tokens->current()[1];
            }
            $tokens->next();
        }

        if (count($result) == 1) {
            $backslashPos = strrpos($result[0], '\\');

            if (false !== $backslashPos) {
                $result[] = substr($result[0], $backslashPos + 1);
            } else {
                $result[] = $result[0];
            }
        }

        return array_reverse($result);
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use ArrayIterator;
use IteratorAggregate;
use phpDocumentor\Reflection\Type;

/**
 * Value Object representing a Compound Type.
 *
 * A Compound Type is not so much a special keyword or object reference but is a series of Types that are separated
 * using an OR operator (`|`). This combination of types signifies that whatever is associated with this compound type
 * may contain a value with any of the given types.
 */
final class Compound implements Type, IteratorAggregate
{
    /** @var Type[] */
    private $types;

    /**
     * Initializes a compound type (i.e. `string|int`) and tests if the provided types all implement the Type interface.
     *
     * @param Type[] $types
     * @throws \InvalidArgumentException when types are not all instance of Type
     */
    public function __construct(array $types)
    {
        foreach ($types as $type) {
            if (!$type instanceof Type) {
                throw new \InvalidArgumentException('A compound type can only have other types as elements');
            }
        }

        $this->types = $types;
    }

    /**
     * Returns the type at the given index.
     *
     * @param integer $index
     *
     * @return Type|null
     */
    public function get($index)
    {
        if (!$this->has($index)) {
            return null;
        }

        return $this->types[$index];
    }

    /**
     * Tests if this compound type has a type with the given index.
     *
     * @param integer $index
     *
     * @return bool
     */
    public function has($index)
    {
        return isset($this->types[$index]);
    }

    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return implode('|', $this->types);
    }

    /**
     * {@inheritdoc}
     */
    public function getIterator()
    {
        return new ArrayIterator($this->types);
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing the '$this' pseudo-type.
 *
 * $this, as a Type, represents the instance of the class associated with the element as it was called. $this is
 * commonly used when documenting fluent interfaces since it represents that the same object is returned.
 */
final class This implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return '$this';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Represents an array type as described in the PSR-5, the PHPDoc Standard.
 *
 * An array can be represented in two forms:
 *
 * 1. Untyped (`array`), where the key and value type is unknown and hence classified as 'Mixed_'.
 * 2. Types (`string[]`), where the value type is provided by preceding an opening and closing square bracket with a
 *    type name.
 */
final class Array_ implements Type
{
    /** @var Type */
    private $valueType;

    /** @var Type */
    private $keyType;

    /**
     * Initializes this representation of an array with the given Type or Fqsen.
     *
     * @param Type $valueType
     * @param Type $keyType
     */
    public function __construct(Type $valueType = null, Type $keyType = null)
    {
        if ($keyType === null) {
            $keyType = new Compound([ new String_(), new Integer() ]);
        }
        if ($valueType === null) {
            $valueType = new Mixed_();
        }

        $this->valueType = $valueType;
        $this->keyType = $keyType;
    }

    /**
     * Returns the type for the keys of this array.
     *
     * @return Type
     */
    public function getKeyType()
    {
        return $this->keyType;
    }

    /**
     * Returns the value for the keys of this array.
     *
     * @return Type
     */
    public function getValueType()
    {
        return $this->valueType;
    }

    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        if ($this->valueType instanceof Mixed_) {
            return 'array';
        }

        return $this->valueType . '[]';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\Types;

use phpDocumentor\Reflection\Type;

/**
 * Value Object representing an unknown, or mixed, type.
 */
final class Mixed_ implements Type
{
    /**
     * Returns a rendered output of the Type as it would be used in a DocBlock.
     *
     * @return string
     */
    public function __toString()
    {
        return 'mixed';
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection;

interface Type
{
    public function __toString();
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection;

use phpDocumentor\Reflection\Types\Context;

class FqsenResolver
{
    /** @var string Definition of the NAMESPACE operator in PHP */
    const OPERATOR_NAMESPACE = '\\';

    public function resolve($fqsen, Context $context = null)
    {
        if ($context === null) {
            $context = new Context('');
        }

        if ($this->isFqsen($fqsen)) {
            return new Fqsen($fqsen);
        }

        return $this->resolvePartialStructuralElementName($fqsen, $context);
    }

    /**
     * Tests whether the given type is a Fully Qualified Structural Element Name.
     *
     * @param string $type
     *
     * @return bool
     */
    private function isFqsen($type)
    {
        return strpos($type, self::OPERATOR_NAMESPACE) === 0;
    }

    /**
     * Resolves a partial Structural Element Name (i.e. `Reflection\DocBlock`) to its FQSEN representation
     * (i.e. `\phpDocumentor\Reflection\DocBlock`) based on the Namespace and aliases mentioned in the Context.
     *
     * @param string $type
     * @param Context $context
     *
     * @return Fqsen
     * @throws \InvalidArgumentException when type is not a valid FQSEN.
     */
    private function resolvePartialStructuralElementName($type, Context $context)
    {
        $typeParts = explode(self::OPERATOR_NAMESPACE, $type, 2);

        $namespaceAliases = $context->getNamespaceAliases();

        // if the first segment is not an alias; prepend namespace name and return
        if (!isset($namespaceAliases[$typeParts[0]])) {
            $namespace = $context->getNamespace();
            if ('' !== $namespace) {
                $namespace .= self::OPERATOR_NAMESPACE;
            }

            return new Fqsen(self::OPERATOR_NAMESPACE . $namespace . $type);
        }

        $typeParts[0] = $namespaceAliases[$typeParts[0]];

        return new Fqsen(self::OPERATOR_NAMESPACE . implode(self::OPERATOR_NAMESPACE, $typeParts));
    }
}
{
    "name":    "phpdocumentor/reflection-docblock",
    "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
    "type":    "library",
    "license": "MIT",
    "authors": [
        {
            "name": "Mike van Riel",
            "email": "me@mikevanriel.com"
        }
    ],
    "require": {
        "php": "^7.0",
        "phpdocumentor/reflection-common": "^1.0.0",
        "phpdocumentor/type-resolver": "^0.4.0",
        "webmozart/assert": "^1.0"
    },
    "autoload": {
        "psr-4": {"phpDocumentor\\Reflection\\": ["src/"]}
    },
    "autoload-dev": {
        "psr-4": {"phpDocumentor\\Reflection\\": ["tests/unit"]}
    },
    "require-dev": {
        "mockery/mockery": "^1.0",
        "phpunit/phpunit": "^6.4",
        "doctrine/instantiator": "~1.0.5"
    },
    "extra": {
        "branch-alias": {
            "dev-master": "4.x-dev"
        }
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection;

use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\DocBlock\StandardTagFactory;
use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\DocBlock\TagFactory;
use Webmozart\Assert\Assert;

final class DocBlockFactory implements DocBlockFactoryInterface
{
    /** @var DocBlock\DescriptionFactory */
    private $descriptionFactory;

    /** @var DocBlock\TagFactory */
    private $tagFactory;

    /**
     * Initializes this factory with the required subcontractors.
     *
     * @param DescriptionFactory $descriptionFactory
     * @param TagFactory         $tagFactory
     */
    public function __construct(DescriptionFactory $descriptionFactory, TagFactory $tagFactory)
    {
        $this->descriptionFactory = $descriptionFactory;
        $this->tagFactory = $tagFactory;
    }

    /**
     * Factory method for easy instantiation.
     *
     * @param string[] $additionalTags
     *
     * @return DocBlockFactory
     */
    public static function createInstance(array $additionalTags = [])
    {
        $fqsenResolver = new FqsenResolver();
        $tagFactory = new StandardTagFactory($fqsenResolver);
        $descriptionFactory = new DescriptionFactory($tagFactory);

        $tagFactory->addService($descriptionFactory);
        $tagFactory->addService(new TypeResolver($fqsenResolver));

        $docBlockFactory = new self($descriptionFactory, $tagFactory);
        foreach ($additionalTags as $tagName => $tagHandler) {
            $docBlockFactory->registerTagHandler($tagName, $tagHandler);
        }

        return $docBlockFactory;
    }

    /**
     * @param object|string $docblock A string containing the DocBlock to parse or an object supporting the
     *                                getDocComment method (such as a ReflectionClass object).
     * @param Types\Context $context
     * @param Location      $location
     *
     * @return DocBlock
     */
    public function create($docblock, Types\Context $context = null, Location $location = null)
    {
        if (is_object($docblock)) {
            if (!method_exists($docblock, 'getDocComment')) {
                $exceptionMessage = 'Invalid object passed; the given object must support the getDocComment method';
                throw new \InvalidArgumentException($exceptionMessage);
            }

            $docblock = $docblock->getDocComment();
        }

        Assert::stringNotEmpty($docblock);

        if ($context === null) {
            $context = new Types\Context('');
        }

        $parts = $this->splitDocBlock($this->stripDocComment($docblock));
        list($templateMarker, $summary, $description, $tags) = $parts;

        return new DocBlock(
            $summary,
            $description ? $this->descriptionFactory->create($description, $context) : null,
            array_filter($this->parseTagBlock($tags, $context), function ($tag) {
                return $tag instanceof Tag;
            }),
            $context,
            $location,
            $templateMarker === '#@+',
            $templateMarker === '#@-'
        );
    }

    public function registerTagHandler($tagName, $handler)
    {
        $this->tagFactory->registerTagHandler($tagName, $handler);
    }

    /**
     * Strips the asterisks from the DocBlock comment.
     *
     * @param string $comment String containing the comment text.
     *
     * @return string
     */
    private function stripDocComment($comment)
    {
        $comment = trim(preg_replace('#[ \t]*(?:\/\*\*|\*\/|\*)?[ \t]{0,1}(.*)?#u', '$1', $comment));

        // reg ex above is not able to remove */ from a single line docblock
        if (substr($comment, -2) === '*/') {
            $comment = trim(substr($comment, 0, -2));
        }

        return str_replace(["\r\n", "\r"], "\n", $comment);
    }

    /**
     * Splits the DocBlock into a template marker, summary, description and block of tags.
     *
     * @param string $comment Comment to split into the sub-parts.
     *
     * @author Richard van Velzen (@_richardJ) Special thanks to Richard for the regex responsible for the split.
     * @author Mike van Riel <me@mikevanriel.com> for extending the regex with template marker support.
     *
     * @return string[] containing the template marker (if any), summary, description and a string containing the tags.
     */
    private function splitDocBlock($comment)
    {
        // Performance improvement cheat: if the first character is an @ then only tags are in this DocBlock. This
        // method does not split tags so we return this verbatim as the fourth result (tags). This saves us the
        // performance impact of running a regular expression
        if (strpos($comment, '@') === 0) {
            return ['', '', '', $comment];
        }

        // clears all extra horizontal whitespace from the line endings to prevent parsing issues
        $comment = preg_replace('/\h*$/Sum', '', $comment);

        /*
         * Splits the docblock into a template marker, summary, description and tags section.
         *
         * - The template marker is empty, #@+ or #@- if the DocBlock starts with either of those (a newline may
         *   occur after it and will be stripped).
         * - The short description is started from the first character until a dot is encountered followed by a
         *   newline OR two consecutive newlines (horizontal whitespace is taken into account to consider spacing
         *   errors). This is optional.
         * - The long description, any character until a new line is encountered followed by an @ and word
         *   characters (a tag). This is optional.
         * - Tags; the remaining characters
         *
         * Big thanks to RichardJ for contributing this Regular Expression
         */
        preg_match(
            '/
            \A
            # 1. Extract the template marker
            (?:(\#\@\+|\#\@\-)\n?)?

            # 2. Extract the summary
            (?:
              (?! @\pL ) # The summary may not start with an @
              (
                [^\n.]+
                (?:
                  (?! \. \n | \n{2} )     # End summary upon a dot followed by newline or two newlines
                  [\n.] (?! [ \t]* @\pL ) # End summary when an @ is found as first character on a new line
                  [^\n.]+                 # Include anything else
                )*
                \.?
              )?
            )

            # 3. Extract the description
            (?:
              \s*        # Some form of whitespace _must_ precede a description because a summary must be there
              (?! @\pL ) # The description may not start with an @
              (
                [^\n]+
                (?: \n+
                  (?! [ \t]* @\pL ) # End description when an @ is found as first character on a new line
                  [^\n]+            # Include anything else
                )*
              )
            )?

            # 4. Extract the tags (anything that follows)
            (\s+ [\s\S]*)? # everything that follows
            /ux',
            $comment,
            $matches
        );
        array_shift($matches);

        while (count($matches) < 4) {
            $matches[] = '';
        }

        return $matches;
    }

    /**
     * Creates the tag objects.
     *
     * @param string $tags Tag block to parse.
     * @param Types\Context $context Context of the parsed Tag
     *
     * @return DocBlock\Tag[]
     */
    private function parseTagBlock($tags, Types\Context $context)
    {
        $tags = $this->filterTagBlock($tags);
        if (!$tags) {
            return [];
        }

        $result = $this->splitTagBlockIntoTagLines($tags);
        foreach ($result as $key => $tagLine) {
            $result[$key] = $this->tagFactory->create(trim($tagLine), $context);
        }

        return $result;
    }

    /**
     * @param string $tags
     *
     * @return string[]
     */
    private function splitTagBlockIntoTagLines($tags)
    {
        $result = [];
        foreach (explode("\n", $tags) as $tag_line) {
            if (isset($tag_line[0]) && ($tag_line[0] === '@')) {
                $result[] = $tag_line;
            } else {
                $result[count($result) - 1] .= "\n" . $tag_line;
            }
        }

        return $result;
    }

    /**
     * @param $tags
     * @return string
     */
    private function filterTagBlock($tags)
    {
        $tags = trim($tags);
        if (!$tags) {
            return null;
        }

        if ('@' !== $tags[0]) {
            // @codeCoverageIgnoreStart
            // Can't simulate this; this only happens if there is an error with the parsing of the DocBlock that
            // we didn't foresee.
            throw new \LogicException('A tag block started with text instead of an at-sign(@): ' . $tags);
            // @codeCoverageIgnoreEnd
        }

        return $tags;
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock;

use phpDocumentor\Reflection\DocBlock;
use Webmozart\Assert\Assert;

/**
 * Converts a DocBlock back from an object to a complete DocComment including Asterisks.
 */
class Serializer
{
    /** @var string The string to indent the comment with. */
    protected $indentString = ' ';

    /** @var int The number of times the indent string is repeated. */
    protected $indent = 0;

    /** @var bool Whether to indent the first line with the given indent amount and string. */
    protected $isFirstLineIndented = true;

    /** @var int|null The max length of a line. */
    protected $lineLength = null;

    /** @var DocBlock\Tags\Formatter A custom tag formatter. */
    protected $tagFormatter = null;

    /**
     * Create a Serializer instance.
     *
     * @param int $indent The number of times the indent string is repeated.
     * @param string   $indentString    The string to indent the comment with.
     * @param bool     $indentFirstLine Whether to indent the first line.
     * @param int|null $lineLength The max length of a line or NULL to disable line wrapping.
     * @param DocBlock\Tags\Formatter $tagFormatter A custom tag formatter, defaults to PassthroughFormatter.
     */
    public function __construct($indent = 0, $indentString = ' ', $indentFirstLine = true, $lineLength = null, $tagFormatter = null)
    {
        Assert::integer($indent);
        Assert::string($indentString);
        Assert::boolean($indentFirstLine);
        Assert::nullOrInteger($lineLength);
        Assert::nullOrIsInstanceOf($tagFormatter, 'phpDocumentor\Reflection\DocBlock\Tags\Formatter');

        $this->indent = $indent;
        $this->indentString = $indentString;
        $this->isFirstLineIndented = $indentFirstLine;
        $this->lineLength = $lineLength;
        $this->tagFormatter = $tagFormatter ?: new DocBlock\Tags\Formatter\PassthroughFormatter();
    }

    /**
     * Generate a DocBlock comment.
     *
     * @param DocBlock $docblock The DocBlock to serialize.
     *
     * @return string The serialized doc block.
     */
    public function getDocComment(DocBlock $docblock)
    {
        $indent = str_repeat($this->indentString, $this->indent);
        $firstIndent = $this->isFirstLineIndented ? $indent : '';
        // 3 === strlen(' * ')
        $wrapLength = $this->lineLength ? $this->lineLength - strlen($indent) - 3 : null;

        $text = $this->removeTrailingSpaces(
            $indent,
            $this->addAsterisksForEachLine(
                $indent,
                $this->getSummaryAndDescriptionTextBlock($docblock, $wrapLength)
            )
        );

        $comment = "{$firstIndent}/**\n";
        if ($text) {
            $comment .= "{$indent} * {$text}\n";
            $comment .= "{$indent} *\n";
        }

        $comment = $this->addTagBlock($docblock, $wrapLength, $indent, $comment);
        $comment .= $indent . ' */';

        return $comment;
    }

    /**
     * @param $indent
     * @param $text
     * @return mixed
     */
    private function removeTrailingSpaces($indent, $text)
    {
        return str_replace("\n{$indent} * \n", "\n{$indent} *\n", $text);
    }

    /**
     * @param $indent
     * @param $text
     * @return mixed
     */
    private function addAsterisksForEachLine($indent, $text)
    {
        return str_replace("\n", "\n{$indent} * ", $text);
    }

    /**
     * @param DocBlock $docblock
     * @param $wrapLength
     * @return string
     */
    private function getSummaryAndDescriptionTextBlock(DocBlock $docblock, $wrapLength)
    {
        $text = $docblock->getSummary() . ((string)$docblock->getDescription() ? "\n\n" . $docblock->getDescription()
                : '');
        if ($wrapLength !== null) {
            $text = wordwrap($text, $wrapLength);
            return $text;
        }

        return $text;
    }

    /**
     * @param DocBlock $docblock
     * @param $wrapLength
     * @param $indent
     * @param $comment
     * @return string
     */
    private function addTagBlock(DocBlock $docblock, $wrapLength, $indent, $comment)
    {
        foreach ($docblock->getTags() as $tag) {
            $tagText = $this->tagFormatter->format($tag);
            if ($wrapLength !== null) {
                $tagText = wordwrap($tagText, $wrapLength);
            }

            $tagText = str_replace("\n", "\n{$indent} * ", $tagText);

            $comment .= "{$indent} * {$tagText}\n";
        }

        return $comment;
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock;

use phpDocumentor\Reflection\DocBlock\Tags\Example;

/**
 * Class used to find an example file's location based on a given ExampleDescriptor.
 */
class ExampleFinder
{
    /** @var string */
    private $sourceDirectory = '';

    /** @var string[] */
    private $exampleDirectories = [];

    /**
     * Attempts to find the example contents for the given descriptor.
     *
     * @param Example $example
     *
     * @return string
     */
    public function find(Example $example)
    {
        $filename = $example->getFilePath();

        $file = $this->getExampleFileContents($filename);
        if (!$file) {
            return "** File not found : {$filename} **";
        }

        return implode('', array_slice($file, $example->getStartingLine() - 1, $example->getLineCount()));
    }

    /**
     * Registers the project's root directory where an 'examples' folder can be expected.
     *
     * @param string $directory
     *
     * @return void
     */
    public function setSourceDirectory($directory = '')
    {
        $this->sourceDirectory = $directory;
    }

    /**
     * Returns the project's root directory where an 'examples' folder can be expected.
     *
     * @return string
     */
    public function getSourceDirectory()
    {
        return $this->sourceDirectory;
    }

    /**
     * Registers a series of directories that may contain examples.
     *
     * @param string[] $directories
     */
    public function setExampleDirectories(array $directories)
    {
        $this->exampleDirectories = $directories;
    }

    /**
     * Returns a series of directories that may contain examples.
     *
     * @return string[]
     */
    public function getExampleDirectories()
    {
        return $this->exampleDirectories;
    }

    /**
     * Attempts to find the requested example file and returns its contents or null if no file was found.
     *
     * This method will try several methods in search of the given example file, the first one it encounters is
     * returned:
     *
     * 1. Iterates through all examples folders for the given filename
     * 2. Checks the source folder for the given filename
     * 3. Checks the 'examples' folder in the current working directory for examples
     * 4. Checks the path relative to the current working directory for the given filename
     *
     * @param string $filename
     *
     * @return string|null
     */
    private function getExampleFileContents($filename)
    {
        $normalizedPath = null;

        foreach ($this->exampleDirectories as $directory) {
            $exampleFileFromConfig = $this->constructExamplePath($directory, $filename);
            if (is_readable($exampleFileFromConfig)) {
                $normalizedPath = $exampleFileFromConfig;
                break;
            }
        }

        if (!$normalizedPath) {
            if (is_readable($this->getExamplePathFromSource($filename))) {
                $normalizedPath = $this->getExamplePathFromSource($filename);
            } elseif (is_readable($this->getExamplePathFromExampleDirectory($filename))) {
                $normalizedPath = $this->getExamplePathFromExampleDirectory($filename);
            } elseif (is_readable($filename)) {
                $normalizedPath = $filename;
            }
        }

        return $normalizedPath && is_readable($normalizedPath) ? file($normalizedPath) : null;
    }

    /**
     * Get example filepath based on the example directory inside your project.
     *
     * @param string $file
     *
     * @return string
     */
    private function getExamplePathFromExampleDirectory($file)
    {
        return getcwd() . DIRECTORY_SEPARATOR . 'examples' . DIRECTORY_SEPARATOR . $file;
    }

    /**
     * Returns a path to the example file in the given directory..
     *
     * @param string $directory
     * @param string $file
     *
     * @return string
     */
    private function constructExamplePath($directory, $file)
    {
        return rtrim($directory, '\\/') . DIRECTORY_SEPARATOR . $file;
    }

    /**
     * Get example filepath based on sourcecode.
     *
     * @param string $file
     *
     * @return string
     */
    private function getExamplePathFromSource($file)
    {
        return sprintf(
            '%s%s%s',
            trim($this->getSourceDirectory(), '\\/'),
            DIRECTORY_SEPARATOR,
            trim($file, '"')
        );
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock;

use phpDocumentor\Reflection\DocBlock\Tags\Formatter;

interface Tag
{
    public function getName();

    public static function create($body);

    public function render(Formatter $formatter = null);

    public function __toString();
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock;

use phpDocumentor\Reflection\Types\Context as TypeContext;

interface TagFactory
{
    /**
     * Adds a parameter to the service locator that can be injected in a tag's factory method.
     *
     * When calling a tag's "create" method we always check the signature for dependencies to inject. One way is to
     * typehint a parameter in the signature so that we can use that interface or class name to inject a dependency
     * (see {@see addService()} for more information on that).
     *
     * Another way is to check the name of the argument against the names in the Service Locator. With this method
     * you can add a variable that will be inserted when a tag's create method is not typehinted and has a matching
     * name.
     *
     * Be aware that there are two reserved names:
     *
     * - name, representing the name of the tag.
     * - body, representing the complete body of the tag.
     *
     * These parameters are injected at the last moment and will override any existing parameter with those names.
     *
     * @param string $name
     * @param mixed  $value
     *
     * @return void
     */
    public function addParameter($name, $value);

    /**
     * Registers a service with the Service Locator using the FQCN of the class or the alias, if provided.
     *
     * When calling a tag's "create" method we always check the signature for dependencies to inject. If a parameter
     * has a typehint then the ServiceLocator is queried to see if a Service is registered for that typehint.
     *
     * Because interfaces are regularly used as type-hints this method provides an alias parameter; if the FQCN of the
     * interface is passed as alias then every time that interface is requested the provided service will be returned.
     *
     * @param object $service
     * @param string $alias
     *
     * @return void
     */
    public function addService($service);

    /**
     * Factory method responsible for instantiating the correct sub type.
     *
     * @param string $tagLine The text for this tag, including description.
     * @param TypeContext $context
     *
     * @throws \InvalidArgumentException if an invalid tag line was presented.
     *
     * @return Tag A new tag object.
     */
    public function create($tagLine, TypeContext $context = null);

    /**
     * Registers a handler for tags.
     *
     * If you want to use your own tags then you can use this method to instruct the TagFactory to register the name
     * of a tag with the FQCN of a 'Tag Handler'. The Tag handler should implement the {@see Tag} interface (and thus
     * the create method).
     *
     * @param string $tagName Name of tag to register a handler for. When registering a namespaced tag, the full
     *                        name, along with a prefixing slash MUST be provided.
     * @param string $handler FQCN of handler.
     *
     * @throws \InvalidArgumentException if the tag name is not a string
     * @throws \InvalidArgumentException if the tag name is namespaced (contains backslashes) but does not start with
     *     a backslash
     * @throws \InvalidArgumentException if the handler is not a string
     * @throws \InvalidArgumentException if the handler is not an existing class
     * @throws \InvalidArgumentException if the handler does not implement the {@see Tag} interface
     *
     * @return void
     */
    public function registerTagHandler($tagName, $handler);
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock;

use phpDocumentor\Reflection\Types\Context as TypeContext;

/**
 * Creates a new Description object given a body of text.
 *
 * Descriptions in phpDocumentor are somewhat complex entities as they can contain one or more tags inside their
 * body that can be replaced with a readable output. The replacing is done by passing a Formatter object to the
 * Description object's `render` method.
 *
 * In addition to the above does a Description support two types of escape sequences:
 *
 * 1. `{@}` to escape the `@` character to prevent it from being interpreted as part of a tag, i.e. `{{@}link}`
 * 2. `{}` to escape the `}` character, this can be used if you want to use the `}` character in the description
 *    of an inline tag.
 *
 * If a body consists of multiple lines then this factory will also remove any superfluous whitespace at the beginning
 * of each line while maintaining any indentation that is used. This will prevent formatting parsers from tripping
 * over unexpected spaces as can be observed with tag descriptions.
 */
class DescriptionFactory
{
    /** @var TagFactory */
    private $tagFactory;

    /**
     * Initializes this factory with the means to construct (inline) tags.
     *
     * @param TagFactory $tagFactory
     */
    public function __construct(TagFactory $tagFactory)
    {
        $this->tagFactory = $tagFactory;
    }

    /**
     * Returns the parsed text of this description.
     *
     * @param string $contents
     * @param TypeContext $context
     *
     * @return Description
     */
    public function create($contents, TypeContext $context = null)
    {
        list($text, $tags) = $this->parse($this->lex($contents), $context);

        return new Description($text, $tags);
    }

    /**
     * Strips the contents from superfluous whitespace and splits the description into a series of tokens.
     *
     * @param string $contents
     *
     * @return string[] A series of tokens of which the description text is composed.
     */
    private function lex($contents)
    {
        $contents = $this->removeSuperfluousStartingWhitespace($contents);

        // performance optimalization; if there is no inline tag, don't bother splitting it up.
        if (strpos($contents, '{@') === false) {
            return [$contents];
        }

        return preg_split(
            '/\{
                # "{@}" is not a valid inline tag. This ensures that we do not treat it as one, but treat it literally.
                (?!@\})
                # We want to capture the whole tag line, but without the inline tag delimiters.
                (\@
                    # Match everything up to the next delimiter.
                    [^{}]*
                    # Nested inline tag content should not be captured, or it will appear in the result separately.
                    (?:
                        # Match nested inline tags.
                        (?:
                            # Because we did not catch the tag delimiters earlier, we must be explicit with them here.
                            # Notice that this also matches "{}", as a way to later introduce it as an escape sequence.
                            \{(?1)?\}
                            |
                            # Make sure we match hanging "{".
                            \{
                        )
                        # Match content after the nested inline tag.
                        [^{}]*
                    )* # If there are more inline tags, match them as well. We use "*" since there may not be any
                       # nested inline tags.
                )
            \}/Sux',
            $contents,
            null,
            PREG_SPLIT_DELIM_CAPTURE
        );
    }

    /**
     * Parses the stream of tokens in to a new set of tokens containing Tags.
     *
     * @param string[] $tokens
     * @param TypeContext $context
     *
     * @return string[]|Tag[]
     */
    private function parse($tokens, TypeContext $context)
    {
        $count = count($tokens);
        $tagCount = 0;
        $tags  = [];

        for ($i = 1; $i < $count; $i += 2) {
            $tags[] = $this->tagFactory->create($tokens[$i], $context);
            $tokens[$i] = '%' . ++$tagCount . '$s';
        }

        //In order to allow "literal" inline tags, the otherwise invalid
        //sequence "{@}" is changed to "@", and "{}" is changed to "}".
        //"%" is escaped to "%%" because of vsprintf.
        //See unit tests for examples.
        for ($i = 0; $i < $count; $i += 2) {
            $tokens[$i] = str_replace(['{@}', '{}', '%'], ['@', '}', '%%'], $tokens[$i]);
        }

        return [implode('', $tokens), $tags];
    }

    /**
     * Removes the superfluous from a multi-line description.
     *
     * When a description has more than one line then it can happen that the second and subsequent lines have an
     * additional indentation. This is commonly in use with tags like this:
     *
     *     {@}since 1.1.0 This is an example
     *         description where we have an
     *         indentation in the second and
     *         subsequent lines.
     *
     * If we do not normalize the indentation then we have superfluous whitespace on the second and subsequent
     * lines and this may cause rendering issues when, for example, using a Markdown converter.
     *
     * @param string $contents
     *
     * @return string
     */
    private function removeSuperfluousStartingWhitespace($contents)
    {
        $lines = explode("\n", $contents);

        // if there is only one line then we don't have lines with superfluous whitespace and
        // can use the contents as-is
        if (count($lines) <= 1) {
            return $contents;
        }

        // determine how many whitespace characters need to be stripped
        $startingSpaceCount = 9999999;
        for ($i = 1; $i < count($lines); $i++) {
            // lines with a no length do not count as they are not indented at all
            if (strlen(trim($lines[$i])) === 0) {
                continue;
            }

            // determine the number of prefixing spaces by checking the difference in line length before and after
            // an ltrim
            $startingSpaceCount = min($startingSpaceCount, strlen($lines[$i]) - strlen(ltrim($lines[$i])));
        }

        // strip the number of spaces from each line
        if ($startingSpaceCount > 0) {
            for ($i = 1; $i < count($lines); $i++) {
                $lines[$i] = substr($lines[$i], $startingSpaceCount);
            }
        }

        return implode("\n", $lines);
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for a {@}since tag in a Docblock.
 */
final class Since extends BaseTag implements Factory\StaticMethod
{
    protected $name = 'since';

    /**
     * PCRE regular expression matching a version vector.
     * Assumes the "x" modifier.
     */
    const REGEX_VECTOR = '(?:
        # Normal release vectors.
        \d\S*
        |
        # VCS version vectors. Per PHPCS, they are expected to
        # follow the form of the VCS name, followed by ":", followed
        # by the version vector itself.
        # By convention, popular VCSes like CVS, SVN and GIT use "$"
        # around the actual version vector.
        [^\s\:]+\:\s*\$[^\$]+\$
    )';

    /** @var string The version vector. */
    private $version = '';

    public function __construct($version = null, Description $description = null)
    {
        Assert::nullOrStringNotEmpty($version);

        $this->version     = $version;
        $this->description = $description;
    }

    /**
     * @return static
     */
    public static function create($body, DescriptionFactory $descriptionFactory = null, TypeContext $context = null)
    {
        Assert::nullOrString($body);
        if (empty($body)) {
            return new static();
        }

        $matches = [];
        if (! preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) {
            return null;
        }

        return new static(
            $matches[1],
            $descriptionFactory->create(isset($matches[2]) ? $matches[2] : '', $context)
        );
    }

    /**
     * Gets the version section of the tag.
     *
     * @return string
     */
    public function getVersion()
    {
        return $this->version;
    }

    /**
     * Returns a string representation for this tag.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->version . ($this->description ? ' ' . $this->description->render() : '');
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for a {@}property-write tag in a Docblock.
 */
class PropertyWrite extends BaseTag implements Factory\StaticMethod
{
    /** @var string */
    protected $name = 'property-write';

    /** @var Type */
    private $type;

    /** @var string */
    protected $variableName = '';

    /**
     * @param string      $variableName
     * @param Type        $type
     * @param Description $description
     */
    public function __construct($variableName, Type $type = null, Description $description = null)
    {
        Assert::string($variableName);

        $this->variableName = $variableName;
        $this->type = $type;
        $this->description = $description;
    }

    /**
     * {@inheritdoc}
     */
    public static function create(
        $body,
        TypeResolver $typeResolver = null,
        DescriptionFactory $descriptionFactory = null,
        TypeContext $context = null
    ) {
        Assert::stringNotEmpty($body);
        Assert::allNotNull([$typeResolver, $descriptionFactory]);

        $parts = preg_split('/(\s+)/Su', $body, 3, PREG_SPLIT_DELIM_CAPTURE);
        $type = null;
        $variableName = '';

        // if the first item that is encountered is not a variable; it is a type
        if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] !== '$')) {
            $type = $typeResolver->resolve(array_shift($parts), $context);
            array_shift($parts);
        }

        // if the next item starts with a $ or ...$ it must be the variable name
        if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] === '$')) {
            $variableName = array_shift($parts);
            array_shift($parts);

            if (substr($variableName, 0, 1) === '$') {
                $variableName = substr($variableName, 1);
            }
        }

        $description = $descriptionFactory->create(implode('', $parts), $context);

        return new static($variableName, $type, $description);
    }

    /**
     * Returns the variable's name.
     *
     * @return string
     */
    public function getVariableName()
    {
        return $this->variableName;
    }

    /**
     * Returns the variable's type or null if unknown.
     *
     * @return Type|null
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * Returns a string representation for this tag.
     *
     * @return string
     */
    public function __toString()
    {
        return ($this->type ? $this->type . ' ' : '')
        . '$' . $this->variableName
        . ($this->description ? ' ' . $this->description : '');
    }
}
<?php
/**
 * phpDocumentor
 *
 * PHP Version 5.3
 *
 * @author    Ben Selby <benmatselby@gmail.com>
 * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com)
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for a @link tag in a Docblock.
 */
final class Link extends BaseTag implements Factory\StaticMethod
{
    protected $name = 'link';

    /** @var string */
    private $link = '';

    /**
     * Initializes a link to a URL.
     *
     * @param string      $link
     * @param Description $description
     */
    public function __construct($link, Description $description = null)
    {
        Assert::string($link);

        $this->link = $link;
        $this->description = $description;
    }

    /**
     * {@inheritdoc}
     */
    public static function create($body, DescriptionFactory $descriptionFactory = null, TypeContext $context = null)
    {
        Assert::string($body);
        Assert::notNull($descriptionFactory);

        $parts = preg_split('/\s+/Su', $body, 2);
        $description = isset($parts[1]) ? $descriptionFactory->create($parts[1], $context) : null;

        return new static($parts[0], $description);
    }

    /**
    * Gets the link
    *
    * @return string
    */
    public function getLink()
    {
        return $this->link;
    }

    /**
     * Returns a string representation for this tag.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->link . ($this->description ? ' ' . $this->description->render() : '');
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock\Description;

/**
 * Parses a tag definition for a DocBlock.
 */
abstract class BaseTag implements DocBlock\Tag
{
    /** @var string Name of the tag */
    protected $name = '';

    /** @var Description|null Description of the tag. */
    protected $description;

    /**
     * Gets the name of this tag.
     *
     * @return string The name of this tag.
     */
    public function getName()
    {
        return $this->name;
    }

    public function getDescription()
    {
        return $this->description;
    }

    public function render(Formatter $formatter = null)
    {
        if ($formatter === null) {
            $formatter = new Formatter\PassthroughFormatter();
        }

        return $formatter->format($this);
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for a {@}var tag in a Docblock.
 */
class Var_ extends BaseTag implements Factory\StaticMethod
{
    /** @var string */
    protected $name = 'var';

    /** @var Type */
    private $type;

    /** @var string */
    protected $variableName = '';

    /**
     * @param string      $variableName
     * @param Type        $type
     * @param Description $description
     */
    public function __construct($variableName, Type $type = null, Description $description = null)
    {
        Assert::string($variableName);

        $this->variableName = $variableName;
        $this->type         = $type;
        $this->description  = $description;
    }

    /**
     * {@inheritdoc}
     */
    public static function create(
        $body,
        TypeResolver $typeResolver = null,
        DescriptionFactory $descriptionFactory = null,
        TypeContext $context = null
    ) {
        Assert::stringNotEmpty($body);
        Assert::allNotNull([$typeResolver, $descriptionFactory]);

        $parts        = preg_split('/(\s+)/Su', $body, 3, PREG_SPLIT_DELIM_CAPTURE);
        $type         = null;
        $variableName = '';

        // if the first item that is encountered is not a variable; it is a type
        if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] !== '$')) {
            $type = $typeResolver->resolve(array_shift($parts), $context);
            array_shift($parts);
        }

        // if the next item starts with a $ or ...$ it must be the variable name
        if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] === '$')) {
            $variableName = array_shift($parts);
            array_shift($parts);

            if (substr($variableName, 0, 1) === '$') {
                $variableName = substr($variableName, 1);
            }
        }

        $description = $descriptionFactory->create(implode('', $parts), $context);

        return new static($variableName, $type, $description);
    }

    /**
     * Returns the variable's name.
     *
     * @return string
     */
    public function getVariableName()
    {
        return $this->variableName;
    }

    /**
     * Returns the variable's type or null if unknown.
     *
     * @return Type|null
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * Returns a string representation for this tag.
     *
     * @return string
     */
    public function __toString()
    {
        return ($this->type ? $this->type . ' ' : '')
            . (empty($this->variableName) ? null : ('$' . $this->variableName))
            . ($this->description ? ' ' . $this->description : '');
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for the {@}param tag in a Docblock.
 */
final class Param extends BaseTag implements Factory\StaticMethod
{
    /** @var string */
    protected $name = 'param';

    /** @var Type */
    private $type;

    /** @var string */
    private $variableName = '';

    /** @var bool determines whether this is a variadic argument */
    private $isVariadic = false;

    /**
     * @param string $variableName
     * @param Type $type
     * @param bool $isVariadic
     * @param Description $description
     */
    public function __construct($variableName, Type $type = null, $isVariadic = false, Description $description = null)
    {
        Assert::string($variableName);
        Assert::boolean($isVariadic);

        $this->variableName = $variableName;
        $this->type = $type;
        $this->isVariadic = $isVariadic;
        $this->description = $description;
    }

    /**
     * {@inheritdoc}
     */
    public static function create(
        $body,
        TypeResolver $typeResolver = null,
        DescriptionFactory $descriptionFactory = null,
        TypeContext $context = null
    ) {
        Assert::stringNotEmpty($body);
        Assert::allNotNull([$typeResolver, $descriptionFactory]);

        $parts = preg_split('/(\s+)/Su', $body, 3, PREG_SPLIT_DELIM_CAPTURE);
        $type = null;
        $variableName = '';
        $isVariadic = false;

        // if the first item that is encountered is not a variable; it is a type
        if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] !== '$')) {
            $type = $typeResolver->resolve(array_shift($parts), $context);
            array_shift($parts);
        }

        // if the next item starts with a $ or ...$ it must be the variable name
        if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] === '$' || substr($parts[0], 0, 4) === '...$')) {
            $variableName = array_shift($parts);
            array_shift($parts);

            if (substr($variableName, 0, 3) === '...') {
                $isVariadic = true;
                $variableName = substr($variableName, 3);
            }

            if (substr($variableName, 0, 1) === '$') {
                $variableName = substr($variableName, 1);
            }
        }

        $description = $descriptionFactory->create(implode('', $parts), $context);

        return new static($variableName, $type, $isVariadic, $description);
    }

    /**
     * Returns the variable's name.
     *
     * @return string
     */
    public function getVariableName()
    {
        return $this->variableName;
    }

    /**
     * Returns the variable's type or null if unknown.
     *
     * @return Type|null
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * Returns whether this tag is variadic.
     *
     * @return boolean
     */
    public function isVariadic()
    {
        return $this->isVariadic;
    }

    /**
     * Returns a string representation for this tag.
     *
     * @return string
     */
    public function __toString()
    {
        return ($this->type ? $this->type . ' ' : '')
        . ($this->isVariadic() ? '...' : '')
        . '$' . $this->variableName
        . ($this->description ? ' ' . $this->description : '');
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use phpDocumentor\Reflection\Types\Void_;
use Webmozart\Assert\Assert;

/**
 * Reflection class for an {@}method in a Docblock.
 */
final class Method extends BaseTag implements Factory\StaticMethod
{
    protected $name = 'method';

    /** @var string */
    private $methodName = '';

    /** @var string[] */
    private $arguments = [];

    /** @var bool */
    private $isStatic = false;

    /** @var Type */
    private $returnType;

    public function __construct(
        $methodName,
        array $arguments = [],
        Type $returnType = null,
        $static = false,
        Description $description = null
    ) {
        Assert::stringNotEmpty($methodName);
        Assert::boolean($static);

        if ($returnType === null) {
            $returnType = new Void_();
        }

        $this->methodName  = $methodName;
        $this->arguments   = $this->filterArguments($arguments);
        $this->returnType  = $returnType;
        $this->isStatic    = $static;
        $this->description = $description;
    }

    /**
     * {@inheritdoc}
     */
    public static function create(
        $body,
        TypeResolver $typeResolver = null,
        DescriptionFactory $descriptionFactory = null,
        TypeContext $context = null
    ) {
        Assert::stringNotEmpty($body);
        Assert::allNotNull([ $typeResolver, $descriptionFactory ]);

        // 1. none or more whitespace
        // 2. optionally the keyword "static" followed by whitespace
        // 3. optionally a word with underscores followed by whitespace : as
        //    type for the return value
        // 4. then optionally a word with underscores followed by () and
        //    whitespace : as method name as used by phpDocumentor
        // 5. then a word with underscores, followed by ( and any character
        //    until a ) and whitespace : as method name with signature
        // 6. any remaining text : as description
        if (!preg_match(
            '/^
                # Static keyword
                # Declares a static method ONLY if type is also present
                (?:
                    (static)
                    \s+
                )?
                # Return type
                (?:
                    (   
                        (?:[\w\|_\\\\]*\$this[\w\|_\\\\]*)
                        |
                        (?:
                            (?:[\w\|_\\\\]+)
                            # array notation           
                            (?:\[\])*
                        )*
                    )
                    \s+
                )?
                # Legacy method name (not captured)
                (?:
                    [\w_]+\(\)\s+
                )?
                # Method name
                ([\w\|_\\\\]+)
                # Arguments
                (?:
                    \(([^\)]*)\)
                )?
                \s*
                # Description
                (.*)
            $/sux',
            $body,
            $matches
        )) {
            return null;
        }

        list(, $static, $returnType, $methodName, $arguments, $description) = $matches;

        $static      = $static === 'static';

        if ($returnType === '') {
            $returnType = 'void';
        }

        $returnType  = $typeResolver->resolve($returnType, $context);
        $description = $descriptionFactory->create($description, $context);

        if (is_string($arguments) && strlen($arguments) > 0) {
            $arguments = explode(',', $arguments);
            foreach ($arguments as &$argument) {
                $argument = explode(' ', self::stripRestArg(trim($argument)), 2);
                if ($argument[0][0] === '$') {
                    $argumentName = substr($argument[0], 1);
                    $argumentType = new Void_();
                } else {
                    $argumentType = $typeResolver->resolve($argument[0], $context);
                    $argumentName = '';
                    if (isset($argument[1])) {
                        $argument[1] = self::stripRestArg($argument[1]);
                        $argumentName = substr($argument[1], 1);
                    }
                }

                $argument = [ 'name' => $argumentName, 'type' => $argumentType];
            }
        } else {
            $arguments = [];
        }

        return new static($methodName, $arguments, $returnType, $static, $description);
    }

    /**
     * Retrieves the method name.
     *
     * @return string
     */
    public function getMethodName()
    {
        return $this->methodName;
    }

    /**
     * @return string[]
     */
    public function getArguments()
    {
        return $this->arguments;
    }

    /**
     * Checks whether the method tag describes a static method or not.
     *
     * @return bool TRUE if the method declaration is for a static method, FALSE otherwise.
     */
    public function isStatic()
    {
        return $this->isStatic;
    }

    /**
     * @return Type
     */
    public function getReturnType()
    {
        return $this->returnType;
    }

    public function __toString()
    {
        $arguments = [];
        foreach ($this->arguments as $argument) {
            $arguments[] = $argument['type'] . ' $' . $argument['name'];
        }

        return trim(($this->isStatic() ? 'static ' : '')
            . (string)$this->returnType . ' '
            . $this->methodName
            . '(' . implode(', ', $arguments) . ')'
            . ($this->description ? ' ' . $this->description->render() : ''));
    }

    private function filterArguments($arguments)
    {
        foreach ($arguments as &$argument) {
            if (is_string($argument)) {
                $argument = [ 'name' => $argument ];
            }

            if (! isset($argument['type'])) {
                $argument['type'] = new Void_();
            }

            $keys = array_keys($argument);
            sort($keys);
            if ($keys !== [ 'name', 'type' ]) {
                throw new \InvalidArgumentException(
                    'Arguments can only have the "name" and "type" fields, found: ' . var_export($keys, true)
                );
            }
        }

        return $arguments;
    }

    private static function stripRestArg($argument)
    {
        if (strpos($argument, '...') === 0) {
            $argument = trim(substr($argument, 3));
        }

        return $argument;
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Tag;

interface Formatter
{
    /**
     * Formats a tag into a string representation according to a specific format, such as Markdown.
     *
     * @param Tag $tag
     *
     * @return string
     */
    public function format(Tag $tag);
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\FqsenResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for a {@}uses tag in a Docblock.
 */
final class Uses extends BaseTag implements Factory\StaticMethod
{
    protected $name = 'uses';

    /** @var Fqsen */
    protected $refers = null;

    /**
     * Initializes this tag.
     *
     * @param Fqsen       $refers
     * @param Description $description
     */
    public function __construct(Fqsen $refers, Description $description = null)
    {
        $this->refers      = $refers;
        $this->description = $description;
    }

    /**
     * {@inheritdoc}
     */
    public static function create(
        $body,
        FqsenResolver $resolver = null,
        DescriptionFactory $descriptionFactory = null,
        TypeContext $context = null
    ) {
        Assert::string($body);
        Assert::allNotNull([$resolver, $descriptionFactory]);

        $parts = preg_split('/\s+/Su', $body, 2);

        return new static(
            $resolver->resolve($parts[0], $context),
            $descriptionFactory->create(isset($parts[1]) ? $parts[1] : '', $context)
        );
    }

    /**
     * Returns the structural element this tag refers to.
     *
     * @return Fqsen
     */
    public function getReference()
    {
        return $this->refers;
    }

    /**
     * Returns a string representation of this tag.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->refers . ' ' . $this->description->render();
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for a {@}return tag in a Docblock.
 */
final class Return_ extends BaseTag implements Factory\StaticMethod
{
    protected $name = 'return';

    /** @var Type */
    private $type;

    public function __construct(Type $type, Description $description = null)
    {
        $this->type = $type;
        $this->description = $description;
    }

    /**
     * {@inheritdoc}
     */
    public static function create(
        $body,
        TypeResolver $typeResolver = null,
        DescriptionFactory $descriptionFactory = null,
        TypeContext $context = null
    ) {
        Assert::string($body);
        Assert::allNotNull([$typeResolver, $descriptionFactory]);

        $parts = preg_split('/\s+/Su', $body, 2);

        $type = $typeResolver->resolve(isset($parts[0]) ? $parts[0] : '', $context);
        $description = $descriptionFactory->create(isset($parts[1]) ? $parts[1] : '', $context);

        return new static($type, $description);
    }

    /**
     * Returns the type section of the variable.
     *
     * @return Type
     */
    public function getType()
    {
        return $this->type;
    }

    public function __toString()
    {
        return $this->type . ' ' . $this->description;
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags\Formatter;

use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\DocBlock\Tags\Formatter;

class PassthroughFormatter implements Formatter
{
    /**
     * Formats the given tag to return a simple plain text version.
     *
     * @param Tag $tag
     *
     * @return string
     */
    public function format(Tag $tag)
    {
        return trim('@' . $tag->getName() . ' ' . (string)$tag);
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @author    Jan Schneider <jan@horde.org>
 * @copyright 2017 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags\Formatter;

use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\DocBlock\Tags\Formatter;

class AlignFormatter implements Formatter
{
    /** @var int The maximum tag name length. */
    protected $maxLen = 0;

    /**
     * Constructor.
     *
     * @param Tag[] $tags All tags that should later be aligned with the formatter.
     */
    public function __construct(array $tags)
    {
        foreach ($tags as $tag) {
            $this->maxLen = max($this->maxLen, strlen($tag->getName()));
        }
    }

    /**
     * Formats the given tag to return a simple plain text version.
     *
     * @param Tag $tag
     *
     * @return string
     */
    public function format(Tag $tag)
    {
        return '@' . $tag->getName() . str_repeat(' ', $this->maxLen - strlen($tag->getName()) + 1) . (string)$tag;
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for a {@}property-read tag in a Docblock.
 */
class PropertyRead extends BaseTag implements Factory\StaticMethod
{
    /** @var string */
    protected $name = 'property-read';

    /** @var Type */
    private $type;

    /** @var string */
    protected $variableName = '';

    /**
     * @param string      $variableName
     * @param Type        $type
     * @param Description $description
     */
    public function __construct($variableName, Type $type = null, Description $description = null)
    {
        Assert::string($variableName);

        $this->variableName = $variableName;
        $this->type = $type;
        $this->description = $description;
    }

    /**
     * {@inheritdoc}
     */
    public static function create(
        $body,
        TypeResolver $typeResolver = null,
        DescriptionFactory $descriptionFactory = null,
        TypeContext $context = null
    ) {
        Assert::stringNotEmpty($body);
        Assert::allNotNull([$typeResolver, $descriptionFactory]);

        $parts = preg_split('/(\s+)/Su', $body, 3, PREG_SPLIT_DELIM_CAPTURE);
        $type = null;
        $variableName = '';

        // if the first item that is encountered is not a variable; it is a type
        if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] !== '$')) {
            $type = $typeResolver->resolve(array_shift($parts), $context);
            array_shift($parts);
        }

        // if the next item starts with a $ or ...$ it must be the variable name
        if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] === '$')) {
            $variableName = array_shift($parts);
            array_shift($parts);

            if (substr($variableName, 0, 1) === '$') {
                $variableName = substr($variableName, 1);
            }
        }

        $description = $descriptionFactory->create(implode('', $parts), $context);

        return new static($variableName, $type, $description);
    }

    /**
     * Returns the variable's name.
     *
     * @return string
     */
    public function getVariableName()
    {
        return $this->variableName;
    }

    /**
     * Returns the variable's type or null if unknown.
     *
     * @return Type|null
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * Returns a string representation for this tag.
     *
     * @return string
     */
    public function __toString()
    {
        return ($this->type ? $this->type . ' ' : '')
        . '$' . $this->variableName
        . ($this->description ? ' ' . $this->description : '');
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for a {@}property tag in a Docblock.
 */
class Property extends BaseTag implements Factory\StaticMethod
{
    /** @var string */
    protected $name = 'property';

    /** @var Type */
    private $type;

    /** @var string */
    protected $variableName = '';

    /**
     * @param string      $variableName
     * @param Type        $type
     * @param Description $description
     */
    public function __construct($variableName, Type $type = null, Description $description = null)
    {
        Assert::string($variableName);

        $this->variableName = $variableName;
        $this->type = $type;
        $this->description = $description;
    }

    /**
     * {@inheritdoc}
     */
    public static function create(
        $body,
        TypeResolver $typeResolver = null,
        DescriptionFactory $descriptionFactory = null,
        TypeContext $context = null
    ) {
        Assert::stringNotEmpty($body);
        Assert::allNotNull([$typeResolver, $descriptionFactory]);

        $parts = preg_split('/(\s+)/Su', $body, 3, PREG_SPLIT_DELIM_CAPTURE);
        $type = null;
        $variableName = '';

        // if the first item that is encountered is not a variable; it is a type
        if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] !== '$')) {
            $type = $typeResolver->resolve(array_shift($parts), $context);
            array_shift($parts);
        }

        // if the next item starts with a $ or ...$ it must be the variable name
        if (isset($parts[0]) && (strlen($parts[0]) > 0) && ($parts[0][0] === '$')) {
            $variableName = array_shift($parts);
            array_shift($parts);

            if (substr($variableName, 0, 1) === '$') {
                $variableName = substr($variableName, 1);
            }
        }

        $description = $descriptionFactory->create(implode('', $parts), $context);

        return new static($variableName, $type, $description);
    }

    /**
     * Returns the variable's name.
     *
     * @return string
     */
    public function getVariableName()
    {
        return $this->variableName;
    }

    /**
     * Returns the variable's type or null if unknown.
     *
     * @return Type|null
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * Returns a string representation for this tag.
     *
     * @return string
     */
    public function __toString()
    {
        return ($this->type ? $this->type . ' ' : '')
        . '$' . $this->variableName
        . ($this->description ? ' ' . $this->description : '');
    }
}
<?php
/**
 * phpDocumentor
 *
 * PHP Version 5.3
 *
 * @author    Vasil Rangelov <boen.robot@gmail.com>
 * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com)
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for a {@}version tag in a Docblock.
 */
final class Version extends BaseTag implements Factory\StaticMethod
{
    protected $name = 'version';

    /**
     * PCRE regular expression matching a version vector.
     * Assumes the "x" modifier.
     */
    const REGEX_VECTOR = '(?:
        # Normal release vectors.
        \d\S*
        |
        # VCS version vectors. Per PHPCS, they are expected to
        # follow the form of the VCS name, followed by ":", followed
        # by the version vector itself.
        # By convention, popular VCSes like CVS, SVN and GIT use "$"
        # around the actual version vector.
        [^\s\:]+\:\s*\$[^\$]+\$
    )';

    /** @var string The version vector. */
    private $version = '';

    public function __construct($version = null, Description $description = null)
    {
        Assert::nullOrStringNotEmpty($version);

        $this->version = $version;
        $this->description = $description;
    }

    /**
     * @return static
     */
    public static function create($body, DescriptionFactory $descriptionFactory = null, TypeContext $context = null)
    {
        Assert::nullOrString($body);
        if (empty($body)) {
            return new static();
        }

        $matches = [];
        if (!preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) {
            return null;
        }

        return new static(
            $matches[1],
            $descriptionFactory->create(isset($matches[2]) ? $matches[2] : '', $context)
        );
    }

    /**
     * Gets the version section of the tag.
     *
     * @return string
     */
    public function getVersion()
    {
        return $this->version;
    }

    /**
     * Returns a string representation for this tag.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->version . ($this->description ? ' ' . $this->description->render() : '');
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\DocBlock\Tags\Reference\Fqsen as FqsenRef;
use phpDocumentor\Reflection\DocBlock\Tags\Reference\Reference;
use phpDocumentor\Reflection\DocBlock\Tags\Reference\Url;
use phpDocumentor\Reflection\FqsenResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for an {@}see tag in a Docblock.
 */
class See extends BaseTag implements Factory\StaticMethod
{
    protected $name = 'see';

    /** @var Reference */
    protected $refers = null;

    /**
     * Initializes this tag.
     *
     * @param Reference $refers
     * @param Description $description
     */
    public function __construct(Reference $refers, Description $description = null)
    {
        $this->refers = $refers;
        $this->description = $description;
    }

    /**
     * {@inheritdoc}
     */
    public static function create(
        $body,
        FqsenResolver $resolver = null,
        DescriptionFactory $descriptionFactory = null,
        TypeContext $context = null
    ) {
        Assert::string($body);
        Assert::allNotNull([$resolver, $descriptionFactory]);

        $parts       = preg_split('/\s+/Su', $body, 2);
        $description = isset($parts[1]) ? $descriptionFactory->create($parts[1], $context) : null;

        // https://tools.ietf.org/html/rfc2396#section-3
        if (preg_match('/\w:\/\/\w/i', $parts[0])) {
            return new static(new Url($parts[0]), $description);
        }

        return new static(new FqsenRef($resolver->resolve($parts[0], $context)), $description);
    }

    /**
     * Returns the ref of this tag.
     *
     * @return Reference
     */
    public function getReference()
    {
        return $this->refers;
    }

    /**
     * Returns a string representation of this tag.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->refers . ($this->description ? ' ' . $this->description->render() : '');
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\DocBlock\StandardTagFactory;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Parses a tag definition for a DocBlock.
 */
class Generic extends BaseTag implements Factory\StaticMethod
{
    /**
     * Parses a tag and populates the member variables.
     *
     * @param string $name Name of the tag.
     * @param Description $description The contents of the given tag.
     */
    public function __construct($name, Description $description = null)
    {
        $this->validateTagName($name);

        $this->name = $name;
        $this->description = $description;
    }

    /**
     * Creates a new tag that represents any unknown tag type.
     *
     * @param string             $body
     * @param string             $name
     * @param DescriptionFactory $descriptionFactory
     * @param TypeContext        $context
     *
     * @return static
     */
    public static function create(
        $body,
        $name = '',
        DescriptionFactory $descriptionFactory = null,
        TypeContext $context = null
    ) {
        Assert::string($body);
        Assert::stringNotEmpty($name);
        Assert::notNull($descriptionFactory);

        $description = $descriptionFactory && $body !== "" ? $descriptionFactory->create($body, $context) : null;

        return new static($name, $description);
    }

    /**
     * Returns the tag as a serialized string
     *
     * @return string
     */
    public function __toString()
    {
        return ($this->description ? $this->description->render() : '');
    }

    /**
     * Validates if the tag name matches the expected format, otherwise throws an exception.
     *
     * @param string $name
     *
     * @return void
     */
    private function validateTagName($name)
    {
        if (! preg_match('/^' . StandardTagFactory::REGEX_TAGNAME . '$/u', $name)) {
            throw new \InvalidArgumentException(
                'The tag name "' . $name . '" is not wellformed. Tags may only consist of letters, underscores, '
                . 'hyphens and backslashes.'
            );
        }
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for a {@}source tag in a Docblock.
 */
final class Source extends BaseTag implements Factory\StaticMethod
{
    /** @var string */
    protected $name = 'source';

    /** @var int The starting line, relative to the structural element's location. */
    private $startingLine = 1;

    /** @var int|null The number of lines, relative to the starting line. NULL means "to the end". */
    private $lineCount = null;

    public function __construct($startingLine, $lineCount = null, Description $description = null)
    {
        Assert::integerish($startingLine);
        Assert::nullOrIntegerish($lineCount);

        $this->startingLine = (int)$startingLine;
        $this->lineCount    = $lineCount !== null ? (int)$lineCount : null;
        $this->description  = $description;
    }

    /**
     * {@inheritdoc}
     */
    public static function create($body, DescriptionFactory $descriptionFactory = null, TypeContext $context = null)
    {
        Assert::stringNotEmpty($body);
        Assert::notNull($descriptionFactory);

        $startingLine = 1;
        $lineCount    = null;
        $description  = null;

        // Starting line / Number of lines / Description
        if (preg_match('/^([1-9]\d*)\s*(?:((?1))\s+)?(.*)$/sux', $body, $matches)) {
            $startingLine = (int)$matches[1];
            if (isset($matches[2]) && $matches[2] !== '') {
                $lineCount = (int)$matches[2];
            }

            $description = $matches[3];
        }

        return new static($startingLine, $lineCount, $descriptionFactory->create($description, $context));
    }

    /**
     * Gets the starting line.
     *
     * @return int The starting line, relative to the structural element's
     *     location.
     */
    public function getStartingLine()
    {
        return $this->startingLine;
    }

    /**
     * Returns the number of lines.
     *
     * @return int|null The number of lines, relative to the starting line. NULL
     *     means "to the end".
     */
    public function getLineCount()
    {
        return $this->lineCount;
    }

    public function __toString()
    {
        return $this->startingLine
        . ($this->lineCount !== null ? ' ' . $this->lineCount : '')
        . ($this->description ? ' ' . $this->description->render() : '');
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use Webmozart\Assert\Assert;

/**
 * Reflection class for an {@}author tag in a Docblock.
 */
final class Author extends BaseTag implements Factory\StaticMethod
{
    /** @var string register that this is the author tag. */
    protected $name = 'author';

    /** @var string The name of the author */
    private $authorName = '';

    /** @var string The email of the author */
    private $authorEmail = '';

    /**
     * Initializes this tag with the author name and e-mail.
     *
     * @param string $authorName
     * @param string $authorEmail
     */
    public function __construct($authorName, $authorEmail)
    {
        Assert::string($authorName);
        Assert::string($authorEmail);
        if ($authorEmail && !filter_var($authorEmail, FILTER_VALIDATE_EMAIL)) {
            throw new \InvalidArgumentException('The author tag does not have a valid e-mail address');
        }

        $this->authorName  = $authorName;
        $this->authorEmail = $authorEmail;
    }

    /**
     * Gets the author's name.
     *
     * @return string The author's name.
     */
    public function getAuthorName()
    {
        return $this->authorName;
    }

    /**
     * Returns the author's email.
     *
     * @return string The author's email.
     */
    public function getEmail()
    {
        return $this->authorEmail;
    }

    /**
     * Returns this tag in string form.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->authorName . (strlen($this->authorEmail) ? ' <' . $this->authorEmail . '>' : '');
    }

    /**
     * Attempts to create a new Author object based on â€ he tag body.
     *
     * @param string $body
     *
     * @return static
     */
    public static function create($body)
    {
        Assert::string($body);

        $splitTagContent = preg_match('/^([^\<]*)(?:\<([^\>]*)\>)?$/u', $body, $matches);
        if (!$splitTagContent) {
            return null;
        }

        $authorName = trim($matches[1]);
        $email = isset($matches[2]) ? trim($matches[2]) : '';

        return new static($authorName, $email);
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for a {@}throws tag in a Docblock.
 */
final class Throws extends BaseTag implements Factory\StaticMethod
{
    protected $name = 'throws';

    /** @var Type */
    private $type;

    public function __construct(Type $type, Description $description = null)
    {
        $this->type        = $type;
        $this->description = $description;
    }

    /**
     * {@inheritdoc}
     */
    public static function create(
        $body,
        TypeResolver $typeResolver = null,
        DescriptionFactory $descriptionFactory = null,
        TypeContext $context = null
    ) {
        Assert::string($body);
        Assert::allNotNull([$typeResolver, $descriptionFactory]);

        $parts = preg_split('/\s+/Su', $body, 2);

        $type        = $typeResolver->resolve(isset($parts[0]) ? $parts[0] : '', $context);
        $description = $descriptionFactory->create(isset($parts[1]) ? $parts[1] : '', $context);

        return new static($type, $description);
    }

    /**
     * Returns the type section of the variable.
     *
     * @return Type
     */
    public function getType()
    {
        return $this->type;
    }

    public function __toString()
    {
        return $this->type . ' ' . $this->description;
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;

interface Strategy
{
    public function create($body);
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;

interface StaticMethod
{
    public static function create($body);
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 *  For the full copyright and license information, please view the LICENSE
 *  file that was distributed with this source code.
 *
 *  @copyright 2010-2017 Mike van Riel<mike@phpdoc.org>
 *  @license   http://www.opensource.org/licenses/mit-license.php MIT
 *  @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags\Reference;

/**
 * Interface for references in {@see phpDocumentor\Reflection\DocBlock\Tags\See}
 */
interface Reference
{
    public function __toString();
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 *  For the full copyright and license information, please view the LICENSE
 *  file that was distributed with this source code.
 *
 *  @copyright 2010-2017 Mike van Riel<mike@phpdoc.org>
 *  @license   http://www.opensource.org/licenses/mit-license.php MIT
 *  @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags\Reference;

use Webmozart\Assert\Assert;

/**
 * Url reference used by {@see phpDocumentor\Reflection\DocBlock\Tags\See}
 */
final class Url implements Reference
{
    /**
     * @var string
     */
    private $uri;

    /**
     * Url constructor.
     */
    public function __construct($uri)
    {
        Assert::stringNotEmpty($uri);
        $this->uri = $uri;
    }

    public function __toString()
    {
        return $this->uri;
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 *  For the full copyright and license information, please view the LICENSE
 *  file that was distributed with this source code.
 *
 *  @copyright 2010-2017 Mike van Riel<mike@phpdoc.org>
 *  @license   http://www.opensource.org/licenses/mit-license.php MIT
 *  @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags\Reference;

use phpDocumentor\Reflection\Fqsen as RealFqsen;

/**
 * Fqsen reference used by {@see phpDocumentor\Reflection\DocBlock\Tags\See}
 */
final class Fqsen implements Reference
{
    /**
     * @var RealFqsen
     */
    private $fqsen;

    /**
     * Fqsen constructor.
     */
    public function __construct(RealFqsen $fqsen)
    {
        $this->fqsen = $fqsen;
    }

    /**
     * @return string string representation of the referenced fqsen
     */
    public function __toString()
    {
        return (string)$this->fqsen;
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\Tag;
use Webmozart\Assert\Assert;

/**
 * Reflection class for a {@}example tag in a Docblock.
 */
final class Example extends BaseTag
{
    /**
     * @var string Path to a file to use as an example. May also be an absolute URI.
     */
    private $filePath;

    /**
     * @var bool Whether the file path component represents an URI. This determines how the file portion
     *     appears at {@link getContent()}.
     */
    private $isURI = false;

    /**
     * @var int
     */
    private $startingLine;

    /**
     * @var int
     */
    private $lineCount;

    public function __construct($filePath, $isURI, $startingLine, $lineCount, $description)
    {
        Assert::notEmpty($filePath);
        Assert::integer($startingLine);
        Assert::greaterThanEq($startingLine, 0);

        $this->filePath = $filePath;
        $this->startingLine = $startingLine;
        $this->lineCount = $lineCount;
        $this->name = 'example';
        if ($description !== null) {
            $this->description = trim($description);
        }

        $this->isURI = $isURI;
    }

    /**
     * {@inheritdoc}
     */
    public function getContent()
    {
        if (null === $this->description) {
            $filePath = '"' . $this->filePath . '"';
            if ($this->isURI) {
                $filePath = $this->isUriRelative($this->filePath)
                    ? str_replace('%2F', '/', rawurlencode($this->filePath))
                    :$this->filePath;
            }

            return trim($filePath . ' ' . parent::getDescription());
        }

        return $this->description;
    }

    /**
     * {@inheritdoc}
     */
    public static function create($body)
    {
        // File component: File path in quotes or File URI / Source information
        if (! preg_match('/^(?:\"([^\"]+)\"|(\S+))(?:\s+(.*))?$/sux', $body, $matches)) {
            return null;
        }

        $filePath = null;
        $fileUri  = null;
        if ('' !== $matches[1]) {
            $filePath = $matches[1];
        } else {
            $fileUri = $matches[2];
        }

        $startingLine = 1;
        $lineCount    = null;
        $description  = null;

        if (array_key_exists(3, $matches)) {
            $description = $matches[3];

            // Starting line / Number of lines / Description
            if (preg_match('/^([1-9]\d*)(?:\s+((?1))\s*)?(.*)$/sux', $matches[3], $contentMatches)) {
                $startingLine = (int)$contentMatches[1];
                if (isset($contentMatches[2]) && $contentMatches[2] !== '') {
                    $lineCount = (int)$contentMatches[2];
                }

                if (array_key_exists(3, $contentMatches)) {
                    $description = $contentMatches[3];
                }
            }
        }

        return new static(
            $filePath !== null?$filePath:$fileUri,
            $fileUri !== null,
            $startingLine,
            $lineCount,
            $description
        );
    }

    /**
     * Returns the file path.
     *
     * @return string Path to a file to use as an example.
     *     May also be an absolute URI.
     */
    public function getFilePath()
    {
        return $this->filePath;
    }

    /**
     * Returns a string representation for this tag.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->filePath . ($this->description ? ' ' . $this->description : '');
    }

    /**
     * Returns true if the provided URI is relative or contains a complete scheme (and thus is absolute).
     *
     * @param string $uri
     *
     * @return bool
     */
    private function isUriRelative($uri)
    {
        return false === strpos($uri, ':');
    }

    /**
     * @return int
     */
    public function getStartingLine()
    {
        return $this->startingLine;
    }

    /**
     * @return int
     */
    public function getLineCount()
    {
        return $this->lineCount;
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for a {@}deprecated tag in a Docblock.
 */
final class Deprecated extends BaseTag implements Factory\StaticMethod
{
    protected $name = 'deprecated';

    /**
     * PCRE regular expression matching a version vector.
     * Assumes the "x" modifier.
     */
    const REGEX_VECTOR = '(?:
        # Normal release vectors.
        \d\S*
        |
        # VCS version vectors. Per PHPCS, they are expected to
        # follow the form of the VCS name, followed by ":", followed
        # by the version vector itself.
        # By convention, popular VCSes like CVS, SVN and GIT use "$"
        # around the actual version vector.
        [^\s\:]+\:\s*\$[^\$]+\$
    )';

    /** @var string The version vector. */
    private $version = '';

    public function __construct($version = null, Description $description = null)
    {
        Assert::nullOrStringNotEmpty($version);

        $this->version = $version;
        $this->description = $description;
    }

    /**
     * @return static
     */
    public static function create($body, DescriptionFactory $descriptionFactory = null, TypeContext $context = null)
    {
        Assert::nullOrString($body);
        if (empty($body)) {
            return new static();
        }

        $matches = [];
        if (!preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) {
            return new static(
                null,
                null !== $descriptionFactory ? $descriptionFactory->create($body, $context) : null
            );
        }

        return new static(
            $matches[1],
            $descriptionFactory->create(isset($matches[2]) ? $matches[2] : '', $context)
        );
    }

    /**
     * Gets the version section of the tag.
     *
     * @return string
     */
    public function getVersion()
    {
        return $this->version;
    }

    /**
     * Returns a string representation for this tag.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->version . ($this->description ? ' ' . $this->description->render() : '');
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock\Tags;

use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\FqsenResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Reflection class for a @covers tag in a Docblock.
 */
final class Covers extends BaseTag implements Factory\StaticMethod
{
    protected $name = 'covers';

    /** @var Fqsen */
    private $refers = null;

    /**
     * Initializes this tag.
     *
     * @param Fqsen $refers
     * @param Description $description
     */
    public function __construct(Fqsen $refers, Description $description = null)
    {
        $this->refers = $refers;
        $this->description = $description;
    }

    /**
     * {@inheritdoc}
     */
    public static function create(
        $body,
        DescriptionFactory $descriptionFactory = null,
        FqsenResolver $resolver = null,
        TypeContext $context = null
    ) {
        Assert::string($body);
        Assert::notEmpty($body);

        $parts = preg_split('/\s+/Su', $body, 2);

        return new static(
            $resolver->resolve($parts[0], $context),
            $descriptionFactory->create(isset($parts[1]) ? $parts[1] : '', $context)
        );
    }

    /**
     * Returns the structural element this tag refers to.
     *
     * @return Fqsen
     */
    public function getReference()
    {
        return $this->refers;
    }

    /**
     * Returns a string representation of this tag.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->refers . ($this->description ? ' ' . $this->description->render() : '');
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock;

use phpDocumentor\Reflection\DocBlock\Tags\Factory\StaticMethod;
use phpDocumentor\Reflection\DocBlock\Tags\Generic;
use phpDocumentor\Reflection\FqsenResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;

/**
 * Creates a Tag object given the contents of a tag.
 *
 * This Factory is capable of determining the appropriate class for a tag and instantiate it using its `create`
 * factory method. The `create` factory method of a Tag can have a variable number of arguments; this way you can
 * pass the dependencies that you need to construct a tag object.
 *
 * > Important: each parameter in addition to the body variable for the `create` method must default to null, otherwise
 * > it violates the constraint with the interface; it is recommended to use the {@see Assert::notNull()} method to
 * > verify that a dependency is actually passed.
 *
 * This Factory also features a Service Locator component that is used to pass the right dependencies to the
 * `create` method of a tag; each dependency should be registered as a service or as a parameter.
 *
 * When you want to use a Tag of your own with custom handling you need to call the `registerTagHandler` method, pass
 * the name of the tag and a Fully Qualified Class Name pointing to a class that implements the Tag interface.
 */
final class StandardTagFactory implements TagFactory
{
    /** PCRE regular expression matching a tag name. */
    const REGEX_TAGNAME = '[\w\-\_\\\\]+';

    /**
     * @var string[] An array with a tag as a key, and an FQCN to a class that handles it as an array value.
     */
    private $tagHandlerMappings = [
        'author'         => '\phpDocumentor\Reflection\DocBlock\Tags\Author',
        'covers'         => '\phpDocumentor\Reflection\DocBlock\Tags\Covers',
        'deprecated'     => '\phpDocumentor\Reflection\DocBlock\Tags\Deprecated',
        // 'example'        => '\phpDocumentor\Reflection\DocBlock\Tags\Example',
        'link'           => '\phpDocumentor\Reflection\DocBlock\Tags\Link',
        'method'         => '\phpDocumentor\Reflection\DocBlock\Tags\Method',
        'param'          => '\phpDocumentor\Reflection\DocBlock\Tags\Param',
        'property-read'  => '\phpDocumentor\Reflection\DocBlock\Tags\PropertyRead',
        'property'       => '\phpDocumentor\Reflection\DocBlock\Tags\Property',
        'property-write' => '\phpDocumentor\Reflection\DocBlock\Tags\PropertyWrite',
        'return'         => '\phpDocumentor\Reflection\DocBlock\Tags\Return_',
        'see'            => '\phpDocumentor\Reflection\DocBlock\Tags\See',
        'since'          => '\phpDocumentor\Reflection\DocBlock\Tags\Since',
        'source'         => '\phpDocumentor\Reflection\DocBlock\Tags\Source',
        'throw'          => '\phpDocumentor\Reflection\DocBlock\Tags\Throws',
        'throws'         => '\phpDocumentor\Reflection\DocBlock\Tags\Throws',
        'uses'           => '\phpDocumentor\Reflection\DocBlock\Tags\Uses',
        'var'            => '\phpDocumentor\Reflection\DocBlock\Tags\Var_',
        'version'        => '\phpDocumentor\Reflection\DocBlock\Tags\Version'
    ];

    /**
     * @var \ReflectionParameter[][] a lazy-loading cache containing parameters for each tagHandler that has been used.
     */
    private $tagHandlerParameterCache = [];

    /**
     * @var FqsenResolver
     */
    private $fqsenResolver;

    /**
     * @var mixed[] an array representing a simple Service Locator where we can store parameters and
     *     services that can be inserted into the Factory Methods of Tag Handlers.
     */
    private $serviceLocator = [];

    /**
     * Initialize this tag factory with the means to resolve an FQSEN and optionally a list of tag handlers.
     *
     * If no tag handlers are provided than the default list in the {@see self::$tagHandlerMappings} property
     * is used.
     *
     * @param FqsenResolver $fqsenResolver
     * @param string[]      $tagHandlers
     *
     * @see self::registerTagHandler() to add a new tag handler to the existing default list.
     */
    public function __construct(FqsenResolver $fqsenResolver, array $tagHandlers = null)
    {
        $this->fqsenResolver = $fqsenResolver;
        if ($tagHandlers !== null) {
            $this->tagHandlerMappings = $tagHandlers;
        }

        $this->addService($fqsenResolver, FqsenResolver::class);
    }

    /**
     * {@inheritDoc}
     */
    public function create($tagLine, TypeContext $context = null)
    {
        if (! $context) {
            $context = new TypeContext('');
        }

        list($tagName, $tagBody) = $this->extractTagParts($tagLine);

        if ($tagBody !== '' && $tagBody[0] === '[') {
            throw new \InvalidArgumentException(
                'The tag "' . $tagLine . '" does not seem to be wellformed, please check it for errors'
            );
        }

        return $this->createTag($tagBody, $tagName, $context);
    }

    /**
     * {@inheritDoc}
     */
    public function addParameter($name, $value)
    {
        $this->serviceLocator[$name] = $value;
    }

    /**
     * {@inheritDoc}
     */
    public function addService($service, $alias = null)
    {
        $this->serviceLocator[$alias ?: get_class($service)] = $service;
    }

    /**
     * {@inheritDoc}
     */
    public function registerTagHandler($tagName, $handler)
    {
        Assert::stringNotEmpty($tagName);
        Assert::stringNotEmpty($handler);
        Assert::classExists($handler);
        Assert::implementsInterface($handler, StaticMethod::class);

        if (strpos($tagName, '\\') && $tagName[0] !== '\\') {
            throw new \InvalidArgumentException(
                'A namespaced tag must have a leading backslash as it must be fully qualified'
            );
        }

        $this->tagHandlerMappings[$tagName] = $handler;
    }

    /**
     * Extracts all components for a tag.
     *
     * @param string $tagLine
     *
     * @return string[]
     */
    private function extractTagParts($tagLine)
    {
        $matches = [];
        if (! preg_match('/^@(' . self::REGEX_TAGNAME . ')(?:\s*([^\s].*)|$)/us', $tagLine, $matches)) {
            throw new \InvalidArgumentException(
                'The tag "' . $tagLine . '" does not seem to be wellformed, please check it for errors'
            );
        }

        if (count($matches) < 3) {
            $matches[] = '';
        }

        return array_slice($matches, 1);
    }

    /**
     * Creates a new tag object with the given name and body or returns null if the tag name was recognized but the
     * body was invalid.
     *
     * @param string  $body
     * @param string  $name
     * @param TypeContext $context
     *
     * @return Tag|null
     */
    private function createTag($body, $name, TypeContext $context)
    {
        $handlerClassName = $this->findHandlerClassName($name, $context);
        $arguments        = $this->getArgumentsForParametersFromWiring(
            $this->fetchParametersForHandlerFactoryMethod($handlerClassName),
            $this->getServiceLocatorWithDynamicParameters($context, $name, $body)
        );

        return call_user_func_array([$handlerClassName, 'create'], $arguments);
    }

    /**
     * Determines the Fully Qualified Class Name of the Factory or Tag (containing a Factory Method `create`).
     *
     * @param string  $tagName
     * @param TypeContext $context
     *
     * @return string
     */
    private function findHandlerClassName($tagName, TypeContext $context)
    {
        $handlerClassName = Generic::class;
        if (isset($this->tagHandlerMappings[$tagName])) {
            $handlerClassName = $this->tagHandlerMappings[$tagName];
        } elseif ($this->isAnnotation($tagName)) {
            // TODO: Annotation support is planned for a later stage and as such is disabled for now
            // $tagName = (string)$this->fqsenResolver->resolve($tagName, $context);
            // if (isset($this->annotationMappings[$tagName])) {
            //     $handlerClassName = $this->annotationMappings[$tagName];
            // }
        }

        return $handlerClassName;
    }

    /**
     * Retrieves the arguments that need to be passed to the Factory Method with the given Parameters.
     *
     * @param \ReflectionParameter[] $parameters
     * @param mixed[]                $locator
     *
     * @return mixed[] A series of values that can be passed to the Factory Method of the tag whose parameters
     *     is provided with this method.
     */
    private function getArgumentsForParametersFromWiring($parameters, $locator)
    {
        $arguments = [];
        foreach ($parameters as $index => $parameter) {
            $typeHint = $parameter->getClass() ? $parameter->getClass()->getName() : null;
            if (isset($locator[$typeHint])) {
                $arguments[] = $locator[$typeHint];
                continue;
            }

            $parameterName = $parameter->getName();
            if (isset($locator[$parameterName])) {
                $arguments[] = $locator[$parameterName];
                continue;
            }

            $arguments[] = null;
        }

        return $arguments;
    }

    /**
     * Retrieves a series of ReflectionParameter objects for the static 'create' method of the given
     * tag handler class name.
     *
     * @param string $handlerClassName
     *
     * @return \ReflectionParameter[]
     */
    private function fetchParametersForHandlerFactoryMethod($handlerClassName)
    {
        if (! isset($this->tagHandlerParameterCache[$handlerClassName])) {
            $methodReflection                                  = new \ReflectionMethod($handlerClassName, 'create');
            $this->tagHandlerParameterCache[$handlerClassName] = $methodReflection->getParameters();
        }

        return $this->tagHandlerParameterCache[$handlerClassName];
    }

    /**
     * Returns a copy of this class' Service Locator with added dynamic parameters, such as the tag's name, body and
     * Context.
     *
     * @param TypeContext $context The Context (namespace and aliasses) that may be passed and is used to resolve FQSENs.
     * @param string      $tagName The name of the tag that may be passed onto the factory method of the Tag class.
     * @param string      $tagBody The body of the tag that may be passed onto the factory method of the Tag class.
     *
     * @return mixed[]
     */
    private function getServiceLocatorWithDynamicParameters(TypeContext $context, $tagName, $tagBody)
    {
        $locator = array_merge(
            $this->serviceLocator,
            [
                'name'             => $tagName,
                'body'             => $tagBody,
                TypeContext::class => $context
            ]
        );

        return $locator;
    }

    /**
     * Returns whether the given tag belongs to an annotation.
     *
     * @param string $tagContent
     *
     * @todo this method should be populated once we implement Annotation notation support.
     *
     * @return bool
     */
    private function isAnnotation($tagContent)
    {
        // 1. Contains a namespace separator
        // 2. Contains parenthesis
        // 3. Is present in a list of known annotations (make the algorithm smart by first checking is the last part
        //    of the annotation class name matches the found tag name

        return false;
    }
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection\DocBlock;

use phpDocumentor\Reflection\DocBlock\Tags\Formatter;
use phpDocumentor\Reflection\DocBlock\Tags\Formatter\PassthroughFormatter;
use Webmozart\Assert\Assert;

/**
 * Object representing to description for a DocBlock.
 *
 * A Description object can consist of plain text but can also include tags. A Description Formatter can then combine
 * a body template with sprintf-style placeholders together with formatted tags in order to reconstitute a complete
 * description text using the format that you would prefer.
 *
 * Because parsing a Description text can be a verbose process this is handled by the {@see DescriptionFactory}. It is
 * thus recommended to use that to create a Description object, like this:
 *
 *     $description = $descriptionFactory->create('This is a {@see Description}', $context);
 *
 * The description factory will interpret the given body and create a body template and list of tags from them, and pass
 * that onto the constructor if this class.
 *
 * > The $context variable is a class of type {@see \phpDocumentor\Reflection\Types\Context} and contains the namespace
 * > and the namespace aliases that apply to this DocBlock. These are used by the Factory to resolve and expand partial
 * > type names and FQSENs.
 *
 * If you do not want to use the DescriptionFactory you can pass a body template and tag listing like this:
 *
 *     $description = new Description(
 *         'This is a %1$s',
 *         [ new See(new Fqsen('\phpDocumentor\Reflection\DocBlock\Description')) ]
 *     );
 *
 * It is generally recommended to use the Factory as that will also apply escaping rules, while the Description object
 * is mainly responsible for rendering.
 *
 * @see DescriptionFactory to create a new Description.
 * @see Description\Formatter for the formatting of the body and tags.
 */
class Description
{
    /** @var string */
    private $bodyTemplate;

    /** @var Tag[] */
    private $tags;

    /**
     * Initializes a Description with its body (template) and a listing of the tags used in the body template.
     *
     * @param string $bodyTemplate
     * @param Tag[] $tags
     */
    public function __construct($bodyTemplate, array $tags = [])
    {
        Assert::string($bodyTemplate);

        $this->bodyTemplate = $bodyTemplate;
        $this->tags = $tags;
    }

    /**
     * Returns the tags for this DocBlock.
     *
     * @return Tag[]
     */
    public function getTags()
    {
        return $this->tags;
    }

    /**
     * Renders this description as a string where the provided formatter will format the tags in the expected string
     * format.
     *
     * @param Formatter|null $formatter
     *
     * @return string
     */
    public function render(Formatter $formatter = null)
    {
        if ($formatter === null) {
            $formatter = new PassthroughFormatter();
        }

        $tags = [];
        foreach ($this->tags as $tag) {
            $tags[] = '{' . $formatter->format($tag) . '}';
        }

        return vsprintf($this->bodyTemplate, $tags);
    }

    /**
     * Returns a plain string representation of this description.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->render();
    }
}
<?php
namespace phpDocumentor\Reflection;

interface DocBlockFactoryInterface
{
    /**
     * Factory method for easy instantiation.
     *
     * @param string[] $additionalTags
     *
     * @return DocBlockFactory
     */
    public static function createInstance(array $additionalTags = []);

    /**
     * @param string $docblock
     * @param Types\Context $context
     * @param Location $location
     *
     * @return DocBlock
     */
    public function create($docblock, Types\Context $context = null, Location $location = null);
}
<?php
/**
 * This file is part of phpDocumentor.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
 * @link      http://phpdoc.org
 */

namespace phpDocumentor\Reflection;

use phpDocumentor\Reflection\DocBlock\Tag;
use Webmozart\Assert\Assert;

final class DocBlock
{
    /** @var string The opening line for this docblock. */
    private $summary = '';

    /** @var DocBlock\Description The actual description for this docblock. */
    private $description = null;

    /** @var Tag[] An array containing all the tags in this docblock; except inline. */
    private $tags = [];

    /** @var Types\Context Information about the context of this DocBlock. */
    private $context = null;

    /** @var Location Information about the location of this DocBlock. */
    private $location = null;

    /** @var bool Is this DocBlock (the start of) a template? */
    private $isTemplateStart = false;

    /** @var bool Does this DocBlock signify the end of a DocBlock template? */
    private $isTemplateEnd = false;

    /**
     * @param string $summary
     * @param DocBlock\Description $description
     * @param DocBlock\Tag[] $tags
     * @param Types\Context $context The context in which the DocBlock occurs.
     * @param Location $location The location within the file that this DocBlock occurs in.
     * @param bool $isTemplateStart
     * @param bool $isTemplateEnd
     */
    public function __construct(
        $summary = '',
        DocBlock\Description $description = null,
        array $tags = [],
        Types\Context $context = null,
        Location $location = null,
        $isTemplateStart = false,
        $isTemplateEnd = false
    ) {
        Assert::string($summary);
        Assert::boolean($isTemplateStart);
        Assert::boolean($isTemplateEnd);
        Assert::allIsInstanceOf($tags, Tag::class);

        $this->summary = $summary;
        $this->description = $description ?: new DocBlock\Description('');
        foreach ($tags as $tag) {
            $this->addTag($tag);
        }

        $this->context = $context;
        $this->location = $location;

        $this->isTemplateEnd = $isTemplateEnd;
        $this->isTemplateStart = $isTemplateStart;
    }

    /**
     * @return string
     */
    public function getSummary()
    {
        return $this->summary;
    }

    /**
     * @return DocBlock\Description
     */
    public function getDescription()
    {
        return $this->description;
    }

    /**
     * Returns the current context.
     *
     * @return Types\Context
     */
    public function getContext()
    {
        return $this->context;
    }

    /**
     * Returns the current location.
     *
     * @return Location
     */
    public function getLocation()
    {
        return $this->location;
    }

    /**
     * Returns whether this DocBlock is the start of a Template section.
     *
     * A Docblock may serve as template for a series of subsequent DocBlocks. This is indicated by a special marker
     * (`#@+`) that is appended directly after the opening `/**` of a DocBlock.
     *
     * An example of such an opening is:
     *
     * ```
     * /**#@+
     *  * My DocBlock
     *  * /
     * ```
     *
     * The description and tags (not the summary!) are copied onto all subsequent DocBlocks and also applied to all
     * elements that follow until another DocBlock is found that contains the closing marker (`#@-`).
     *
     * @see self::isTemplateEnd() for the check whether a closing marker was provided.
     *
     * @return boolean
     */
    public function isTemplateStart()
    {
        return $this->isTemplateStart;
    }

    /**
     * Returns whether this DocBlock is the end of a Template section.
     *
     * @see self::isTemplateStart() for a more complete description of the Docblock Template functionality.
     *
     * @return boolean
     */
    public function isTemplateEnd()
    {
        return $this->isTemplateEnd;
    }

    /**
     * Returns the tags for this DocBlock.
     *
     * @return Tag[]
     */
    public function getTags()
    {
        return $this->tags;
    }

    /**
     * Returns an array of tags matching the given name. If no tags are found
     * an empty array is returned.
     *
     * @param string $name String to search by.
     *
     * @return Tag[]
     */
    public function getTagsByName($name)
    {
        Assert::string($name);

        $result = [];

        /** @var Tag $tag */
        foreach ($this->getTags() as $tag) {
            if ($tag->getName() !== $name) {
                continue;
            }

            $result[] = $tag;
        }

        return $result;
    }

    /**
     * Checks if a tag of a certain type is present in this DocBlock.
     *
     * @param string $name Tag name to check for.
     *
     * @return bool
     */
    public function hasTag($name)
    {
        Assert::string($name);

        /** @var Tag $tag */
        foreach ($this->getTags() as $tag) {
            if ($tag->getName() === $name) {
                return true;
            }
        }

        return false;
    }

    /**
     * Remove a tag from this DocBlock.
     *
     * @param Tag $tag The tag to remove.
     *
     * @return void
     */
    public function removeTag(Tag $tagToRemove)
    {
        foreach ($this->tags as $key => $tag) {
            if ($tag === $tagToRemove) {
                unset($this->tags[$key]);
                break;
            }
        }
    }

    /**
     * Adds a tag to this DocBlock.
     *
     * @param Tag $tag The tag to add.
     *
     * @return void
     */
    private function addTag(Tag $tag)
    {
        $this->tags[] = $tag;
    }
}
{
    "bootstrap": "vendor/autoload.php",
    "path": "tests/DoctrineTest/InstantiatorPerformance"
}
{
    "name":              "doctrine/instantiator",
    "description":       "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
    "type":              "library",
    "license":           "MIT",
    "homepage":          "https://www.doctrine-project.org/projects/instantiator.html",
    "keywords":          [
        "instantiate",
        "constructor"
    ],
    "authors": [
        {
            "name":     "Marco Pivetta",
            "email":    "ocramius@gmail.com",
            "homepage": "http://ocramius.github.com/"
        }
    ],
    "require": {
        "php": "^7.1"
    },
    "require-dev": {
        "ext-phar":                  "*",
        "ext-pdo":                   "*",
        "doctrine/coding-standard":  "^6.0",
        "phpbench/phpbench":         "^0.13",
        "phpstan/phpstan-phpunit":   "^0.11",
        "phpstan/phpstan-shim":      "^0.11",
        "phpunit/phpunit":           "^7.0"
    },
    "autoload": {
        "psr-4": {
            "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
        }
    },
    "autoload-dev": {
        "psr-0": {
            "DoctrineTest\\InstantiatorPerformance\\": "tests",
            "DoctrineTest\\InstantiatorTest\\": "tests",
            "DoctrineTest\\InstantiatorTestAsset\\": "tests"
        }
    },
    "extra": {
        "branch-alias": {
            "dev-master": "1.2.x-dev"
        }
    }
}
<?php

namespace Doctrine\Instantiator;

use Doctrine\Instantiator\Exception\InvalidArgumentException;
use Doctrine\Instantiator\Exception\UnexpectedValueException;
use Exception;
use ReflectionClass;
use ReflectionException;
use function class_exists;
use function restore_error_handler;
use function set_error_handler;
use function sprintf;
use function strlen;
use function unserialize;

/**
 * {@inheritDoc}
 */
final class Instantiator implements InstantiatorInterface
{
    /**
     * Markers used internally by PHP to define whether {@see \unserialize} should invoke
     * the method {@see \Serializable::unserialize()} when dealing with classes implementing
     * the {@see \Serializable} interface.
     */
    public const SERIALIZATION_FORMAT_USE_UNSERIALIZER   = 'C';
    public const SERIALIZATION_FORMAT_AVOID_UNSERIALIZER = 'O';

    /**
     * Used to instantiate specific classes, indexed by class name.
     *
     * @var callable[]
     */
    private static $cachedInstantiators = [];

    /**
     * Array of objects that can directly be cloned, indexed by class name.
     *
     * @var object[]
     */
    private static $cachedCloneables = [];

    /**
     * {@inheritDoc}
     */
    public function instantiate($className)
    {
        if (isset(self::$cachedCloneables[$className])) {
            return clone self::$cachedCloneables[$className];
        }

        if (isset(self::$cachedInstantiators[$className])) {
            $factory = self::$cachedInstantiators[$className];

            return $factory();
        }

        return $this->buildAndCacheFromFactory($className);
    }

    /**
     * Builds the requested object and caches it in static properties for performance
     *
     * @return object
     */
    private function buildAndCacheFromFactory(string $className)
    {
        $factory  = self::$cachedInstantiators[$className] = $this->buildFactory($className);
        $instance = $factory();

        if ($this->isSafeToClone(new ReflectionClass($instance))) {
            self::$cachedCloneables[$className] = clone $instance;
        }

        return $instance;
    }

    /**
     * Builds a callable capable of instantiating the given $className without
     * invoking its constructor.
     *
     * @throws InvalidArgumentException
     * @throws UnexpectedValueException
     * @throws ReflectionException
     */
    private function buildFactory(string $className) : callable
    {
        $reflectionClass = $this->getReflectionClass($className);

        if ($this->isInstantiableViaReflection($reflectionClass)) {
            return [$reflectionClass, 'newInstanceWithoutConstructor'];
        }

        $serializedString = sprintf(
            '%s:%d:"%s":0:{}',
            self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER,
            strlen($className),
            $className
        );

        $this->checkIfUnSerializationIsSupported($reflectionClass, $serializedString);

        return static function () use ($serializedString) {
            return unserialize($serializedString);
        };
    }

    /**
     * @param string $className
     *
     * @throws InvalidArgumentException
     * @throws ReflectionException
     */
    private function getReflectionClass($className) : ReflectionClass
    {
        if (! class_exists($className)) {
            throw InvalidArgumentException::fromNonExistingClass($className);
        }

        $reflection = new ReflectionClass($className);

        if ($reflection->isAbstract()) {
            throw InvalidArgumentException::fromAbstractClass($reflection);
        }

        return $reflection;
    }

    /**
     * @throws UnexpectedValueException
     */
    private function checkIfUnSerializationIsSupported(ReflectionClass $reflectionClass, string $serializedString) : void
    {
        set_error_handler(static function ($code, $message, $file, $line) use ($reflectionClass, & $error) : void {
            $error = UnexpectedValueException::fromUncleanUnSerialization(
                $reflectionClass,
                $message,
                $code,
                $file,
                $line
            );
        });

        try {
            $this->attemptInstantiationViaUnSerialization($reflectionClass, $serializedString);
        } finally {
            restore_error_handler();
        }

        if ($error) {
            throw $error;
        }
    }

    /**
     * @throws UnexpectedValueException
     */
    private function attemptInstantiationViaUnSerialization(ReflectionClass $reflectionClass, string $serializedString) : void
    {
        try {
            unserialize($serializedString);
        } catch (Exception $exception) {
            throw UnexpectedValueException::fromSerializationTriggeredException($reflectionClass, $exception);
        }
    }

    private function isInstantiableViaReflection(ReflectionClass $reflectionClass) : bool
    {
        return ! ($this->hasInternalAncestors($reflectionClass) && $reflectionClass->isFinal());
    }

    /**
     * Verifies whether the given class is to be considered internal
     */
    private function hasInternalAncestors(ReflectionClass $reflectionClass) : bool
    {
        do {
            if ($reflectionClass->isInternal()) {
                return true;
            }

            $reflectionClass = $reflectionClass->getParentClass();
        } while ($reflectionClass);

        return false;
    }

    /**
     * Checks if a class is cloneable
     *
     * Classes implementing `__clone` cannot be safely cloned, as that may cause side-effects.
     */
    private function isSafeToClone(ReflectionClass $reflection) : bool
    {
        return $reflection->isCloneable() && ! $reflection->hasMethod('__clone');
    }
}
<?php

namespace Doctrine\Instantiator;

use Doctrine\Instantiator\Exception\ExceptionInterface;

/**
 * Instantiator provides utility methods to build objects without invoking their constructors
 */
interface InstantiatorInterface
{
    /**
     * @param string $className
     *
     * @return object
     *
     * @throws ExceptionInterface
     */
    public function instantiate($className);
}
<?php

namespace Doctrine\Instantiator\Exception;

use Throwable;

/**
 * Base exception marker interface for the instantiator component
 */
interface ExceptionInterface extends Throwable
{
}
<?php

namespace Doctrine\Instantiator\Exception;

use Exception;
use ReflectionClass;
use UnexpectedValueException as BaseUnexpectedValueException;
use function sprintf;

/**
 * Exception for given parameters causing invalid/unexpected state on instantiation
 */
class UnexpectedValueException extends BaseUnexpectedValueException implements ExceptionInterface
{
    public static function fromSerializationTriggeredException(
        ReflectionClass $reflectionClass,
        Exception $exception
    ) : self {
        return new self(
            sprintf(
                'An exception was raised while trying to instantiate an instance of "%s" via un-serialization',
                $reflectionClass->getName()
            ),
            0,
            $exception
        );
    }

    public static function fromUncleanUnSerialization(
        ReflectionClass $reflectionClass,
        string $errorString,
        int $errorCode,
        string $errorFile,
        int $errorLine
    ) : self {
        return new self(
            sprintf(
                'Could not produce an instance of "%s" via un-serialization, since an error was triggered '
                . 'in file "%s" at line "%d"',
                $reflectionClass->getName(),
                $errorFile,
                $errorLine
            ),
            0,
            new Exception($errorString, $errorCode)
        );
    }
}
<?php

namespace Doctrine\Instantiator\Exception;

use InvalidArgumentException as BaseInvalidArgumentException;
use ReflectionClass;
use const PHP_VERSION_ID;
use function interface_exists;
use function sprintf;
use function trait_exists;

/**
 * Exception for invalid arguments provided to the instantiator
 */
class InvalidArgumentException extends BaseInvalidArgumentException implements ExceptionInterface
{
    public static function fromNonExistingClass(string $className) : self
    {
        if (interface_exists($className)) {
            return new self(sprintf('The provided type "%s" is an interface, and can not be instantiated', $className));
        }

        if (PHP_VERSION_ID >= 50400 && trait_exists($className)) {
            return new self(sprintf('The provided type "%s" is a trait, and can not be instantiated', $className));
        }

        return new self(sprintf('The provided class "%s" does not exist', $className));
    }

    public static function fromAbstractClass(ReflectionClass $reflectionClass) : self
    {
        return new self(sprintf(
            'The provided class "%s" is abstract, and can not be instantiated',
            $reflectionClass->getName()
        ));
    }
}
{
    "name": "firebase/php-jwt",
    "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
    "homepage": "https://github.com/firebase/php-jwt",
    "authors": [
        {
            "name": "Neuman Vong",
            "email": "neuman+pear@twilio.com",
            "role": "Developer"
        },
        {
            "name": "Anant Narayanan",
            "email": "anant@php.net",
            "role": "Developer"
        }
    ],
    "license": "BSD-3-Clause",
    "require": {
        "php": ">=5.3.0"
    },
    "autoload": {
        "psr-4": {
            "Firebase\\JWT\\": "src"
        }
    },
    "require-dev": {
        "phpunit/phpunit": " 4.8.35"
    }
}
<?php
namespace Firebase\JWT;

class SignatureInvalidException extends \UnexpectedValueException
{

}
<?php
namespace Firebase\JWT;

class BeforeValidException extends \UnexpectedValueException
{

}
<?php
namespace Firebase\JWT;

class ExpiredException extends \UnexpectedValueException
{

}
<?php

namespace Firebase\JWT;
use \DomainException;
use \InvalidArgumentException;
use \UnexpectedValueException;
use \DateTime;

/**
 * JSON Web Token implementation, based on this spec:
 * https://tools.ietf.org/html/rfc7519
 *
 * PHP version 5
 *
 * @category Authentication
 * @package  Authentication_JWT
 * @author   Neuman Vong <neuman@twilio.com>
 * @author   Anant Narayanan <anant@php.net>
 * @license  http://opensource.org/licenses/BSD-3-Clause 3-clause BSD
 * @link     https://github.com/firebase/php-jwt
 */
class JWT
{

    /**
     * When checking nbf, iat or expiration times,
     * we want to provide some extra leeway time to
     * account for clock skew.
     */
    public static $leeway = 0;

    /**
     * Allow the current timestamp to be specified.
     * Useful for fixing a value within unit testing.
     *
     * Will default to PHP time() value if null.
     */
    public static $timestamp = null;

    public static $supported_algs = array(
        'HS256' => array('hash_hmac', 'SHA256'),
        'HS512' => array('hash_hmac', 'SHA512'),
        'HS384' => array('hash_hmac', 'SHA384'),
        'RS256' => array('openssl', 'SHA256'),
        'RS384' => array('openssl', 'SHA384'),
        'RS512' => array('openssl', 'SHA512'),
    );

    /**
     * Decodes a JWT string into a PHP object.
     *
     * @param string        $jwt            The JWT
     * @param string|array  $key            The key, or map of keys.
     *                                      If the algorithm used is asymmetric, this is the public key
     * @param array         $allowed_algs   List of supported verification algorithms
     *                                      Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'
     *
     * @return object The JWT's payload as a PHP object
     *
     * @throws UnexpectedValueException     Provided JWT was invalid
     * @throws SignatureInvalidException    Provided JWT was invalid because the signature verification failed
     * @throws BeforeValidException         Provided JWT is trying to be used before it's eligible as defined by 'nbf'
     * @throws BeforeValidException         Provided JWT is trying to be used before it's been created as defined by 'iat'
     * @throws ExpiredException             Provided JWT has since expired, as defined by the 'exp' claim
     *
     * @uses jsonDecode
     * @uses urlsafeB64Decode
     */
    public static function decode($jwt, $key, array $allowed_algs = array())
    {
        $timestamp = is_null(static::$timestamp) ? time() : static::$timestamp;

        if (empty($key)) {
            throw new InvalidArgumentException('Key may not be empty');
        }
        $tks = explode('.', $jwt);
        if (count($tks) != 3) {
            throw new UnexpectedValueException('Wrong number of segments');
        }
        list($headb64, $bodyb64, $cryptob64) = $tks;
        if (null === ($header = static::jsonDecode(static::urlsafeB64Decode($headb64)))) {
            throw new UnexpectedValueException('Invalid header encoding');
        }
        if (null === $payload = static::jsonDecode(static::urlsafeB64Decode($bodyb64))) {
            throw new UnexpectedValueException('Invalid claims encoding');
        }
        if (false === ($sig = static::urlsafeB64Decode($cryptob64))) {
            throw new UnexpectedValueException('Invalid signature encoding');
        }
        if (empty($header->alg)) {
            throw new UnexpectedValueException('Empty algorithm');
        }
        if (empty(static::$supported_algs[$header->alg])) {
            throw new UnexpectedValueException('Algorithm not supported');
        }
        if (!in_array($header->alg, $allowed_algs)) {
            throw new UnexpectedValueException('Algorithm not allowed');
        }
        if (is_array($key) || $key instanceof \ArrayAccess) {
            if (isset($header->kid)) {
                if (!isset($key[$header->kid])) {
                    throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key');
                }
                $key = $key[$header->kid];
            } else {
                throw new UnexpectedValueException('"kid" empty, unable to lookup correct key');
            }
        }

        // Check the signature
        if (!static::verify("$headb64.$bodyb64", $sig, $key, $header->alg)) {
            throw new SignatureInvalidException('Signature verification failed');
        }

        // Check if the nbf if it is defined. This is the time that the
        // token can actually be used. If it's not yet that time, abort.
        if (isset($payload->nbf) && $payload->nbf > ($timestamp + static::$leeway)) {
            throw new BeforeValidException(
                'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->nbf)
            );
        }

        // Check that this token has been created before 'now'. This prevents
        // using tokens that have been created for later use (and haven't
        // correctly used the nbf claim).
        if (isset($payload->iat) && $payload->iat > ($timestamp + static::$leeway)) {
            throw new BeforeValidException(
                'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->iat)
            );
        }

        // Check if this token has expired.
        if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) {
            throw new ExpiredException('Expired token');
        }

        return $payload;
    }

    /**
     * Converts and signs a PHP object or array into a JWT string.
     *
     * @param object|array  $payload    PHP object or array
     * @param string        $key        The secret key.
     *                                  If the algorithm used is asymmetric, this is the private key
     * @param string        $alg        The signing algorithm.
     *                                  Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'
     * @param mixed         $keyId
     * @param array         $head       An array with header elements to attach
     *
     * @return string A signed JWT
     *
     * @uses jsonEncode
     * @uses urlsafeB64Encode
     */
    public static function encode($payload, $key, $alg = 'HS256', $keyId = null, $head = null)
    {
        $header = array('typ' => 'JWT', 'alg' => $alg);
        if ($keyId !== null) {
            $header['kid'] = $keyId;
        }
        if ( isset($head) && is_array($head) ) {
            $header = array_merge($head, $header);
        }
        $segments = array();
        $segments[] = static::urlsafeB64Encode(static::jsonEncode($header));
        $segments[] = static::urlsafeB64Encode(static::jsonEncode($payload));
        $signing_input = implode('.', $segments);

        $signature = static::sign($signing_input, $key, $alg);
        $segments[] = static::urlsafeB64Encode($signature);

        return implode('.', $segments);
    }

    /**
     * Sign a string with a given key and algorithm.
     *
     * @param string            $msg    The message to sign
     * @param string|resource   $key    The secret key
     * @param string            $alg    The signing algorithm.
     *                                  Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'
     *
     * @return string An encrypted message
     *
     * @throws DomainException Unsupported algorithm was specified
     */
    public static function sign($msg, $key, $alg = 'HS256')
    {
        if (empty(static::$supported_algs[$alg])) {
            throw new DomainException('Algorithm not supported');
        }
        list($function, $algorithm) = static::$supported_algs[$alg];
        switch($function) {
            case 'hash_hmac':
                return hash_hmac($algorithm, $msg, $key, true);
            case 'openssl':
                $signature = '';
                $success = openssl_sign($msg, $signature, $key, $algorithm);
                if (!$success) {
                    throw new DomainException("OpenSSL unable to sign data");
                } else {
                    return $signature;
                }
        }
    }

    /**
     * Verify a signature with the message, key and method. Not all methods
     * are symmetric, so we must have a separate verify and sign method.
     *
     * @param string            $msg        The original message (header and body)
     * @param string            $signature  The original signature
     * @param string|resource   $key        For HS*, a string key works. for RS*, must be a resource of an openssl public key
     * @param string            $alg        The algorithm
     *
     * @return bool
     *
     * @throws DomainException Invalid Algorithm or OpenSSL failure
     */
    private static function verify($msg, $signature, $key, $alg)
    {
        if (empty(static::$supported_algs[$alg])) {
            throw new DomainException('Algorithm not supported');
        }

        list($function, $algorithm) = static::$supported_algs[$alg];
        switch($function) {
            case 'openssl':
                $success = openssl_verify($msg, $signature, $key, $algorithm);
                if ($success === 1) {
                    return true;
                } elseif ($success === 0) {
                    return false;
                }
                // returns 1 on success, 0 on failure, -1 on error.
                throw new DomainException(
                    'OpenSSL error: ' . openssl_error_string()
                );
            case 'hash_hmac':
            default:
                $hash = hash_hmac($algorithm, $msg, $key, true);
                if (function_exists('hash_equals')) {
                    return hash_equals($signature, $hash);
                }
                $len = min(static::safeStrlen($signature), static::safeStrlen($hash));

                $status = 0;
                for ($i = 0; $i < $len; $i++) {
                    $status |= (ord($signature[$i]) ^ ord($hash[$i]));
                }
                $status |= (static::safeStrlen($signature) ^ static::safeStrlen($hash));

                return ($status === 0);
        }
    }

    /**
     * Decode a JSON string into a PHP object.
     *
     * @param string $input JSON string
     *
     * @return object Object representation of JSON string
     *
     * @throws DomainException Provided string was invalid JSON
     */
    public static function jsonDecode($input)
    {
        if (version_compare(PHP_VERSION, '5.4.0', '>=') && !(defined('JSON_C_VERSION') && PHP_INT_SIZE > 4)) {
            /** In PHP >=5.4.0, json_decode() accepts an options parameter, that allows you
             * to specify that large ints (like Steam Transaction IDs) should be treated as
             * strings, rather than the PHP default behaviour of converting them to floats.
             */
            $obj = json_decode($input, false, 512, JSON_BIGINT_AS_STRING);
        } else {
            /** Not all servers will support that, however, so for older versions we must
             * manually detect large ints in the JSON string and quote them (thus converting
             *them to strings) before decoding, hence the preg_replace() call.
             */
            $max_int_length = strlen((string) PHP_INT_MAX) - 1;
            $json_without_bigints = preg_replace('/:\s*(-?\d{'.$max_int_length.',})/', ': "$1"', $input);
            $obj = json_decode($json_without_bigints);
        }

        if (function_exists('json_last_error') && $errno = json_last_error()) {
            static::handleJsonError($errno);
        } elseif ($obj === null && $input !== 'null') {
            throw new DomainException('Null result with non-null input');
        }
        return $obj;
    }

    /**
     * Encode a PHP object into a JSON string.
     *
     * @param object|array $input A PHP object or array
     *
     * @return string JSON representation of the PHP object or array
     *
     * @throws DomainException Provided object could not be encoded to valid JSON
     */
    public static function jsonEncode($input)
    {
        $json = json_encode($input);
        if (function_exists('json_last_error') && $errno = json_last_error()) {
            static::handleJsonError($errno);
        } elseif ($json === 'null' && $input !== null) {
            throw new DomainException('Null result with non-null input');
        }
        return $json;
    }

    /**
     * Decode a string with URL-safe Base64.
     *
     * @param string $input A Base64 encoded string
     *
     * @return string A decoded string
     */
    public static function urlsafeB64Decode($input)
    {
        $remainder = strlen($input) % 4;
        if ($remainder) {
            $padlen = 4 - $remainder;
            $input .= str_repeat('=', $padlen);
        }
        return base64_decode(strtr($input, '-_', '+/'));
    }

    /**
     * Encode a string with URL-safe Base64.
     *
     * @param string $input The string you want encoded
     *
     * @return string The base64 encode of what you passed in
     */
    public static function urlsafeB64Encode($input)
    {
        return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
    }

    /**
     * Helper method to create a JSON error.
     *
     * @param int $errno An error number from json_last_error()
     *
     * @return void
     */
    private static function handleJsonError($errno)
    {
        $messages = array(
            JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
            JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON',
            JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
            JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON',
            JSON_ERROR_UTF8 => 'Malformed UTF-8 characters' //PHP >= 5.3.3
        );
        throw new DomainException(
            isset($messages[$errno])
            ? $messages[$errno]
            : 'Unknown JSON error: ' . $errno
        );
    }

    /**
     * Get the number of bytes in cryptographic strings.
     *
     * @param string
     *
     * @return int
     */
    private static function safeStrlen($str)
    {
        if (function_exists('mb_strlen')) {
            return mb_strlen($str, '8bit');
        }
        return strlen($str);
    }
}
<?php
/* Phinx
 *
 * (The MIT license)
 * Copyright (c) 2014 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

$autoloader = require __DIR__ . '/../src/composer_autoloader.php';

if (!$autoloader()) {
    die(
        'You need to set up the project dependencies using the following commands:' . PHP_EOL .
        'curl -sS https://getcomposer.org/installer | php' . PHP_EOL .
        'php composer.phar install' . PHP_EOL
    );
}

return new Phinx\Console\PhinxApplication();
<?php
/* Phinx
 *
 * (The MIT license)
 * Copyright (c) 2014 Rob Morgan
 * Copyright (c) 2014 Woody Gilk
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

// This script can be run as a router with the built in PHP web server:
//
//   php -S localhost:8000 app/web.php
//
// Or can be run from any other web server with:
//
//   require 'phinx/app/web.php';
//
// This script uses the following query string arguments:
//
// - (string) "e" environment name
// - (string) "t" target version
// - (boolean) "debug" enable debugging?

// Get the phinx console application and inject it into TextWrapper.
$app = require __DIR__ . '/phinx.php';
$wrap = new Phinx\Wrapper\TextWrapper($app);

// Mapping of route names to commands.
$routes = [
    'status' => 'getStatus',
    'migrate' => 'getMigrate',
    'rollback' => 'getRollback',
];

// Extract the requested command from the URL, default to "status".
$command = trim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), '/');
if (!$command) {
    $command = 'status';
}

// Verify that the command exists, or list available commands.
if (!isset($routes[$command])) {
    $commands = implode(', ', array_keys($routes));
    header('Content-Type: text/plain', true, 404);
    die("Command not found! Valid commands are: {$commands}.");
}

// Get the environment and target version parameters.
$env = isset($_GET['e']) ? $_GET['e'] : null;
$target = isset($_GET['t']) ? $_GET['t'] : null;

// Check if debugging is enabled.
$debug = !empty($_GET['debug']) && filter_var($_GET['debug'], FILTER_VALIDATE_BOOLEAN);

// Execute the command and determine if it was successful.
$output = call_user_func([$wrap, $routes[$command]], $env, $target);
$error = $wrap->getExitCode() > 0;

// Finally, display the output of the command.
header('Content-Type: text/plain', true, $error ? 500 : 200);
if ($debug) {
    // Show what command was executed based on request parameters.
    $args = implode(', ', [var_export($env, true), var_export($target, true)]);
    echo "DEBUG: $command($args)" . PHP_EOL . PHP_EOL;
}
echo $output;
{
    "name": "robmorgan/phinx",
    "type": "library",
    "description": "Phinx makes it ridiculously easy to manage the database migrations for your PHP app.",
    "keywords": ["phinx", "migrations", "database", "db", "database migrations"],
    "homepage": "https://phinx.org",
    "license": "MIT",
    "version": "0.9.2",
    "authors": [{
        "name": "Rob Morgan",
        "email": "robbym@gmail.com",
        "homepage": "https://robmorgan.id.au",
        "role": "Lead Developer"
    }, {
        "name": "Woody Gilk",
        "email": "woody.gilk@gmail.com",
        "homepage": "http://shadowhand.me",
        "role": "Developer"
    }, {
        "name": "Richard Quadling",
        "email": "rquadling@gmail.com",
        "role": "Developer"
    }, {
        "name": "CakePHP Community",
        "homepage": "https://github.com/cakephp/phinx/graphs/contributors"
    }],
    "require": {
        "php": ">=5.4",
        "symfony/console": "^2.8|^3.0|^4.0",
        "symfony/config": "^2.8|^3.0|^4.0",
        "symfony/yaml": "^2.8|^3.0|^4.0"
    },
    "require-dev": {
        "phpunit/phpunit": "^4.8.35|^5.7|^6.5",
        "cakephp/cakephp-codesniffer": "^3.0"
    },
    "autoload": {
        "psr-4": {
            "Phinx\\": "src/Phinx"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Test\\Phinx\\": "tests/Phinx"
        }
    },
    "scripts": {
        "check": [
            "@test",
            "@cs-check"
        ],
        "cs-check": "phpcs --colors -p --standard=vendor/cakephp/cakephp-codesniffer/CakePHP ./src ./tests ./app",
        "cs-fix": "phpcbf --colors --standard=vendor/cakephp/cakephp-codesniffer/CakePHP ./src ./tests ./app",
        "test": "phpunit --colors=always"
    },
    "bin": ["bin/phinx"]
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Seed
 */
namespace Phinx\Seed;

use Phinx\Db\Adapter\AdapterInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Seed interface
 *
 * @author Rob Morgan <robbym@gmail.com>
 */
interface SeedInterface
{
    /**
     * @var string
     */
    const RUN = 'run';

    /**
     * Run the seeder.
     *
     * @return void
     */
    public function run();

    /**
     * Sets the database adapter.
     *
     * @param \Phinx\Db\Adapter\AdapterInterface $adapter Database Adapter
     * @return \Phinx\Seed\SeedInterface
     */
    public function setAdapter(AdapterInterface $adapter);

    /**
     * Gets the database adapter.
     *
     * @return \Phinx\Db\Adapter\AdapterInterface
     */
    public function getAdapter();

    /**
     * Sets the input object to be used in migration object
     *
     * @param \Symfony\Component\Console\Input\InputInterface $input
     * @return \Phinx\Seed\SeedInterface
     */
    public function setInput(InputInterface $input);

    /**
     * Gets the input object to be used in migration object
     *
     * @return \Symfony\Component\Console\Input\InputInterface
     */
    public function getInput();

    /**
     * Sets the output object to be used in migration object
     *
     * @param \Symfony\Component\Console\Output\OutputInterface $output
     * @return \Phinx\Seed\SeedInterface
     */
    public function setOutput(OutputInterface $output);

    /**
     * Gets the output object to be used in migration object
     *
     * @return \Symfony\Component\Console\Output\OutputInterface
     */
    public function getOutput();

    /**
     * Gets the name.
     *
     * @return string
     */
    public function getName();

    /**
     * Executes a SQL statement and returns the number of affected rows.
     *
     * @param string $sql SQL
     * @return int
     */
    public function execute($sql);

    /**
     * Executes a SQL statement and returns the result as an array.
     *
     * @param string $sql SQL
     * @return array
     */
    public function query($sql);

    /**
     * Executes a query and returns only one row as an array.
     *
     * @param string $sql SQL
     * @return array
     */
    public function fetchRow($sql);

    /**
     * Executes a query and returns an array of rows.
     *
     * @param string $sql SQL
     * @return array
     */
    public function fetchAll($sql);

    /**
     * Insert data into a table.
     *
     * @param string $tableName
     * @param array $data
     * @return void
     */
    public function insert($tableName, $data);

    /**
     * Checks to see if a table exists.
     *
     * @param string $tableName Table Name
     * @return bool
     */
    public function hasTable($tableName);

    /**
     * Returns an instance of the <code>\Table</code> class.
     *
     * You can use this class to create and manipulate tables.
     *
     * @param string $tableName Table Name
     * @param array $options Options
     * @return \Phinx\Db\Table
     */
    public function table($tableName, $options);
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Seed
 */
namespace Phinx\Seed;

use Phinx\Db\Adapter\AdapterInterface;
use Phinx\Db\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Abstract Seed Class.
 *
 * It is expected that the seeds you write extend from this class.
 *
 * This abstract class proxies the various database methods to your specified
 * adapter.
 *
 * @author Rob Morgan <robbym@gmail.com>
 */
abstract class AbstractSeed implements SeedInterface
{
    /**
     * @var \Phinx\Db\Adapter\AdapterInterface
     */
    protected $adapter;

    /**
     * @var \Symfony\Component\Console\Input\InputInterface
     */
    protected $input;

    /**
     * @var \Symfony\Component\Console\Output\OutputInterface
     */
    protected $output;

    /**
     * Class Constructor.
     *
     * @param \Symfony\Component\Console\Input\InputInterface $input
     * @param \Symfony\Component\Console\Output\OutputInterface $output
     */
    final public function __construct(InputInterface $input = null, OutputInterface $output = null)
    {
        if (!is_null($input)) {
            $this->setInput($input);
        }
        if (!is_null($output)) {
            $this->setOutput($output);
        }

        $this->init();
    }

    /**
     * Initialize method.
     *
     * @return void
     */
    protected function init()
    {
    }

    /**
     * {@inheritdoc}
     */
    public function run()
    {
    }

    /**
     * Return seeds dependencies.
     *
     * @return array
     */
    public function getDependencies()
    {
        return [];
    }

    /**
     * {@inheritdoc}
     */
    public function setAdapter(AdapterInterface $adapter)
    {
        $this->adapter = $adapter;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getAdapter()
    {
        return $this->adapter;
    }

    /**
     * {@inheritdoc}
     */
    public function setInput(InputInterface $input)
    {
        $this->input = $input;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getInput()
    {
        return $this->input;
    }

    /**
     * {@inheritdoc}
     */
    public function setOutput(OutputInterface $output)
    {
        $this->output = $output;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getOutput()
    {
        return $this->output;
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return get_class($this);
    }

    /**
     * {@inheritdoc}
     */
    public function execute($sql)
    {
        return $this->getAdapter()->execute($sql);
    }

    /**
     * {@inheritdoc}
     */
    public function query($sql)
    {
        return $this->getAdapter()->query($sql);
    }

    /**
     * {@inheritdoc}
     */
    public function fetchRow($sql)
    {
        return $this->getAdapter()->fetchRow($sql);
    }

    /**
     * {@inheritdoc}
     */
    public function fetchAll($sql)
    {
        return $this->getAdapter()->fetchAll($sql);
    }

    /**
     * {@inheritdoc}
     */
    public function insert($table, $data)
    {
        // convert to table object
        if (is_string($table)) {
            $table = new Table($table, [], $this->getAdapter());
        }
        $table->insert($data)->save();
    }

    /**
     * {@inheritdoc}
     */
    public function hasTable($tableName)
    {
        return $this->getAdapter()->hasTable($tableName);
    }

    /**
     * {@inheritdoc}
     */
    public function table($tableName, $options = [])
    {
        return new Table($tableName, $options, $this->getAdapter());
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Util
 */
namespace Phinx\Util;

class Util
{
    /**
     * @var string
     */
    const DATE_FORMAT = 'YmdHis';

    /**
     * @var string
     */
    const MIGRATION_FILE_NAME_PATTERN = '/^\d+_([\w_]+).php$/i';

    /**
     * @var string
     */
    const SEED_FILE_NAME_PATTERN = '/^([A-Z][a-z0-9]+).php$/i';

    /**
     * Gets the current timestamp string, in UTC.
     *
     * @return string
     */
    public static function getCurrentTimestamp()
    {
        $dt = new \DateTime('now', new \DateTimeZone('UTC'));

        return $dt->format(static::DATE_FORMAT);
    }

    /**
     * Gets an array of all the existing migration class names.
     *
     * @return string[]
     */
    public static function getExistingMigrationClassNames($path)
    {
        $classNames = [];

        if (!is_dir($path)) {
            return $classNames;
        }

        // filter the files to only get the ones that match our naming scheme
        $phpFiles = glob($path . DIRECTORY_SEPARATOR . '*.php');

        foreach ($phpFiles as $filePath) {
            if (preg_match('/([0-9]+)_([_a-z0-9]*).php/', basename($filePath))) {
                $classNames[] = static::mapFileNameToClassName(basename($filePath));
            }
        }

        return $classNames;
    }

    /**
     * Get the version from the beginning of a file name.
     *
     * @param string $fileName File Name
     * @return string
     */
    public static function getVersionFromFileName($fileName)
    {
        $matches = [];
        preg_match('/^[0-9]+/', basename($fileName), $matches);

        return $matches[0];
    }

    /**
     * Turn migration names like 'CreateUserTable' into file names like
     * '12345678901234_create_user_table.php' or 'LimitResourceNamesTo30Chars' into
     * '12345678901234_limit_resource_names_to_30_chars.php'.
     *
     * @param string $className Class Name
     * @return string
     */
    public static function mapClassNameToFileName($className)
    {
        $arr = preg_split('/(?=[A-Z])/', $className);
        unset($arr[0]); // remove the first element ('')
        $fileName = static::getCurrentTimestamp() . '_' . strtolower(implode($arr, '_')) . '.php';

        return $fileName;
    }

    /**
     * Turn file names like '12345678901234_create_user_table.php' into class
     * names like 'CreateUserTable'.
     *
     * @param string $fileName File Name
     * @return string
     */
    public static function mapFileNameToClassName($fileName)
    {
        $matches = [];
        if (preg_match(static::MIGRATION_FILE_NAME_PATTERN, $fileName, $matches)) {
            $fileName = $matches[1];
        }

        return str_replace(' ', '', ucwords(str_replace('_', ' ', $fileName)));
    }

    /**
     * Check if a migration class name is unique regardless of the
     * timestamp.
     *
     * This method takes a class name and a path to a migrations directory.
     *
     * Migration class names must be in CamelCase format.
     * e.g: CreateUserTable or AddIndexToPostsTable.
     *
     * Single words are not allowed on their own.
     *
     * @param string $className Class Name
     * @param string $path Path
     * @return bool
     */
    public static function isUniqueMigrationClassName($className, $path)
    {
        $existingClassNames = static::getExistingMigrationClassNames($path);

        return !(in_array($className, $existingClassNames));
    }

    /**
     * Check if a migration/seed class name is valid.
     *
     * Migration & Seed class names must be in CamelCase format.
     * e.g: CreateUserTable, AddIndexToPostsTable or UserSeeder.
     *
     * Single words are not allowed on their own.
     *
     * @param string $className Class Name
     * @return bool
     */
    public static function isValidPhinxClassName($className)
    {
        return (bool)preg_match('/^([A-Z][a-z0-9]+)+$/', $className);
    }

    /**
     * Check if a migration file name is valid.
     *
     * @param string $fileName File Name
     * @return bool
     */
    public static function isValidMigrationFileName($fileName)
    {
        $matches = [];

        return preg_match(static::MIGRATION_FILE_NAME_PATTERN, $fileName, $matches);
    }

    /**
     * Check if a seed file name is valid.
     *
     * @param string $fileName File Name
     * @return bool
     */
    public static function isValidSeedFileName($fileName)
    {
        $matches = [];

        return preg_match(static::SEED_FILE_NAME_PATTERN, $fileName, $matches);
    }

    /**
     * Expands a set of paths with curly braces (if supported by the OS).
     *
     * @param array $paths
     * @return array
     */
    public static function globAll(array $paths)
    {
        $result = [];

        foreach ($paths as $path) {
            $result = array_merge($result, static::glob($path));
        }

        return $result;
    }

    /**
     * Expands a path with curly braces (if supported by the OS).
     *
     * @param string $path
     * @return array
     */
    public static function glob($path)
    {
        return glob($path, defined('GLOB_BRACE') ? GLOB_BRACE : 0);
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 * Copyright (c) 2015 Woody Gilk
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

namespace Phinx\Wrapper;

use Phinx\Console\PhinxApplication;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\StreamOutput;

/**
 * Phinx text wrapper: a way to run `status`, `migrate`, and `rollback` commands
 * and get the output of the command back as plain text.
 *
 * @author Woody Gilk <woody.gilk@gmail.com>
 */
class TextWrapper
{
    /**
     * @var \Phinx\Console\PhinxApplication
     */
    private $app;

    /**
     * @var array
     */
    private $options = [];

    /**
     * @var integer
     */
    private $exit_code;

    /**
     * @param \Phinx\Console\PhinxApplication $app
     * @param array $options
     */
    public function __construct(PhinxApplication $app, array $options = [])
    {
        $this->app = $app;
        $this->options = $options;
    }

    /**
     * Get the application instance.
     *
     * @return \Phinx\Console\PhinxApplication
     */
    public function getApp()
    {
        return $this->app;
    }

    /**
     * Returns the exit code from the last run command.
     * @return int
     */
    public function getExitCode()
    {
        return $this->exit_code;
    }

    /**
     * Returns the output from running the "status" command.
     * @param  string $env environment name (optional)
     * @return string
     */
    public function getStatus($env = null)
    {
        $command = ['status'];
        if ($env ?: $this->hasOption('environment')) {
            $command += ['-e' => $env ?: $this->getOption('environment')];
        }
        if ($this->hasOption('configuration')) {
            $command += ['-c' => $this->getOption('configuration')];
        }
        if ($this->hasOption('parser')) {
            $command += ['-p' => $this->getOption('parser')];
        }

        return $this->executeRun($command);
    }

    /**
     * Returns the output from running the "migrate" command.
     * @param  string $env environment name (optional)
     * @param  string $target target version (optional)
     * @return string
     */
    public function getMigrate($env = null, $target = null)
    {
        $command = ['migrate'];
        if ($env ?: $this->hasOption('environment')) {
            $command += ['-e' => $env ?: $this->getOption('environment')];
        }
        if ($this->hasOption('configuration')) {
            $command += ['-c' => $this->getOption('configuration')];
        }
        if ($this->hasOption('parser')) {
            $command += ['-p' => $this->getOption('parser')];
        }
        if ($target) {
            $command += ['-t' => $target];
        }

        return $this->executeRun($command);
    }

    /**
     * Returns the output from running the "seed:run" command.
     * @param  string|null       $env environment name
     * @param  string|null       $target target version
     * @param  array|string|null $seed array of seed names or seed name
     * @return string
     */
    public function getSeed($env = null, $target = null, $seed = null)
    {
        $command = ['seed:run'];
        if ($env ?: $this->hasOption('environment')) {
            $command += ['-e' => $env ?: $this->getOption('environment')];
        }
        if ($this->hasOption('configuration')) {
            $command += ['-c' => $this->getOption('configuration')];
        }
        if ($this->hasOption('parser')) {
            $command += ['-p' => $this->getOption('parser')];
        }
        if ($target) {
            $command += ['-t' => $target];
        }
        if ($seed) {
            $seed = (array)$seed;
            $command += ['-s' => $seed];
        }

        return $this->executeRun($command);
    }

    /**
     * Returns the output from running the "rollback" command.
     * @param  string $env environment name (optional)
     * @param  mixed $target target version, or 0 (zero) fully revert (optional)
     * @return string
     */
    public function getRollback($env = null, $target = null)
    {
        $command = ['rollback'];
        if ($env ?: $this->hasOption('environment')) {
            $command += ['-e' => $env ?: $this->getOption('environment')];
        }
        if ($this->hasOption('configuration')) {
            $command += ['-c' => $this->getOption('configuration')];
        }
        if ($this->hasOption('parser')) {
            $command += ['-p' => $this->getOption('parser')];
        }
        if (isset($target)) {
            // Need to use isset() with rollback, because -t0 is a valid option!
            // See http://docs.phinx.org/en/latest/commands.html#the-rollback-command
            $command += ['-t' => $target];
        }

        return $this->executeRun($command);
    }

    /**
     * Check option from options array
     *
     * @param  string $key
     * @return bool
     */
    protected function hasOption($key)
    {
        return isset($this->options[$key]);
    }

    /**
     * Get option from options array
     *
     * @param  string $key
     * @return string|null
     */
    protected function getOption($key)
    {
        if (!isset($this->options[$key])) {
            return null;
        }

        return $this->options[$key];
    }

    /**
     * Set option in options array
     *
     * @param  string $key
     * @param  string $value
     * @return object
     */
    public function setOption($key, $value)
    {
        $this->options[$key] = $value;

        return $this;
    }

    /**
     * Execute a command, capturing output and storing the exit code.
     *
     * @param  array $command
     * @return string
     */
    protected function executeRun(array $command)
    {
        // Output will be written to a temporary stream, so that it can be
        // collected after running the command.
        $stream = fopen('php://temp', 'w+');

        // Execute the command, capturing the output in the temporary stream
        // and storing the exit code for debugging purposes.
        $this->exit_code = $this->app->doRun(new ArrayInput($command), new StreamOutput($stream));

        // Get the output of the command and close the stream, which will
        // destroy the temporary file.
        $result = stream_get_contents($stream, -1, 0);
        fclose($stream);

        return $result;
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2017 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Config
 */

namespace Phinx\Config;

/**
 * Config aware getNamespaceByPath method.
 * @package Phinx\Config
 * @author Andrey N. Mokhov
 */
interface NamespaceAwareInterface
{
    /**
     * Get Migration Namespace associated with path.
     *
     * @param string $path
     * @return string|null
     */
    public function getMigrationNamespaceByPath($path);

    /**
     * Get Seed Namespace associated with path.
     *
     * @param string $path
     * @return string|null
     */
    public function getSeedNamespaceByPath($path);
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Config
 */
namespace Phinx\Config;

/**
 * Phinx configuration interface.
 *
 * @package Phinx
 * @author  Woody Gilk
 */
interface ConfigInterface extends \ArrayAccess
{
    /**
     * Returns the configuration for each environment.
     *
     * This method returns <code>null</code> if no environments exist.
     *
     * @return array|null
     */
    public function getEnvironments();

    /**
     * Returns the configuration for a given environment.
     *
     * This method returns <code>null</code> if the specified environment
     * doesn't exist.
     *
     * @param string $name
     * @return array|null
     */
    public function getEnvironment($name);

    /**
     * Does the specified environment exist in the configuration file?
     *
     * @param string $name Environment Name
     * @return bool
     */
    public function hasEnvironment($name);

    /**
     * Gets the default environment name.
     *
     * @throws \RuntimeException
     * @return string
     */
    public function getDefaultEnvironment();

    /**
     * Get the aliased value from a supplied alias.
     *
     * @param string $alias
     *
     * @return string|null
     */
    public function getAlias($alias);

    /**
     * Gets the config file path.
     *
     * @return string
     */
    public function getConfigFilePath();

    /**
     * Gets the paths to search for migration files.
     *
     * @return string[]
     */
    public function getMigrationPaths();

    /**
     * Gets the paths to search for seed files.
     *
     * @return string[]
     */
    public function getSeedPaths();

    /**
     * Get the template file name.
     *
     * @return string|false
     */
    public function getTemplateFile();

    /**
     * Get the template class name.
     *
     * @return string|false
     */
    public function getTemplateClass();

    /**
     * Get the version order.
     *
     * @return string
     */
    public function getVersionOrder();

    /**
     * Is version order creation time?
     *
     * @return bool
     */
    public function isVersionOrderCreationTime();

    /**
     * Gets the base class name for migrations.
     *
     * @param bool $dropNamespace Return the base migration class name without the namespace.
     * @return string
     */
    public function getMigrationBaseClassName($dropNamespace = true);
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2017 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Config
 */

namespace Phinx\Config;

/**
 * Trait implemented NamespaceAwareInterface.
 * @package Phinx\Config
 * @author Andrey N. Mokhov
 */
trait NamespaceAwareTrait
{
    /**
     * Gets the paths to search for migration files.
     *
     * @return string[]
     */
    abstract public function getMigrationPaths();

    /**
     * Gets the paths to search for seed files.
     *
     * @return string[]
     */
    abstract public function getSeedPaths();

    /**
     * Search $needle in $haystack and return key associate with him.
     *
     * @param string $needle
     * @param array  $haystack
     * @return null|string
     */
    protected function searchNamespace($needle, $haystack)
    {
        $needle = realpath($needle);
        $haystack = array_map('realpath', $haystack);

        $key = array_search($needle, $haystack);

        return is_string($key) ? trim($key, '\\') : null;
    }

    /**
     * Get Migration Namespace associated with path.
     *
     * @param string $path
     * @return string|null
     */
    public function getMigrationNamespaceByPath($path)
    {
        $paths = $this->getMigrationPaths();

        return $this->searchNamespace($path, $paths);
    }

    /**
     * Get Seed Namespace associated with path.
     *
     * @param string $path
     * @return string|null
     */
    public function getSeedNamespaceByPath($path)
    {
        $paths = $this->getSeedPaths();

        return $this->searchNamespace($path, $paths);
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Config
 */
namespace Phinx\Config;

use Symfony\Component\Yaml\Yaml;

/**
 * Phinx configuration class.
 *
 * @package Phinx
 * @author Rob Morgan
 */
class Config implements ConfigInterface, NamespaceAwareInterface
{
    use NamespaceAwareTrait;

    /**
     * The value that identifies a version order by creation time.
     */
    const VERSION_ORDER_CREATION_TIME = 'creation';

    /**
     * The value that identifies a version order by execution time.
     */
    const VERSION_ORDER_EXECUTION_TIME = 'execution';

    /**
     * @var array
     */
    private $values = [];

    /**
     * @var string
     */
    protected $configFilePath;

    /**
     * {@inheritdoc}
     */
    public function __construct(array $configArray, $configFilePath = null)
    {
        $this->configFilePath = $configFilePath;
        $this->values = $this->replaceTokens($configArray);
    }

    /**
     * Create a new instance of the config class using a Yaml file path.
     *
     * @param  string $configFilePath Path to the Yaml File
     * @throws \RuntimeException
     * @return \Phinx\Config\Config
     */
    public static function fromYaml($configFilePath)
    {
        $configFile = file_get_contents($configFilePath);
        $configArray = Yaml::parse($configFile);

        if (!is_array($configArray)) {
            throw new \RuntimeException(sprintf(
                'File \'%s\' must be valid YAML',
                $configFilePath
            ));
        }

        return new static($configArray, $configFilePath);
    }

    /**
     * Create a new instance of the config class using a JSON file path.
     *
     * @param  string $configFilePath Path to the JSON File
     * @throws \RuntimeException
     * @return \Phinx\Config\Config
     */
    public static function fromJson($configFilePath)
    {
        $configArray = json_decode(file_get_contents($configFilePath), true);
        if (!is_array($configArray)) {
            throw new \RuntimeException(sprintf(
                'File \'%s\' must be valid JSON',
                $configFilePath
            ));
        }

        return new static($configArray, $configFilePath);
    }

    /**
     * Create a new instance of the config class using a PHP file path.
     *
     * @param  string $configFilePath Path to the PHP File
     * @throws \RuntimeException
     * @return \Phinx\Config\Config
     */
    public static function fromPhp($configFilePath)
    {
        ob_start();
        /** @noinspection PhpIncludeInspection */
        $configArray = include($configFilePath);

        // Hide console output
        ob_end_clean();

        if (!is_array($configArray)) {
            throw new \RuntimeException(sprintf(
                'PHP file \'%s\' must return an array',
                $configFilePath
            ));
        }

        return new static($configArray, $configFilePath);
    }

    /**
     * {@inheritdoc}
     */
    public function getEnvironments()
    {
        if (isset($this->values) && isset($this->values['environments'])) {
            $environments = [];
            foreach ($this->values['environments'] as $key => $value) {
                if (is_array($value)) {
                    $environments[$key] = $value;
                }
            }

            return $environments;
        }

        return null;
    }

    /**
     * {@inheritdoc}
     */
    public function getEnvironment($name)
    {
        $environments = $this->getEnvironments();

        if (isset($environments[$name])) {
            if (isset($this->values['environments']['default_migration_table'])) {
                $environments[$name]['default_migration_table'] =
                    $this->values['environments']['default_migration_table'];
            }

            return $environments[$name];
        }

        return null;
    }

    /**
     * {@inheritdoc}
     */
    public function hasEnvironment($name)
    {
        return ($this->getEnvironment($name) !== null);
    }

    /**
     * {@inheritdoc}
     */
    public function getDefaultEnvironment()
    {
        // The $PHINX_ENVIRONMENT variable overrides all other default settings
        $env = getenv('PHINX_ENVIRONMENT');
        if (!empty($env)) {
            if ($this->hasEnvironment($env)) {
                return $env;
            }

            throw new \RuntimeException(sprintf(
                'The environment configuration (read from $PHINX_ENVIRONMENT) for \'%s\' is missing',
                $env
            ));
        }

        // if the user has configured a default database then use it,
        // providing it actually exists!
        if (isset($this->values['environments']['default_database'])) {
            if ($this->getEnvironment($this->values['environments']['default_database'])) {
                return $this->values['environments']['default_database'];
            }

            throw new \RuntimeException(sprintf(
                'The environment configuration for \'%s\' is missing',
                $this->values['environments']['default_database']
            ));
        }

        // else default to the first available one
        if (is_array($this->getEnvironments()) && count($this->getEnvironments()) > 0) {
            $names = array_keys($this->getEnvironments());

            return $names[0];
        }

        throw new \RuntimeException('Could not find a default environment');
    }

    /**
     * {@inheritdoc}
     */
    public function getAlias($alias)
    {
        return !empty($this->values['aliases'][$alias]) ? $this->values['aliases'][$alias] : null;
    }

    /**
     * {@inheritdoc}
     */
    public function getConfigFilePath()
    {
        return $this->configFilePath;
    }

    /**
     * {@inheritdoc}
     */
    public function getMigrationPaths()
    {
        if (!isset($this->values['paths']['migrations'])) {
            throw new \UnexpectedValueException('Migrations path missing from config file');
        }

        if (is_string($this->values['paths']['migrations'])) {
            $this->values['paths']['migrations'] = [$this->values['paths']['migrations']];
        }

        return $this->values['paths']['migrations'];
    }

    /**
     * Gets the base class name for migrations.
     *
     * @param bool $dropNamespace Return the base migration class name without the namespace.
     * @return string
     */
    public function getMigrationBaseClassName($dropNamespace = true)
    {
        $className = !isset($this->values['migration_base_class']) ? 'Phinx\Migration\AbstractMigration' : $this->values['migration_base_class'];

        return $dropNamespace ? substr(strrchr($className, '\\'), 1) ?: $className : $className;
    }

    /**
     * {@inheritdoc}
     */
    public function getSeedPaths()
    {
        if (!isset($this->values['paths']['seeds'])) {
            throw new \UnexpectedValueException('Seeds path missing from config file');
        }

        if (is_string($this->values['paths']['seeds'])) {
            $this->values['paths']['seeds'] = [$this->values['paths']['seeds']];
        }

        return $this->values['paths']['seeds'];
    }

    /**
     * Get the template file name.
     *
     * @return string|false
     */
    public function getTemplateFile()
    {
        if (!isset($this->values['templates']['file'])) {
            return false;
        }

        return $this->values['templates']['file'];
    }

    /**
     * Get the template class name.
     *
     * @return string|false
     */
    public function getTemplateClass()
    {
        if (!isset($this->values['templates']['class'])) {
            return false;
        }

        return $this->values['templates']['class'];
    }

    /**
     * Get the version order.
     *
     * @return string
     */
    public function getVersionOrder()
    {
        if (!isset($this->values['version_order'])) {
            return self::VERSION_ORDER_CREATION_TIME;
        }

        return $this->values['version_order'];
    }

    /**
     * Is version order creation time?
     *
     * @return bool
     */
    public function isVersionOrderCreationTime()
    {
        $versionOrder = $this->getVersionOrder();

        return $versionOrder == self::VERSION_ORDER_CREATION_TIME;
    }

    /**
     * Replace tokens in the specified array.
     *
     * @param array $arr Array to replace
     * @return array
     */
    protected function replaceTokens(array $arr)
    {
        // Get environment variables
        // $_ENV is empty because variables_order does not include it normally
        $tokens = [];
        foreach ($_SERVER as $varname => $varvalue) {
            if (0 === strpos($varname, 'PHINX_')) {
                $tokens['%%' . $varname . '%%'] = $varvalue;
            }
        }

        // Phinx defined tokens (override env tokens)
        $tokens['%%PHINX_CONFIG_PATH%%'] = $this->getConfigFilePath();
        $tokens['%%PHINX_CONFIG_DIR%%'] = dirname($this->getConfigFilePath());

        // Recurse the array and replace tokens
        return $this->recurseArrayForTokens($arr, $tokens);
    }

    /**
     * Recurse an array for the specified tokens and replace them.
     *
     * @param array $arr Array to recurse
     * @param array $tokens Array of tokens to search for
     * @return array
     */
    protected function recurseArrayForTokens($arr, $tokens)
    {
        $out = [];
        foreach ($arr as $name => $value) {
            if (is_array($value)) {
                $out[$name] = $this->recurseArrayForTokens($value, $tokens);
                continue;
            }
            if (is_string($value)) {
                foreach ($tokens as $token => $tval) {
                    $value = str_replace($token, $tval, $value);
                }
                $out[$name] = $value;
                continue;
            }
            $out[$name] = $value;
        }

        return $out;
    }

    /**
     * {@inheritdoc}
     */
    public function offsetSet($id, $value)
    {
        $this->values[$id] = $value;
    }

    /**
     * {@inheritdoc}
     */
    public function offsetGet($id)
    {
        if (!array_key_exists($id, $this->values)) {
            throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
        }

        return $this->values[$id] instanceof \Closure ? $this->values[$id]($this) : $this->values[$id];
    }

    /**
     * {@inheritdoc}
     */
    public function offsetExists($id)
    {
        return isset($this->values[$id]);
    }

    /**
     * {@inheritdoc}
     */
    public function offsetUnset($id)
    {
        unset($this->values[$id]);
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Db\Adapter
 */
namespace Phinx\Db\Adapter;

use Phinx\Db\Table;
use Phinx\Db\Table\Column;
use Phinx\Db\Table\ForeignKey;
use Phinx\Db\Table\Index;
use Phinx\Migration\IrreversibleMigrationException;

/**
 * Phinx Proxy Adapter.
 *
 * Used for recording migration commands to automatically reverse them.
 *
 * @author Rob Morgan <robbym@gmail.com>
 */
class ProxyAdapter extends AdapterWrapper
{
    /**
     * @var array
     */
    protected $commands;

    /**
     * {@inheritdoc}
     */
    public function getAdapterType()
    {
        return 'ProxyAdapter';
    }

    /**
     * {@inheritdoc}
     */
    public function createTable(Table $table)
    {
        $this->recordCommand('createTable', [$table->getName()]);
    }

    /**
     * {@inheritdoc}
     */
    public function renameTable($tableName, $newTableName)
    {
        $this->recordCommand('renameTable', [$tableName, $newTableName]);
    }

    /**
     * {@inheritdoc}
     */
    public function dropTable($tableName)
    {
        $this->recordCommand('dropTable', [$tableName]);
    }

    /**
     * {@inheritdoc}
     */
    public function truncateTable($tableName)
    {
        $this->recordCommand('truncateTable', [$tableName]);
    }

    /**
     * {@inheritdoc}
     */
    public function addColumn(Table $table, Column $column)
    {
        $this->recordCommand('addColumn', [$table, $column]);
    }

    /**
     * {@inheritdoc}
     */
    public function renameColumn($tableName, $columnName, $newColumnName)
    {
        $this->recordCommand('renameColumn', [$tableName, $columnName, $newColumnName]);
    }

    /**
     * {@inheritdoc}
     */
    public function changeColumn($tableName, $columnName, Column $newColumn)
    {
        $this->recordCommand('changeColumn', [$tableName, $columnName, $newColumn]);
    }

    /**
     * {@inheritdoc}
     */
    public function dropColumn($tableName, $columnName)
    {
        $this->recordCommand('dropColumn', [$tableName, $columnName]);
    }

    /**
     * {@inheritdoc}
     */
    public function addIndex(Table $table, Index $index)
    {
        $this->recordCommand('addIndex', [$table, $index]);
    }

    /**
     * {@inheritdoc}
     */
    public function dropIndex($tableName, $columns, $options = [])
    {
        $this->recordCommand('dropIndex', [$tableName, $columns, $options]);
    }

    /**
     * {@inheritdoc}
     */
    public function dropIndexByName($tableName, $indexName)
    {
        $this->recordCommand('dropIndexByName', [$tableName, $indexName]);
    }

    /**
     * {@inheritdoc}
     */
    public function addForeignKey(Table $table, ForeignKey $foreignKey)
    {
        $this->recordCommand('addForeignKey', [$table, $foreignKey]);
    }

    /**
     * {@inheritdoc}
     */
    public function dropForeignKey($tableName, $columns, $constraint = null)
    {
        $this->recordCommand('dropForeignKey', [$columns, $constraint]);
    }

    /**
     * {@inheritdoc}
     */
    public function createDatabase($name, $options = [])
    {
        $this->recordCommand('createDatabase', [$name, $options]);
    }

    /**
     * Record a command for execution later.
     *
     * @param string $name Command Name
     * @param array $arguments Command Arguments
     * @return void
     */
    public function recordCommand($name, $arguments)
    {
        $this->commands[] = [
            'name' => $name,
            'arguments' => $arguments
        ];
    }

    /**
     * Sets an array of recorded commands.
     *
     * @param array $commands Commands
     * @return \Phinx\Db\Adapter\ProxyAdapter
     */
    public function setCommands($commands)
    {
        $this->commands = $commands;

        return $this;
    }

    /**
     * Gets an array of the recorded commands.
     *
     * @return array
     */
    public function getCommands()
    {
        return $this->commands;
    }

    /**
     * Gets an array of the recorded commands in reverse.
     *
     * @throws \Phinx\Migration\IrreversibleMigrationException if a command cannot be reversed.
     * @return array
     */
    public function getInvertedCommands()
    {
        if ($this->getCommands() === null) {
            return [];
        }

        $invCommands = [];
        $supportedCommands = [
            'createTable', 'renameTable', 'addColumn',
            'renameColumn', 'addIndex', 'addForeignKey'
        ];
        foreach (array_reverse($this->getCommands()) as $command) {
            if (!in_array($command['name'], $supportedCommands)) {
                throw new IrreversibleMigrationException(sprintf(
                    'Cannot reverse a "%s" command',
                    $command['name']
                ));
            }
            $invertMethod = 'invert' . ucfirst($command['name']);
            $invertedCommand = $this->$invertMethod($command['arguments']);
            $invCommands[] = [
                'name' => $invertedCommand['name'],
                'arguments' => $invertedCommand['arguments']
            ];
        }

        return $invCommands;
    }

    /**
     * Execute the recorded commands.
     *
     * @return void
     */
    public function executeCommands()
    {
        $commands = $this->getCommands();
        foreach ($commands as $command) {
            call_user_func_array([$this->getAdapter(), $command['name']], $command['arguments']);
        }
    }

    /**
     * Execute the recorded commands in reverse.
     *
     * @return void
     */
    public function executeInvertedCommands()
    {
        $commands = $this->getInvertedCommands();
        foreach ($commands as $command) {
            call_user_func_array([$this->getAdapter(), $command['name']], $command['arguments']);
        }
    }

    /**
     * Returns the reverse of a createTable command.
     *
     * @param array $args Method Arguments
     * @return array
     */
    public function invertCreateTable($args)
    {
        return ['name' => 'dropTable', 'arguments' => [$args[0]]];
    }

    /**
     * Returns the reverse of a renameTable command.
     *
     * @param array $args Method Arguments
     * @return array
     */
    public function invertRenameTable($args)
    {
        return ['name' => 'renameTable', 'arguments' => [$args[1], $args[0]]];
    }

    /**
     * Returns the reverse of a addColumn command.
     *
     * @param array $args Method Arguments
     * @return array
     */
    public function invertAddColumn($args)
    {
        return ['name' => 'dropColumn', 'arguments' => [$args[0]->getName(), $args[1]->getName()]];
    }

    /**
     * Returns the reverse of a renameColumn command.
     *
     * @param array $args Method Arguments
     * @return array
     */
    public function invertRenameColumn($args)
    {
        return ['name' => 'renameColumn', 'arguments' => [$args[0], $args[2], $args[1]]];
    }

    /**
     * Returns the reverse of a addIndex command.
     *
     * @param array $args Method Arguments
     * @return array
     */
    public function invertAddIndex($args)
    {
        return ['name' => 'dropIndex', 'arguments' => [$args[0]->getName(), $args[1]->getColumns()]];
    }

    /**
     * Returns the reverse of a addForeignKey command.
     *
     * @param array $args Method Arguments
     * @return array
     */
    public function invertAddForeignKey($args)
    {
        return ['name' => 'dropForeignKey', 'arguments' => [$args[0]->getName(), $args[1]->getColumns()]];
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Db\Adapter
 */
namespace Phinx\Db\Adapter;

use BadMethodCallException;
use Phinx\Db\Table;
use Phinx\Migration\MigrationInterface;

/**
 * Phinx PDO Adapter.
 *
 * @author Rob Morgan <robbym@gmail.com>
 */
abstract class PdoAdapter extends AbstractAdapter
{
    /**
     * @var \PDO|null
     */
    protected $connection;

    /**
     * {@inheritdoc}
     */
    public function setOptions(array $options)
    {
        parent::setOptions($options);

        if (isset($options['connection'])) {
            $this->setConnection($options['connection']);
        }

        return $this;
    }

    /**
     * Sets the database connection.
     *
     * @param \PDO $connection Connection
     * @return \Phinx\Db\Adapter\AdapterInterface
     */
    public function setConnection(\PDO $connection)
    {
        $this->connection = $connection;

        // Create the schema table if it doesn't already exist
        if (!$this->hasSchemaTable()) {
            $this->createSchemaTable();
        } else {
            $table = new Table($this->getSchemaTableName(), [], $this);
            if (!$table->hasColumn('migration_name')) {
                $table
                    ->addColumn(
                        'migration_name',
                        'string',
                        ['limit' => 100, 'after' => 'version', 'default' => null, 'null' => true]
                    )
                    ->save();
            }
            if (!$table->hasColumn('breakpoint')) {
                $table
                    ->addColumn('breakpoint', 'boolean', ['default' => false])
                    ->save();
            }
        }

        return $this;
    }

    /**
     * Gets the database connection
     *
     * @return \PDO
     */
    public function getConnection()
    {
        if ($this->connection === null) {
            $this->connect();
        }

        return $this->connection;
    }

    /**
     * {@inheritdoc}
     */
    public function connect()
    {
    }

    /**
     * {@inheritdoc}
     */
    public function disconnect()
    {
    }

    /**
     * {@inheritdoc}
     */
    public function execute($sql)
    {
        if ($this->isDryRunEnabled()) {
            $this->getOutput()->writeln($sql);

            return 0;
        }

        return $this->getConnection()->exec($sql);
    }

    /**
     * Executes a query and returns PDOStatement.
     *
     * @param string $sql SQL
     * @return \PDOStatement
     */
    public function query($sql)
    {
        return $this->getConnection()->query($sql);
    }

    /**
     * {@inheritdoc}
     */
    public function fetchRow($sql)
    {
        $result = $this->query($sql);

        return $result->fetch();
    }

    /**
     * {@inheritdoc}
     */
    public function fetchAll($sql)
    {
        $rows = [];
        $result = $this->query($sql);
        while ($row = $result->fetch()) {
            $rows[] = $row;
        }

        return $rows;
    }

    /**
     * {@inheritdoc}
     */
    public function insert(Table $table, $row)
    {
        $sql = sprintf(
            "INSERT INTO %s ",
            $this->quoteTableName($table->getName())
        );

        $columns = array_keys($row);
        $sql .= "(" . implode(', ', array_map([$this, 'quoteColumnName'], $columns)) . ")";
        $sql .= " VALUES (" . implode(', ', array_fill(0, count($columns), '?')) . ")";

        $stmt = $this->getConnection()->prepare($sql);
        $stmt->execute(array_values($row));
    }

    /**
     * {@inheritdoc}
     */
    public function bulkinsert(Table $table, $rows)
    {
        $sql = sprintf(
            "INSERT INTO %s ",
            $this->quoteTableName($table->getName())
        );

        $current = current($rows);
        $keys = array_keys($current);
        $sql .= "(" . implode(', ', array_map([$this, 'quoteColumnName'], $keys)) . ") VALUES";

        $vals = [];
        foreach ($rows as $row) {
            foreach ($row as $v) {
                $vals[] = $v;
            }
        }

        $count_keys = count($keys);
        $query = "(" . implode(', ', array_fill(0, $count_keys, '?')) . ")";

        $count_vars = count($rows);
        $queries = array_fill(0, $count_vars, $query);
        $sql .= implode(',', $queries);

        $stmt = $this->getConnection()->prepare($sql);
        $stmt->execute($vals);
    }

    /**
     * {@inheritdoc}
     */
    public function getVersions()
    {
        $rows = $this->getVersionLog();

        return array_keys($rows);
    }

    /**
     * {@inheritdoc}
     */
    public function getVersionLog()
    {
        $result = [];

        switch ($this->options['version_order']) {
            case \Phinx\Config\Config::VERSION_ORDER_CREATION_TIME:
                $orderBy = 'version ASC';
                break;
            case \Phinx\Config\Config::VERSION_ORDER_EXECUTION_TIME:
                $orderBy = 'start_time ASC, version ASC';
                break;
            default:
                throw new \RuntimeException('Invalid version_order configuration option');
        }

        $rows = $this->fetchAll(sprintf('SELECT * FROM %s ORDER BY %s', $this->getSchemaTableName(), $orderBy));
        foreach ($rows as $version) {
            $result[$version['version']] = $version;
        }

        return $result;
    }

    /**
     * {@inheritdoc}
     */
    public function migrated(MigrationInterface $migration, $direction, $startTime, $endTime)
    {
        if (strcasecmp($direction, MigrationInterface::UP) === 0) {
            // up
            $sql = sprintf(
                "INSERT INTO %s (%s, %s, %s, %s, %s) VALUES ('%s', '%s', '%s', '%s', %s);",
                $this->getSchemaTableName(),
                $this->quoteColumnName('version'),
                $this->quoteColumnName('migration_name'),
                $this->quoteColumnName('start_time'),
                $this->quoteColumnName('end_time'),
                $this->quoteColumnName('breakpoint'),
                $migration->getVersion(),
                substr($migration->getName(), 0, 100),
                $startTime,
                $endTime,
                $this->castToBool(false)
            );

            $this->execute($sql);
        } else {
            // down
            $sql = sprintf(
                "DELETE FROM %s WHERE %s = '%s'",
                $this->getSchemaTableName(),
                $this->quoteColumnName('version'),
                $migration->getVersion()
            );

            $this->execute($sql);
        }

        return $this;
    }

    /**
     * @inheritDoc
     */
    public function toggleBreakpoint(MigrationInterface $migration)
    {
        $this->query(
            sprintf(
                'UPDATE %1$s SET %2$s = CASE %2$s WHEN %3$s THEN %4$s ELSE %3$s END, %7$s = %7$s WHERE %5$s = \'%6$s\';',
                $this->getSchemaTableName(),
                $this->quoteColumnName('breakpoint'),
                $this->castToBool(true),
                $this->castToBool(false),
                $this->quoteColumnName('version'),
                $migration->getVersion(),
                $this->quoteColumnName('start_time')
            )
        );

        return $this;
    }

    /**
     * @inheritDoc
     */
    public function resetAllBreakpoints()
    {
        return $this->execute(
            sprintf(
                'UPDATE %1$s SET %2$s = %3$s, %4$s = %4$s WHERE %2$s <> %3$s;',
                $this->getSchemaTableName(),
                $this->quoteColumnName('breakpoint'),
                $this->castToBool(false),
                $this->quoteColumnName('start_time')
            )
        );
    }

    /**
     * {@inheritdoc}
     */
    public function createSchema($schemaName = 'public')
    {
        throw new BadMethodCallException('Creating a schema is not supported');
    }

    /**
     * {@inheritdoc}
     */
    public function dropSchema($name)
    {
        throw new BadMethodCallException('Dropping a schema is not supported');
    }

    /**
     * {@inheritdoc}
     */
    public function getColumnTypes()
    {
        return [
            'string',
            'char',
            'text',
            'integer',
            'biginteger',
            'float',
            'decimal',
            'datetime',
            'timestamp',
            'time',
            'date',
            'blob',
            'binary',
            'varbinary',
            'boolean',
            'uuid',
            // Geospatial data types
            'geometry',
            'point',
            'linestring',
            'polygon',
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function castToBool($value)
    {
        return (bool)$value ? 1 : 0;
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2017 Cake Software Foundation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Db\Adapter
 */
namespace Phinx\Db\Adapter;

use Phinx\Db\Table;
use Phinx\Db\Table\Column;
use Phinx\Db\Table\ForeignKey;
use Phinx\Db\Table\Index;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Wraps any adpter to record the time spend executing its commands
 */
class TimedOutputAdapter extends AdapterWrapper
{

    /**
     * {@inheritdoc}
     */
    public function getAdapterType()
    {
        return $this->getAdapter()->getAdapterType();
    }

    /**
     * Start timing a command.
     *
     * @return callable A function that is to be called when the command finishes
     */
    public function startCommandTimer()
    {
        $started = microtime(true);

        return function () use ($started) {
            $end = microtime(true);
            if (OutputInterface::VERBOSITY_VERBOSE <= $this->getOutput()->getVerbosity()) {
                $this->getOutput()->writeln('    -> ' . sprintf('%.4fs', $end - $started));
            }
        };
    }

    /**
     * Write a Phinx command to the output.
     *
     * @param string $command Command Name
     * @param array  $args    Command Args
     * @return void
     */
    public function writeCommand($command, $args = [])
    {
        if (OutputInterface::VERBOSITY_VERBOSE > $this->getOutput()->getVerbosity()) {
            return;
        }

        if (count($args)) {
            $outArr = [];
            foreach ($args as $arg) {
                if (is_array($arg)) {
                    $arg = array_map(
                        function ($value) {
                            return '\'' . $value . '\'';
                        },
                        $arg
                    );
                    $outArr[] = '[' . implode(', ', $arg) . ']';
                    continue;
                }

                $outArr[] = '\'' . $arg . '\'';
            }
            $this->getOutput()->writeln(' -- ' . $command . '(' . implode(', ', $outArr) . ')');

            return;
        }

        $this->getOutput()->writeln(' -- ' . $command);
    }

    /**
     * {@inheritdoc}
     *
     * @return void
     */
    public function insert(Table $table, $row)
    {
        $end = $this->startCommandTimer();
        $this->writeCommand('insert', [$table->getName()]);
        parent::insert($table, $row);
        $end();
    }

    /**
     * {@inheritdoc}
     *
     * @return void
     */
    public function bulkinsert(Table $table, $rows)
    {
        $end = $this->startCommandTimer();
        $this->writeCommand('bulkinsert', [$table->getName()]);
        parent::bulkinsert($table, $rows);
        $end();
    }

    /**
     * {@inheritdoc}
     */
    public function createTable(Table $table)
    {
        $end = $this->startCommandTimer();
        $this->writeCommand('createTable', [$table->getName()]);
        parent::createTable($table);
        $end();
    }

    /**
     * {@inheritdoc}
     */
    public function renameTable($tableName, $newTableName)
    {
        $end = $this->startCommandTimer();
        $this->writeCommand('renameTable', [$tableName, $newTableName]);
        parent::renameTable($tableName, $newTableName);
        $end();
    }

    /**
     * {@inheritdoc}
     */
    public function dropTable($tableName)
    {
        $end = $this->startCommandTimer();
        $this->writeCommand('dropTable', [$tableName]);
        parent::dropTable($tableName);
        $end();
    }

    /**
     * {@inheritdoc}
     */
    public function truncateTable($tableName)
    {
        $end = $this->startCommandTimer();
        $this->writeCommand('truncateTable', [$tableName]);
        parent::truncateTable($tableName);
        $end();
    }

    /**
     * {@inheritdoc}
     */
    public function addColumn(Table $table, Column $column)
    {
        $end = $this->startCommandTimer();
        $this->writeCommand(
            'addColumn',
            [
                $table->getName(),
                $column->getName(),
                $column->getType()
            ]
        );
        parent::addColumn($table, $column);
        $end();
    }

    /**
     * {@inheritdoc}
     */
    public function renameColumn($tableName, $columnName, $newColumnName)
    {
        $end = $this->startCommandTimer();
        $this->writeCommand('renameColumn', [$tableName, $columnName, $newColumnName]);
        parent::renameColumn($tableName, $columnName, $newColumnName);
        $end();
    }

    /**
     * {@inheritdoc}
     */
    public function changeColumn($tableName, $columnName, Column $newColumn)
    {
        $end = $this->startCommandTimer();
        $this->writeCommand('changeColumn', [$tableName, $columnName, $newColumn->getType()]);
        parent::changeColumn($tableName, $columnName, $newColumn);
        $end();
    }

    /**
     * {@inheritdoc}
     */
    public function dropColumn($tableName, $columnName)
    {
        $end = $this->startCommandTimer();
        $this->writeCommand('dropColumn', [$tableName, $columnName]);
        parent::dropColumn($tableName, $columnName);
        $end();
    }

    /**
     * {@inheritdoc}
     */
    public function addIndex(Table $table, Index $index)
    {
        $end = $this->startCommandTimer();
        $this->writeCommand('addIndex', [$table->getName(), $index->getColumns()]);
        parent::addIndex($table, $index);
        $end();
    }

    /**
     * {@inheritdoc}
     */
    public function dropIndex($tableName, $columns)
    {
        $end = $this->startCommandTimer();
        $this->writeCommand('dropIndex', [$tableName, $columns]);
        parent::dropIndex($tableName, $columns);
        $end();
    }

    /**
     * {@inheritdoc}
     */
    public function dropIndexByName($tableName, $indexName)
    {
        $end = $this->startCommandTimer();
        $this->writeCommand('dropIndexByName', [$tableName, $indexName]);
        parent::dropIndexByName($tableName, $indexName);
        $end();
    }

    /**
     * {@inheritdoc}
     */
    public function addForeignKey(Table $table, ForeignKey $foreignKey)
    {
        $end = $this->startCommandTimer();
        $this->writeCommand('addForeignKey', [$table->getName(), $foreignKey->getColumns()]);
        parent::addForeignKey($table, $foreignKey);
        $end();
    }

    /**
     * {@inheritdoc}
     */
    public function dropForeignKey($tableName, $columns, $constraint = null)
    {
        $end = $this->startCommandTimer();
        $this->writeCommand('dropForeignKey', [$tableName, $columns]);
        parent::dropForeignKey($tableName, $columns, $constraint);
        $end();
    }

    /**
     * {@inheritdoc}
     */
    public function createDatabase($name, $options = [])
    {
        $end = $this->startCommandTimer();
        $this->writeCommand('createDatabase', [$name]);
        parent::createDatabase($name, $options);
        $end();
    }

    /**
     * {@inheritdoc}
     */
    public function dropDatabase($name)
    {
        $end = $this->startCommandTimer();
        $this->writeCommand('dropDatabase', [$name]);
        parent::dropDatabase($name);
        $end();
    }

    /**
     * {@inheritdoc}
     */
    public function createSchema($name = 'public')
    {
        $end = $this->startCommandTimer();
        $this->writeCommand('createSchema', [$name]);
        parent::createSchema($name);
        $end();
    }

    /**
     * {@inheritdoc}
     */
    public function dropSchema($name)
    {
        $end = $this->startCommandTimer();
        $this->writeCommand('dropSchema', [$name]);
        parent::dropSchema($name);
        $end();
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Db\Adapter
 */
namespace Phinx\Db\Adapter;

use Phinx\Db\Table;
use Phinx\Db\Table\Column;
use Phinx\Db\Table\ForeignKey;
use Phinx\Db\Table\Index;

/**
 * Phinx MySQL Adapter.
 *
 * @author Rob Morgan <robbym@gmail.com>
 */
class MysqlAdapter extends PdoAdapter implements AdapterInterface
{

    protected $signedColumnTypes = ['integer' => true, 'biginteger' => true, 'float' => true, 'decimal' => true, 'boolean' => true];

    const TEXT_TINY = 255;
    const TEXT_SMALL = 255; /* deprecated, alias of TEXT_TINY */
    const TEXT_REGULAR = 65535;
    const TEXT_MEDIUM = 16777215;
    const TEXT_LONG = 4294967295;

    // According to https://dev.mysql.com/doc/refman/5.0/en/blob.html BLOB sizes are the same as TEXT
    const BLOB_TINY = 255;
    const BLOB_SMALL = 255; /* deprecated, alias of BLOB_TINY */
    const BLOB_REGULAR = 65535;
    const BLOB_MEDIUM = 16777215;
    const BLOB_LONG = 4294967295;

    const INT_TINY = 255;
    const INT_SMALL = 65535;
    const INT_MEDIUM = 16777215;
    const INT_REGULAR = 4294967295;
    const INT_BIG = 18446744073709551615;

    const TYPE_YEAR = 'year';

    /**
     * {@inheritdoc}
     */
    public function connect()
    {
        if ($this->connection === null) {
            if (!class_exists('PDO') || !in_array('mysql', \PDO::getAvailableDrivers(), true)) {
                // @codeCoverageIgnoreStart
                throw new \RuntimeException('You need to enable the PDO_Mysql extension for Phinx to run properly.');
                // @codeCoverageIgnoreEnd
            }

            $db = null;
            $options = $this->getOptions();

            $dsn = 'mysql:';

            if (!empty($options['unix_socket'])) {
                // use socket connection
                $dsn .= 'unix_socket=' . $options['unix_socket'];
            } else {
                // use network connection
                $dsn .= 'host=' . $options['host'];
                if (!empty($options['port'])) {
                    $dsn .= ';port=' . $options['port'];
                }
            }

            $dsn .= ';dbname=' . $options['name'];

            // charset support
            if (!empty($options['charset'])) {
                $dsn .= ';charset=' . $options['charset'];
            }

            $driverOptions = [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION];

            // support arbitrary \PDO::MYSQL_ATTR_* driver options and pass them to PDO
            // http://php.net/manual/en/ref.pdo-mysql.php#pdo-mysql.constants
            foreach ($options as $key => $option) {
                if (strpos($key, 'mysql_attr_') === 0) {
                    $driverOptions[constant('\PDO::' . strtoupper($key))] = $option;
                }
            }

            try {
                $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions);
            } catch (\PDOException $exception) {
                throw new \InvalidArgumentException(sprintf(
                    'There was a problem connecting to the database: %s',
                    $exception->getMessage()
                ));
            }

            $this->setConnection($db);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function disconnect()
    {
        $this->connection = null;
    }

    /**
     * {@inheritdoc}
     */
    public function hasTransactions()
    {
        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function beginTransaction()
    {
        $this->execute('START TRANSACTION');
    }

    /**
     * {@inheritdoc}
     */
    public function commitTransaction()
    {
        $this->execute('COMMIT');
    }

    /**
     * {@inheritdoc}
     */
    public function rollbackTransaction()
    {
        $this->execute('ROLLBACK');
    }

    /**
     * {@inheritdoc}
     */
    public function quoteTableName($tableName)
    {
        return str_replace('.', '`.`', $this->quoteColumnName($tableName));
    }

    /**
     * {@inheritdoc}
     */
    public function quoteColumnName($columnName)
    {
        return '`' . str_replace('`', '``', $columnName) . '`';
    }

    /**
     * {@inheritdoc}
     */
    public function hasTable($tableName)
    {
        $options = $this->getOptions();

        $exists = $this->fetchRow(sprintf(
            "SELECT TABLE_NAME
            FROM INFORMATION_SCHEMA.TABLES
            WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s'",
            $options['name'],
            $tableName
        ));

        return !empty($exists);
    }

    /**
     * {@inheritdoc}
     */
    public function createTable(Table $table)
    {
        // This method is based on the MySQL docs here: http://dev.mysql.com/doc/refman/5.1/en/create-index.html
        $defaultOptions = [
            'engine' => 'InnoDB',
            'collation' => 'utf8_general_ci'
        ];

        $options = array_merge(
            $defaultOptions,
            array_intersect_key($this->getOptions(), $defaultOptions),
            $table->getOptions()
        );

        // Add the default primary key
        $columns = $table->getPendingColumns();
        if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
            $column = new Column();
            $column->setName('id')
                   ->setType('integer')
                   ->setSigned(isset($options['signed']) ? $options['signed'] : true)
                   ->setIdentity(true);

            array_unshift($columns, $column);
            $options['primary_key'] = 'id';
        } elseif (isset($options['id']) && is_string($options['id'])) {
            // Handle id => "field_name" to support AUTO_INCREMENT
            $column = new Column();
            $column->setName($options['id'])
                   ->setType('integer')
                   ->setIdentity(true);

            array_unshift($columns, $column);
            $options['primary_key'] = $options['id'];
        }

        // TODO - process table options like collation etc

        // process table engine (default to InnoDB)
        $optionsStr = 'ENGINE = InnoDB';
        if (isset($options['engine'])) {
            $optionsStr = sprintf('ENGINE = %s', $options['engine']);
        }

        // process table collation
        if (isset($options['collation'])) {
            $charset = explode('_', $options['collation']);
            $optionsStr .= sprintf(' CHARACTER SET %s', $charset[0]);
            $optionsStr .= sprintf(' COLLATE %s', $options['collation']);
        }

        // set the table comment
        if (isset($options['comment'])) {
            $optionsStr .= sprintf(" COMMENT=%s ", $this->getConnection()->quote($options['comment']));
        }

        $sql = 'CREATE TABLE ';
        $sql .= $this->quoteTableName($table->getName()) . ' (';
        foreach ($columns as $column) {
            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
        }

        // set the primary key(s)
        if (isset($options['primary_key'])) {
            $sql = rtrim($sql);
            $sql .= ' PRIMARY KEY (';
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
                $sql .= $this->quoteColumnName($options['primary_key']);
            } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
            }
            $sql .= ')';
        } else {
            $sql = substr(rtrim($sql), 0, -1); // no primary keys
        }

        // set the indexes
        $indexes = $table->getIndexes();
        foreach ($indexes as $index) {
            $sql .= ', ' . $this->getIndexSqlDefinition($index);
        }

        // set the foreign keys
        $foreignKeys = $table->getForeignKeys();
        foreach ($foreignKeys as $foreignKey) {
            $sql .= ', ' . $this->getForeignKeySqlDefinition($foreignKey);
        }

        $sql .= ') ' . $optionsStr;
        $sql = rtrim($sql) . ';';

        // execute the sql
        $this->execute($sql);
    }

    /**
     * {@inheritdoc}
     */
    public function renameTable($tableName, $newTableName)
    {
        $this->execute(sprintf('RENAME TABLE %s TO %s', $this->quoteTableName($tableName), $this->quoteTableName($newTableName)));
    }

    /**
     * {@inheritdoc}
     */
    public function dropTable($tableName)
    {
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName)));
    }

    /**
     * {@inheritdoc}
     */
    public function truncateTable($tableName)
    {
        $sql = sprintf(
            'TRUNCATE TABLE %s',
            $this->quoteTableName($tableName)
        );

        $this->execute($sql);
    }

    /**
     * {@inheritdoc}
     */
    public function getColumns($tableName)
    {
        $columns = [];
        $rows = $this->fetchAll(sprintf('SHOW COLUMNS FROM %s', $this->quoteTableName($tableName)));
        foreach ($rows as $columnInfo) {
            $phinxType = $this->getPhinxType($columnInfo['Type']);

            $column = new Column();
            $column->setName($columnInfo['Field'])
                   ->setNull($columnInfo['Null'] !== 'NO')
                   ->setDefault($columnInfo['Default'])
                   ->setType($phinxType['name'])
                   ->setLimit($phinxType['limit']);

            if ($columnInfo['Extra'] === 'auto_increment') {
                $column->setIdentity(true);
            }

            if (isset($phinxType['values'])) {
                $column->setValues($phinxType['values']);
            }

            $columns[] = $column;
        }

        return $columns;
    }

    /**
     * {@inheritdoc}
     */
    public function hasColumn($tableName, $columnName)
    {
        $rows = $this->fetchAll(sprintf('SHOW COLUMNS FROM %s', $this->quoteTableName($tableName)));
        foreach ($rows as $column) {
            if (strcasecmp($column['Field'], $columnName) === 0) {
                return true;
            }
        }

        return false;
    }

    /**
     * Get the defintion for a `DEFAULT` statement.
     *
     * @param  mixed $default
     * @return string
     */
    protected function getDefaultValueDefinition($default)
    {
        if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) {
            $default = $this->getConnection()->quote($default);
        } elseif (is_bool($default)) {
            $default = $this->castToBool($default);
        }

        return isset($default) ? ' DEFAULT ' . $default : '';
    }

    /**
     * {@inheritdoc}
     */
    public function addColumn(Table $table, Column $column)
    {
        $sql = sprintf(
            'ALTER TABLE %s ADD %s %s',
            $this->quoteTableName($table->getName()),
            $this->quoteColumnName($column->getName()),
            $this->getColumnSqlDefinition($column)
        );

        if ($column->getAfter()) {
            $sql .= ' AFTER ' . $this->quoteColumnName($column->getAfter());
        }

        $this->execute($sql);
    }

    /**
     * {@inheritdoc}
     */
    public function renameColumn($tableName, $columnName, $newColumnName)
    {
        $rows = $this->fetchAll(sprintf('DESCRIBE %s', $this->quoteTableName($tableName)));
        foreach ($rows as $row) {
            if (strcasecmp($row['Field'], $columnName) === 0) {
                $null = ($row['Null'] == 'NO') ? 'NOT NULL' : 'NULL';
                $extra = ' ' . strtoupper($row['Extra']);
                if (!is_null($row['Default'])) {
                    $extra .= $this->getDefaultValueDefinition($row['Default']);
                }
                $definition = $row['Type'] . ' ' . $null . $extra;

                $this->execute(
                    sprintf(
                        'ALTER TABLE %s CHANGE COLUMN %s %s %s',
                        $this->quoteTableName($tableName),
                        $this->quoteColumnName($columnName),
                        $this->quoteColumnName($newColumnName),
                        $definition
                    )
                );

                return;
            }
        }

        throw new \InvalidArgumentException(sprintf(
            'The specified column doesn\'t exist: ' .
            $columnName
        ));
    }

    /**
     * {@inheritdoc}
     */
    public function changeColumn($tableName, $columnName, Column $newColumn)
    {
        $after = $newColumn->getAfter() ? ' AFTER ' . $this->quoteColumnName($newColumn->getAfter()) : '';
        $this->execute(
            sprintf(
                'ALTER TABLE %s CHANGE %s %s %s%s',
                $this->quoteTableName($tableName),
                $this->quoteColumnName($columnName),
                $this->quoteColumnName($newColumn->getName()),
                $this->getColumnSqlDefinition($newColumn),
                $after
            )
        );
    }

    /**
     * {@inheritdoc}
     */
    public function dropColumn($tableName, $columnName)
    {
        $this->execute(
            sprintf(
                'ALTER TABLE %s DROP COLUMN %s',
                $this->quoteTableName($tableName),
                $this->quoteColumnName($columnName)
            )
        );
    }

    /**
     * Get an array of indexes from a particular table.
     *
     * @param string $tableName Table Name
     * @return array
     */
    protected function getIndexes($tableName)
    {
        $indexes = [];
        $rows = $this->fetchAll(sprintf('SHOW INDEXES FROM %s', $this->quoteTableName($tableName)));
        foreach ($rows as $row) {
            if (!isset($indexes[$row['Key_name']])) {
                $indexes[$row['Key_name']] = ['columns' => []];
            }
            $indexes[$row['Key_name']]['columns'][] = strtolower($row['Column_name']);
        }

        return $indexes;
    }

    /**
     * {@inheritdoc}
     */
    public function hasIndex($tableName, $columns)
    {
        if (is_string($columns)) {
            $columns = [$columns]; // str to array
        }

        $columns = array_map('strtolower', $columns);
        $indexes = $this->getIndexes($tableName);

        foreach ($indexes as $index) {
            if ($columns == $index['columns']) {
                return true;
            }
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function hasIndexByName($tableName, $indexName)
    {
        $indexes = $this->getIndexes($tableName);

        foreach ($indexes as $name => $index) {
            if ($name === $indexName) {
                 return true;
            }
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function addIndex(Table $table, Index $index)
    {
        $this->execute(
            sprintf(
                'ALTER TABLE %s ADD %s',
                $this->quoteTableName($table->getName()),
                $this->getIndexSqlDefinition($index)
            )
        );
    }

    /**
     * {@inheritdoc}
     */
    public function dropIndex($tableName, $columns)
    {
        if (is_string($columns)) {
            $columns = [$columns]; // str to array
        }

        $indexes = $this->getIndexes($tableName);
        $columns = array_map('strtolower', $columns);

        foreach ($indexes as $indexName => $index) {
            if ($columns == $index['columns']) {
                $this->execute(
                    sprintf(
                        'ALTER TABLE %s DROP INDEX %s',
                        $this->quoteTableName($tableName),
                        $this->quoteColumnName($indexName)
                    )
                );

                return;
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function dropIndexByName($tableName, $indexName)
    {
        $indexes = $this->getIndexes($tableName);

        foreach ($indexes as $name => $index) {
            //$a = array_diff($columns, $index['columns']);
            if ($name === $indexName) {
                $this->execute(
                    sprintf(
                        'ALTER TABLE %s DROP INDEX %s',
                        $this->quoteTableName($tableName),
                        $this->quoteColumnName($indexName)
                    )
                );

                return;
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function hasForeignKey($tableName, $columns, $constraint = null)
    {
        if (is_string($columns)) {
            $columns = [$columns]; // str to array
        }
        $foreignKeys = $this->getForeignKeys($tableName);
        if ($constraint) {
            if (isset($foreignKeys[$constraint])) {
                return !empty($foreignKeys[$constraint]);
            }

            return false;
        } else {
            foreach ($foreignKeys as $key) {
                if ($columns == $key['columns']) {
                    return true;
                }
            }

            return false;
        }
    }

    /**
     * Get an array of foreign keys from a particular table.
     *
     * @param string $tableName Table Name
     * @return array
     */
    protected function getForeignKeys($tableName)
    {
        $foreignKeys = [];
        $rows = $this->fetchAll(sprintf(
            "SELECT
              CONSTRAINT_NAME,
              TABLE_NAME,
              COLUMN_NAME,
              REFERENCED_TABLE_NAME,
              REFERENCED_COLUMN_NAME
            FROM information_schema.KEY_COLUMN_USAGE
            WHERE REFERENCED_TABLE_SCHEMA = DATABASE()
              AND REFERENCED_TABLE_NAME IS NOT NULL
              AND TABLE_NAME = '%s'
            ORDER BY POSITION_IN_UNIQUE_CONSTRAINT",
            $tableName
        ));
        foreach ($rows as $row) {
            $foreignKeys[$row['CONSTRAINT_NAME']]['table'] = $row['TABLE_NAME'];
            $foreignKeys[$row['CONSTRAINT_NAME']]['columns'][] = $row['COLUMN_NAME'];
            $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_table'] = $row['REFERENCED_TABLE_NAME'];
            $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_columns'][] = $row['REFERENCED_COLUMN_NAME'];
        }

        return $foreignKeys;
    }

    /**
     * {@inheritdoc}
     */
    public function addForeignKey(Table $table, ForeignKey $foreignKey)
    {
        $this->execute(
            sprintf(
                'ALTER TABLE %s ADD %s',
                $this->quoteTableName($table->getName()),
                $this->getForeignKeySqlDefinition($foreignKey)
            )
        );
    }

    /**
     * {@inheritdoc}
     */
    public function dropForeignKey($tableName, $columns, $constraint = null)
    {
        if (is_string($columns)) {
            $columns = [$columns]; // str to array
        }

        if ($constraint) {
            $this->execute(
                sprintf(
                    'ALTER TABLE %s DROP FOREIGN KEY %s',
                    $this->quoteTableName($tableName),
                    $constraint
                )
            );

            return;
        } else {
            foreach ($columns as $column) {
                $rows = $this->fetchAll(sprintf(
                    "SELECT
                        CONSTRAINT_NAME
                      FROM information_schema.KEY_COLUMN_USAGE
                      WHERE REFERENCED_TABLE_SCHEMA = DATABASE()
                        AND REFERENCED_TABLE_NAME IS NOT NULL
                        AND TABLE_NAME = '%s'
                        AND COLUMN_NAME = '%s'
                      ORDER BY POSITION_IN_UNIQUE_CONSTRAINT",
                    $tableName,
                    $column
                ));
                foreach ($rows as $row) {
                    $this->dropForeignKey($tableName, $columns, $row['CONSTRAINT_NAME']);
                }
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getSqlType($type, $limit = null)
    {
        switch ($type) {
            case static::PHINX_TYPE_STRING:
                return ['name' => 'varchar', 'limit' => $limit ?: 255];
            case static::PHINX_TYPE_CHAR:
                return ['name' => 'char', 'limit' => $limit ?: 255];
            case static::PHINX_TYPE_TEXT:
                if ($limit) {
                    $sizes = [
                        // Order matters! Size must always be tested from longest to shortest!
                        'longtext' => static::TEXT_LONG,
                        'mediumtext' => static::TEXT_MEDIUM,
                        'text' => static::TEXT_REGULAR,
                        'tinytext' => static::TEXT_SMALL,
                    ];
                    foreach ($sizes as $name => $length) {
                        if ($limit >= $length) {
                            return ['name' => $name];
                        }
                    }
                }

                return ['name' => 'text'];
            case static::PHINX_TYPE_BINARY:
                return ['name' => 'binary', 'limit' => $limit ?: 255];
            case static::PHINX_TYPE_VARBINARY:
                return ['name' => 'varbinary', 'limit' => $limit ?: 255];
            case static::PHINX_TYPE_BLOB:
                if ($limit) {
                    $sizes = [
                        // Order matters! Size must always be tested from longest to shortest!
                        'longblob' => static::BLOB_LONG,
                        'mediumblob' => static::BLOB_MEDIUM,
                        'blob' => static::BLOB_REGULAR,
                        'tinyblob' => static::BLOB_SMALL,
                    ];
                    foreach ($sizes as $name => $length) {
                        if ($limit >= $length) {
                            return ['name' => $name];
                        }
                    }
                }

                return ['name' => 'blob'];
            case static::PHINX_TYPE_INTEGER:
                if ($limit && $limit >= static::INT_TINY) {
                    $sizes = [
                        // Order matters! Size must always be tested from longest to shortest!
                        'bigint' => static::INT_BIG,
                        'int' => static::INT_REGULAR,
                        'mediumint' => static::INT_MEDIUM,
                        'smallint' => static::INT_SMALL,
                        'tinyint' => static::INT_TINY,
                    ];
                    $limits = [
                        'int' => 11,
                        'bigint' => 20,
                    ];
                    foreach ($sizes as $name => $length) {
                        if ($limit >= $length) {
                            $def = ['name' => $name];
                            if (isset($limits[$name])) {
                                $def['limit'] = $limits[$name];
                            }

                            return $def;
                        }
                    }
                } elseif (!$limit) {
                    $limit = 11;
                }

                return ['name' => 'int', 'limit' => $limit];
            case static::PHINX_TYPE_BIG_INTEGER:
                return ['name' => 'bigint', 'limit' => 20];
            case static::PHINX_TYPE_FLOAT:
                return ['name' => 'float'];
            case static::PHINX_TYPE_DECIMAL:
                return ['name' => 'decimal'];
            case static::PHINX_TYPE_DATETIME:
                return ['name' => 'datetime'];
            case static::PHINX_TYPE_TIMESTAMP:
                return ['name' => 'timestamp'];
            case static::PHINX_TYPE_TIME:
                return ['name' => 'time'];
            case static::PHINX_TYPE_DATE:
                return ['name' => 'date'];
            case static::PHINX_TYPE_BOOLEAN:
                return ['name' => 'tinyint', 'limit' => 1];
            case static::PHINX_TYPE_UUID:
                return ['name' => 'char', 'limit' => 36];
            // Geospatial database types
            case static::PHINX_TYPE_GEOMETRY:
            case static::PHINX_TYPE_POINT:
            case static::PHINX_TYPE_LINESTRING:
            case static::PHINX_TYPE_POLYGON:
                return ['name' => $type];
            case static::PHINX_TYPE_ENUM:
                return ['name' => 'enum'];
            case static::PHINX_TYPE_SET:
                return ['name' => 'set'];
            case static::TYPE_YEAR:
                if (!$limit || in_array($limit, [2, 4])) {
                    $limit = 4;
                }

                return ['name' => 'year', 'limit' => $limit];
            case static::PHINX_TYPE_JSON:
                return ['name' => 'json'];
            default:
                throw new \RuntimeException('The type: "' . $type . '" is not supported.');
        }
    }

    /**
     * Returns Phinx type by SQL type
     *
     * @param string $sqlTypeDef
     * @throws \RuntimeException
     * @internal param string $sqlType SQL type
     * @returns string Phinx type
     */
    public function getPhinxType($sqlTypeDef)
    {
        $matches = [];
        if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*(.+)*$/', $sqlTypeDef, $matches)) {
            throw new \RuntimeException('Column type ' . $sqlTypeDef . ' is not supported');
        } else {
            $limit = null;
            $precision = null;
            $type = $matches[1];
            if (count($matches) > 2) {
                $limit = $matches[3] ? (int)$matches[3] : null;
            }
            if (count($matches) > 4) {
                $precision = (int)$matches[5];
            }
            if ($type === 'tinyint' && $limit === 1) {
                $type = static::PHINX_TYPE_BOOLEAN;
                $limit = null;
            }
            switch ($type) {
                case 'varchar':
                    $type = static::PHINX_TYPE_STRING;
                    if ($limit === 255) {
                        $limit = null;
                    }
                    break;
                case 'char':
                    $type = static::PHINX_TYPE_CHAR;
                    if ($limit === 255) {
                        $limit = null;
                    }
                    if ($limit === 36) {
                        $type = static::PHINX_TYPE_UUID;
                    }
                    break;
                case 'tinyint':
                    $type = static::PHINX_TYPE_INTEGER;
                    $limit = static::INT_TINY;
                    break;
                case 'smallint':
                    $type = static::PHINX_TYPE_INTEGER;
                    $limit = static::INT_SMALL;
                    break;
                case 'mediumint':
                    $type = static::PHINX_TYPE_INTEGER;
                    $limit = static::INT_MEDIUM;
                    break;
                case 'int':
                    $type = static::PHINX_TYPE_INTEGER;
                    if ($limit === 11) {
                        $limit = null;
                    }
                    break;
                case 'bigint':
                    if ($limit === 20) {
                        $limit = null;
                    }
                    $type = static::PHINX_TYPE_BIG_INTEGER;
                    break;
                case 'blob':
                    $type = static::PHINX_TYPE_BINARY;
                    break;
                case 'tinyblob':
                    $type = static::PHINX_TYPE_BINARY;
                    $limit = static::BLOB_TINY;
                    break;
                case 'mediumblob':
                    $type = static::PHINX_TYPE_BINARY;
                    $limit = static::BLOB_MEDIUM;
                    break;
                case 'longblob':
                    $type = static::PHINX_TYPE_BINARY;
                    $limit = static::BLOB_LONG;
                    break;
                case 'tinytext':
                    $type = static::PHINX_TYPE_TEXT;
                    $limit = static::TEXT_TINY;
                    break;
                case 'mediumtext':
                    $type = static::PHINX_TYPE_TEXT;
                    $limit = static::TEXT_MEDIUM;
                    break;
                case 'longtext':
                    $type = static::PHINX_TYPE_TEXT;
                    $limit = static::TEXT_LONG;
                    break;
            }

            // Call this to check if parsed type is supported.
            $this->getSqlType($type, $limit);

            $phinxType = [
                'name' => $type,
                'limit' => $limit,
                'precision' => $precision
            ];

            if (static::PHINX_TYPE_ENUM == $type) {
                $phinxType['values'] = explode("','", trim($matches[6], "()'"));
            }

            return $phinxType;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function createDatabase($name, $options = [])
    {
        $charset = isset($options['charset']) ? $options['charset'] : 'utf8';

        if (isset($options['collation'])) {
            $this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s` COLLATE `%s`', $name, $charset, $options['collation']));
        } else {
            $this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s`', $name, $charset));
        }
    }

    /**
     * {@inheritdoc}
     */
    public function hasDatabase($name)
    {
        $rows = $this->fetchAll(
            sprintf(
                'SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = \'%s\'',
                $name
            )
        );

        foreach ($rows as $row) {
            if (!empty($row)) {
                return true;
            }
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function dropDatabase($name)
    {
        $this->execute(sprintf('DROP DATABASE IF EXISTS `%s`', $name));
    }

    /**
     * Gets the MySQL Column Definition for a Column object.
     *
     * @param \Phinx\Db\Table\Column $column Column
     * @return string
     */
    protected function getColumnSqlDefinition(Column $column)
    {
        $sqlType = $this->getSqlType($column->getType(), $column->getLimit());

        $def = '';
        $def .= strtoupper($sqlType['name']);
        if ($column->getPrecision() && $column->getScale()) {
            $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
        } elseif (isset($sqlType['limit'])) {
            $def .= '(' . $sqlType['limit'] . ')';
        }
        if (($values = $column->getValues()) && is_array($values)) {
            $def .= "('" . implode("', '", $values) . "')";
        }
        $def .= $column->getEncoding() ? ' CHARACTER SET ' . $column->getEncoding() : '';
        $def .= $column->getCollation() ? ' COLLATE ' . $column->getCollation() : '';
        $def .= (!$column->isSigned() && isset($this->signedColumnTypes[$column->getType()])) ? ' unsigned' : '';
        $def .= ($column->isNull() == false) ? ' NOT NULL' : ' NULL';
        $def .= ($column->isIdentity()) ? ' AUTO_INCREMENT' : '';
        $def .= $this->getDefaultValueDefinition($column->getDefault());

        if ($column->getComment()) {
            $def .= ' COMMENT ' . $this->getConnection()->quote($column->getComment());
        }

        if ($column->getUpdate()) {
            $def .= ' ON UPDATE ' . $column->getUpdate();
        }

        return $def;
    }

    /**
     * Gets the MySQL Index Definition for an Index object.
     *
     * @param \Phinx\Db\Table\Index $index Index
     * @return string
     */
    protected function getIndexSqlDefinition(Index $index)
    {
        $def = '';
        $limit = '';
        if ($index->getLimit()) {
            $limit = '(' . $index->getLimit() . ')';
        }

        if ($index->getType() == Index::UNIQUE) {
            $def .= ' UNIQUE';
        }

        if ($index->getType() == Index::FULLTEXT) {
            $def .= ' FULLTEXT';
        }

        $def .= ' KEY';

        if (is_string($index->getName())) {
            $def .= ' `' . $index->getName() . '`';
        }

        $def .= ' (`' . implode('`,`', $index->getColumns()) . '`' . $limit . ')';

        return $def;
    }

    /**
     * Gets the MySQL Foreign Key Definition for an ForeignKey object.
     *
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
     * @return string
     */
    protected function getForeignKeySqlDefinition(ForeignKey $foreignKey)
    {
        $def = '';
        if ($foreignKey->getConstraint()) {
            $def .= ' CONSTRAINT ' . $this->quoteColumnName($foreignKey->getConstraint());
        }
        $columnNames = [];
        foreach ($foreignKey->getColumns() as $column) {
            $columnNames[] = $this->quoteColumnName($column);
        }
        $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')';
        $refColumnNames = [];
        foreach ($foreignKey->getReferencedColumns() as $column) {
            $refColumnNames[] = $this->quoteColumnName($column);
        }
        $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')';
        if ($foreignKey->getOnDelete()) {
            $def .= ' ON DELETE ' . $foreignKey->getOnDelete();
        }
        if ($foreignKey->getOnUpdate()) {
            $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate();
        }

        return $def;
    }

    /**
     * Describes a database table. This is a MySQL adapter specific method.
     *
     * @param string $tableName Table name
     * @return array
     */
    public function describeTable($tableName)
    {
        $options = $this->getOptions();

        // mysql specific
        $sql = sprintf(
            "SELECT *
             FROM information_schema.tables
             WHERE table_schema = '%s'
             AND table_name = '%s'",
            $options['name'],
            $tableName
        );

        return $this->fetchRow($sql);
    }

    /**
     * Returns MySQL column types (inherited and MySQL specified).
     * @return array
     */
    public function getColumnTypes()
    {
        return array_merge(parent::getColumnTypes(), ['enum', 'set', 'year', 'json']);
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Migration
 */
namespace Phinx\Db\Adapter;

/**
 * Adapter factory and registry.
 *
 * Used for registering adapters and creating instances of adapters.
 *
 * @author Woody Gilk <woody.gilk@gmail.com>
 */
class AdapterFactory
{
    /**
     * @var \Phinx\Db\Adapter\AdapterFactory
     */
    protected static $instance;

    /**
     * Get the factory singleton instance.
     *
     * @return \Phinx\Db\Adapter\AdapterFactory
     */
    public static function instance()
    {
        if (!static::$instance) {
            static::$instance = new static();
        }

        return static::$instance;
    }

    /**
     * Class map of database adapters, indexed by PDO::ATTR_DRIVER_NAME.
     *
     * @var array
     */
    protected $adapters = [
        'mysql' => 'Phinx\Db\Adapter\MysqlAdapter',
        'pgsql' => 'Phinx\Db\Adapter\PostgresAdapter',
        'sqlite' => 'Phinx\Db\Adapter\SQLiteAdapter',
        'sqlsrv' => 'Phinx\Db\Adapter\SqlServerAdapter',
    ];

    /**
     * Class map of adapters wrappers, indexed by name.
     *
     * @var array
     */
    protected $wrappers = [
        'prefix' => 'Phinx\Db\Adapter\TablePrefixAdapter',
        'proxy' => 'Phinx\Db\Adapter\ProxyAdapter',
        'timed' => 'Phinx\Db\Adapter\TimedOutputAdapter',
    ];

    /**
     * Add or replace an adapter with a fully qualified class name.
     *
     * @throws \RuntimeException
     * @param  string $name
     * @param  string $class
     * @return $this
     */
    public function registerAdapter($name, $class)
    {
        if (!is_subclass_of($class, 'Phinx\Db\Adapter\AdapterInterface')) {
            throw new \RuntimeException(sprintf(
                'Adapter class "%s" must implement Phinx\\Db\\Adapter\\AdapterInterface',
                $class
            ));
        }
        $this->adapters[$name] = $class;

        return $this;
    }

    /**
     * Get an adapter class by name.
     *
     * @throws \RuntimeException
     * @param  string $name
     * @return string
     */
    protected function getClass($name)
    {
        if (empty($this->adapters[$name])) {
            throw new \RuntimeException(sprintf(
                'Adapter "%s" has not been registered',
                $name
            ));
        }

        return $this->adapters[$name];
    }

    /**
     * Get an adapter instance by name.
     *
     * @param  string $name
     * @param  array  $options
     * @return \Phinx\Db\Adapter\AdapterInterface
     */
    public function getAdapter($name, array $options)
    {
        $class = $this->getClass($name);

        return new $class($options);
    }

    /**
     * Add or replace a wrapper with a fully qualified class name.
     *
     * @throws \RuntimeException
     * @param  string $name
     * @param  string $class
     * @return $this
     */
    public function registerWrapper($name, $class)
    {
        if (!is_subclass_of($class, 'Phinx\Db\Adapter\WrapperInterface')) {
            throw new \RuntimeException(sprintf(
                'Wrapper class "%s" must be implement Phinx\\Db\\Adapter\\WrapperInterface',
                $class
            ));
        }
        $this->wrappers[$name] = $class;

        return $this;
    }

    /**
     * Get a wrapper class by name.
     *
     * @throws \RuntimeException
     * @param  string $name
     * @return string
     */
    protected function getWrapperClass($name)
    {
        if (empty($this->wrappers[$name])) {
            throw new \RuntimeException(sprintf(
                'Wrapper "%s" has not been registered',
                $name
            ));
        }

        return $this->wrappers[$name];
    }

    /**
     * Get a wrapper instance by name.
     *
     * @param  string $name
     * @param  \Phinx\Db\Adapter\AdapterInterface $adapter
     * @return \Phinx\Db\Adapter\AdapterInterface
     */
    public function getWrapper($name, AdapterInterface $adapter)
    {
        $class = $this->getWrapperClass($name);

        return new $class($adapter);
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Db\Adapter
 */
namespace Phinx\Db\Adapter;

use Phinx\Db\Table;
use Phinx\Db\Table\Column;
use Phinx\Db\Table\ForeignKey;
use Phinx\Db\Table\Index;

/**
 * Table prefix/suffix adapter.
 *
 * Used for inserting a prefix or suffix into table names.
 *
 * @author Samuel Fisher <sam@sfisher.co>
 */
class TablePrefixAdapter extends AdapterWrapper
{
    /**
     * {@inheritdoc}
     */
    public function getAdapterType()
    {
        return 'TablePrefixAdapter';
    }

    /**
     * {@inheritdoc}
     */
    public function hasTable($tableName)
    {
        $adapterTableName = $this->getAdapterTableName($tableName);

        return parent::hasTable($adapterTableName);
    }

    /**
     * {@inheritdoc}
     */
    public function createTable(Table $table)
    {
        $adapterTable = clone $table;
        $adapterTableName = $this->getAdapterTableName($table->getName());
        $adapterTable->setName($adapterTableName);

        foreach ($adapterTable->getForeignKeys() as $fk) {
            $adapterReferenceTable = $fk->getReferencedTable();
            $adapterReferenceTableName = $this->getAdapterTableName($adapterReferenceTable->getName());
            $adapterReferenceTable->setName($adapterReferenceTableName);
        }

        parent::createTable($adapterTable);
    }

    /**
     * {@inheritdoc}
     */
    public function renameTable($tableName, $newTableName)
    {
        $adapterTableName = $this->getAdapterTableName($tableName);
        $adapterNewTableName = $this->getAdapterTableName($newTableName);
        parent::renameTable($adapterTableName, $adapterNewTableName);
    }

    /**
     * {@inheritdoc}
     */
    public function dropTable($tableName)
    {
        $adapterTableName = $this->getAdapterTableName($tableName);
        parent::dropTable($adapterTableName);
    }

    /**
     * {@inheritdoc}
     */
    public function truncateTable($tableName)
    {
        $adapterTableName = $this->getAdapterTableName($tableName);
        parent::truncateTable($adapterTableName);
    }

    /**
     * {@inheritdoc}
     */
    public function getColumns($tableName)
    {
        $adapterTableName = $this->getAdapterTableName($tableName);

        return parent::getColumns($adapterTableName);
    }

    /**
     * {@inheritdoc}
     */
    public function hasColumn($tableName, $columnName)
    {
        $adapterTableName = $this->getAdapterTableName($tableName);

        return parent::hasColumn($adapterTableName, $columnName);
    }

    /**
     * {@inheritdoc}
     */
    public function addColumn(Table $table, Column $column)
    {
        $adapterTable = clone $table;
        $adapterTableName = $this->getAdapterTableName($table->getName());
        $adapterTable->setName($adapterTableName);
        parent::addColumn($adapterTable, $column);
    }

    /**
     * {@inheritdoc}
     */
    public function renameColumn($tableName, $columnName, $newColumnName)
    {
        $adapterTableName = $this->getAdapterTableName($tableName);
        parent::renameColumn($adapterTableName, $columnName, $newColumnName);
    }

    /**
     * {@inheritdoc}
     */
    public function changeColumn($tableName, $columnName, Column $newColumn)
    {
        $adapterTableName = $this->getAdapterTableName($tableName);

        return parent::changeColumn($adapterTableName, $columnName, $newColumn);
    }

    /**
     * {@inheritdoc}
     */
    public function dropColumn($tableName, $columnName)
    {
        $adapterTableName = $this->getAdapterTableName($tableName);
        parent::dropColumn($adapterTableName, $columnName);
    }

    /**
     * {@inheritdoc}
     */
    public function hasIndex($tableName, $columns)
    {
        $adapterTableName = $this->getAdapterTableName($tableName);

        return parent::hasIndex($adapterTableName, $columns);
    }

    /**
     * {@inheritdoc}
     */
    public function hasIndexByName($tableName, $indexName)
    {
        $adapterTableName = $this->getAdapterTableName($tableName);

        return parent::hasIndexByName($adapterTableName, $indexName);
    }

    /**
     * {@inheritdoc}
     */
    public function addIndex(Table $table, Index $index)
    {
        $adapterTable = clone $table;
        $adapterTableName = $this->getAdapterTableName($table->getName());
        $adapterTable->setName($adapterTableName);
        parent::addIndex($adapterTable, $index);
    }

    /**
     * {@inheritdoc}
     */
    public function dropIndex($tableName, $columns)
    {
        $adapterTableName = $this->getAdapterTableName($tableName);
        parent::dropIndex($adapterTableName, $columns);
    }

    /**
     * {@inheritdoc}
     */
    public function dropIndexByName($tableName, $indexName)
    {
        $adapterTableName = $this->getAdapterTableName($tableName);
        parent::dropIndexByName($adapterTableName, $indexName);
    }

    /**
     * {@inheritdoc}
     */
    public function hasForeignKey($tableName, $columns, $constraint = null)
    {
        $adapterTableName = $this->getAdapterTableName($tableName);

        return parent::hasForeignKey($adapterTableName, $columns, $constraint);
    }

    /**
     * {@inheritdoc}
     */
    public function addForeignKey(Table $table, ForeignKey $foreignKey)
    {
        $adapterTable = clone $table;
        $adapterTableName = $this->getAdapterTableName($table->getName());
        $adapterTable->setName($adapterTableName);
        parent::addForeignKey($adapterTable, $foreignKey);
    }

    /**
     * {@inheritdoc}
     */
    public function dropForeignKey($tableName, $columns, $constraint = null)
    {
        $adapterTableName = $this->getAdapterTableName($tableName);
        parent::dropForeignKey($adapterTableName, $columns, $constraint);
    }

    /**
     * {@inheritdoc}
     */
    public function insert(Table $table, $row)
    {
        $adapterTable = clone $table;
        $adapterTableName = $this->getAdapterTableName($table->getName());
        $adapterTable->setName($adapterTableName);
        parent::insert($adapterTable, $row);
    }

    /**
     * {@inheritdoc}
     */
    public function bulkinsert(Table $table, $rows)
    {
        $adapterTable = clone $table;
        $adapterTableName = $this->getAdapterTableName($table->getName());
        $adapterTable->setName($adapterTableName);
        parent::bulkinsert($adapterTable, $rows);
    }

    /**
     * Gets the table prefix.
     *
     * @return string
     */
    public function getPrefix()
    {
        return (string)$this->getOption('table_prefix');
    }

    /**
     * Gets the table suffix.
     *
     * @return string
     */
    public function getSuffix()
    {
        return (string)$this->getOption('table_suffix');
    }

    /**
     * Applies the prefix and suffix to the table name.
     *
     * @param string $tableName
     * @return string
     */
    public function getAdapterTableName($tableName)
    {
        return $this->getPrefix() . $tableName . $this->getSuffix();
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Db\Adapter
 */
namespace Phinx\Db\Adapter;

use Phinx\Db\Table;
use Phinx\Db\Table\Column;
use Phinx\Db\Table\ForeignKey;
use Phinx\Db\Table\Index;
use Phinx\Migration\MigrationInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Adapter Interface.
 *
 * @author Rob Morgan <robbym@gmail.com>
 * @method \PDO getConnection()
 */
interface AdapterInterface
{
    const PHINX_TYPE_STRING = 'string';
    const PHINX_TYPE_CHAR = 'char';
    const PHINX_TYPE_TEXT = 'text';
    const PHINX_TYPE_INTEGER = 'integer';
    const PHINX_TYPE_BIG_INTEGER = 'biginteger';
    const PHINX_TYPE_FLOAT = 'float';
    const PHINX_TYPE_DECIMAL = 'decimal';
    const PHINX_TYPE_DATETIME = 'datetime';
    const PHINX_TYPE_TIMESTAMP = 'timestamp';
    const PHINX_TYPE_TIME = 'time';
    const PHINX_TYPE_DATE = 'date';
    const PHINX_TYPE_BINARY = 'binary';
    const PHINX_TYPE_VARBINARY = 'varbinary';
    const PHINX_TYPE_BLOB = 'blob';
    const PHINX_TYPE_BOOLEAN = 'boolean';
    const PHINX_TYPE_JSON = 'json';
    const PHINX_TYPE_JSONB = 'jsonb';
    const PHINX_TYPE_UUID = 'uuid';
    const PHINX_TYPE_FILESTREAM = 'filestream';

    // Geospatial database types
    const PHINX_TYPE_GEOMETRY = 'geometry';
    const PHINX_TYPE_POINT = 'point';
    const PHINX_TYPE_LINESTRING = 'linestring';
    const PHINX_TYPE_POLYGON = 'polygon';

    // only for mysql so far
    const PHINX_TYPE_ENUM = 'enum';
    const PHINX_TYPE_SET = 'set';

    // only for postgresql so far
    const PHINX_TYPE_CIDR = 'cidr';
    const PHINX_TYPE_INET = 'inet';
    const PHINX_TYPE_MACADDR = 'macaddr';
    const PHINX_TYPE_INTERVAL = 'interval';

    /**
     * Get all migrated version numbers.
     *
     * @return array
     */
    public function getVersions();

    /**
     * Get all migration log entries, indexed by version creation time and sorted ascendingly by the configuration's
     * version order option
     *
     * @return array
     */
    public function getVersionLog();

    /**
     * Set adapter configuration options.
     *
     * @param  array $options
     * @return \Phinx\Db\Adapter\AdapterInterface
     */
    public function setOptions(array $options);

    /**
     * Get all adapter options.
     *
     * @return array
     */
    public function getOptions();

    /**
     * Check if an option has been set.
     *
     * @param  string $name
     * @return bool
     */
    public function hasOption($name);

    /**
     * Get a single adapter option, or null if the option does not exist.
     *
     * @param  string $name
     * @return mixed
     */
    public function getOption($name);

    /**
     * Sets the console input.
     *
     * @param \Symfony\Component\Console\Input\InputInterface $input Input
     * @return \Phinx\Db\Adapter\AdapterInterface
     */
    public function setInput(InputInterface $input);

    /**
     * Gets the console input.
     *
     * @return \Symfony\Component\Console\Input\InputInterface
     */
    public function getInput();

    /**
     * Sets the console output.
     *
     * @param \Symfony\Component\Console\Output\OutputInterface $output Output
     * @return \Phinx\Db\Adapter\AdapterInterface
     */
    public function setOutput(OutputInterface $output);

    /**
     * Gets the console output.
     *
     * @return \Symfony\Component\Console\Output\OutputInterface
     */
    public function getOutput();

    /**
     * Records a migration being run.
     *
     * @param \Phinx\Migration\MigrationInterface $migration Migration
     * @param string $direction Direction
     * @param int $startTime Start Time
     * @param int $endTime End Time
     * @return \Phinx\Db\Adapter\AdapterInterface
     */
    public function migrated(MigrationInterface $migration, $direction, $startTime, $endTime);

    /**
     * Toggle a migration breakpoint.
     *
     * @param \Phinx\Migration\MigrationInterface $migration
     *
     * @return \Phinx\Db\Adapter\AdapterInterface
     */
    public function toggleBreakpoint(MigrationInterface $migration);

    /**
     * Reset all migration breakpoints.
     *
     * @return int The number of breakpoints reset
     */
    public function resetAllBreakpoints();

    /**
     * Does the schema table exist?
     *
     * @deprecated use hasTable instead.
     * @return bool
     */
    public function hasSchemaTable();

    /**
     * Creates the schema table.
     *
     * @return void
     */
    public function createSchemaTable();

    /**
     * Returns the adapter type.
     *
     * @return string
     */
    public function getAdapterType();

    /**
     * Initializes the database connection.
     *
     * @throws \RuntimeException When the requested database driver is not installed.
     * @return void
     */
    public function connect();

    /**
     * Closes the database connection.
     *
     * @return void
     */
    public function disconnect();

    /**
     * Does the adapter support transactions?
     *
     * @return bool
     */
    public function hasTransactions();

    /**
     * Begin a transaction.
     *
     * @return void
     */
    public function beginTransaction();

    /**
     * Commit a transaction.
     *
     * @return void
     */
    public function commitTransaction();

    /**
     * Rollback a transaction.
     *
     * @return void
     */
    public function rollbackTransaction();

    /**
     * Executes a SQL statement and returns the number of affected rows.
     *
     * @param string $sql SQL
     * @return int
     */
    public function execute($sql);

    /**
     * Executes a SQL statement and returns the result as an array.
     *
     * @param string $sql SQL
     * @return mixed
     */
    public function query($sql);

    /**
     * Executes a query and returns only one row as an array.
     *
     * @param string $sql SQL
     * @return array
     */
    public function fetchRow($sql);

    /**
     * Executes a query and returns an array of rows.
     *
     * @param string $sql SQL
     * @return array
     */
    public function fetchAll($sql);

    /**
     * Inserts data into a table.
     *
     * @param \Phinx\Db\Table $table where to insert data
     * @param array $row
     * @return void
     */
    public function insert(Table $table, $row);

    /**
     * Inserts data into a table in a bulk.
     *
     * @param \Phinx\Db\Table $table where to insert data
     * @param array $rows
     * @return void
     */
    public function bulkinsert(Table $table, $rows);

    /**
     * Quotes a table name for use in a query.
     *
     * @param string $tableName Table Name
     * @return string
     */
    public function quoteTableName($tableName);

    /**
     * Quotes a column name for use in a query.
     *
     * @param string $columnName Table Name
     * @return string
     */
    public function quoteColumnName($columnName);

    /**
     * Checks to see if a table exists.
     *
     * @param string $tableName Table Name
     * @return bool
     */
    public function hasTable($tableName);

    /**
     * Creates the specified database table.
     *
     * @param \Phinx\Db\Table $table Table
     * @return void
     */
    public function createTable(Table $table);

    /**
     * Renames the specified database table.
     *
     * @param string $tableName Table Name
     * @param string $newName   New Name
     * @return void
     */
    public function renameTable($tableName, $newName);

    /**
     * Drops the specified database table.
     *
     * @param string $tableName Table Name
     * @return void
     */
    public function dropTable($tableName);

    /**
     * Truncates the specified table
     *
     * @param string $tableName
     * @return void
     */
    public function truncateTable($tableName);

    /**
     * Returns table columns
     *
     * @param string $tableName Table Name
     * @return \Phinx\Db\Table\Column[]
     */
    public function getColumns($tableName);

    /**
     * Checks to see if a column exists.
     *
     * @param string $tableName  Table Name
     * @param string $columnName Column Name
     * @return bool
     */
    public function hasColumn($tableName, $columnName);

    /**
     * Adds the specified column to a database table.
     *
     * @param \Phinx\Db\Table  $table  Table
     * @param \Phinx\Db\Table\Column $column Column
     * @return void
     */
    public function addColumn(Table $table, Column $column);

    /**
     * Renames the specified column.
     *
     * @param string $tableName Table Name
     * @param string $columnName Column Name
     * @param string $newColumnName New Column Name
     * @return void
     */
    public function renameColumn($tableName, $columnName, $newColumnName);

    /**
     * Change a table column type.
     *
     * @param string $tableName  Table Name
     * @param string $columnName Column Name
     * @param \Phinx\Db\Table\Column $newColumn  New Column
     * @return \Phinx\Db\Table
     */
    public function changeColumn($tableName, $columnName, Column $newColumn);

    /**
     * Drops the specified column.
     *
     * @param string $tableName Table Name
     * @param string $columnName Column Name
     * @return void
     */
    public function dropColumn($tableName, $columnName);

    /**
     * Checks to see if an index exists.
     *
     * @param string $tableName Table Name
     * @param mixed  $columns   Column(s)
     * @return bool
     */
    public function hasIndex($tableName, $columns);

    /**
     * Checks to see if an index specified by name exists.
     *
     * @param string $tableName Table Name
     * @param string $indexName
     * @return bool
     */
    public function hasIndexByName($tableName, $indexName);

    /**
     * Adds the specified index to a database table.
     *
     * @param \Phinx\Db\Table $table Table
     * @param \Phinx\Db\Table\Index $index Index
     * @return void
     */
    public function addIndex(Table $table, Index $index);

    /**
     * Drops the specified index from a database table.
     *
     * @param string $tableName
     * @param mixed  $columns Column(s)
     * @return void
     */
    public function dropIndex($tableName, $columns);

    /**
     * Drops the index specified by name from a database table.
     *
     * @param string $tableName
     * @param string $indexName
     * @return void
     */
    public function dropIndexByName($tableName, $indexName);

    /**
     * Checks to see if a foreign key exists.
     *
     * @param string   $tableName
     * @param string[] $columns    Column(s)
     * @param string   $constraint Constraint name
     * @return bool
     */
    public function hasForeignKey($tableName, $columns, $constraint = null);

    /**
     * Adds the specified foreign key to a database table.
     *
     * @param \Phinx\Db\Table      $table
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
     * @return void
     */
    public function addForeignKey(Table $table, ForeignKey $foreignKey);

    /**
     * Drops the specified foreign key from a database table.
     *
     * @param string   $tableName
     * @param string[] $columns    Column(s)
     * @param string   $constraint Constraint name
     * @return void
     */
    public function dropForeignKey($tableName, $columns, $constraint = null);

    /**
     * Returns an array of the supported Phinx column types.
     *
     * @return array
     */
    public function getColumnTypes();

    /**
     * Checks that the given column is of a supported type.
     *
     * @param  \Phinx\Db\Table\Column $column
     * @return bool
     */
    public function isValidColumnType(Column $column);

    /**
     * Converts the Phinx logical type to the adapter's SQL type.
     *
     * @param string $type
     * @param int $limit
     * @return string[]
     */
    public function getSqlType($type, $limit = null);

    /**
     * Creates a new database.
     *
     * @param string $name Database Name
     * @param array $options Options
     * @return void
     */
    public function createDatabase($name, $options = []);

    /**
     * Checks to see if a database exists.
     *
     * @param string $name Database Name
     * @return bool
     */
    public function hasDatabase($name);

    /**
     * Drops the specified database.
     *
     * @param string $name Database Name
     * @return void
     */
    public function dropDatabase($name);

    /**
     * Creates the specified schema or throws an exception
     * if there is no support for it.
     *
     * @param  string $schemaName Schema Name
     * @return void
     */
    public function createSchema($schemaName = 'public');

    /**
     * Drops the specified schema table  or throws an exception
     * if there is no support for it.
     *
     * @param string $schemaName Schema name
     * @return void
     */
    public function dropSchema($schemaName);

    /**
     * Cast a value to a boolean appropriate for the adapter.
     *
     * @param mixed $value The value to be cast
     *
     * @return mixed
     */
    public function castToBool($value);
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Db\Adapter
 */
namespace Phinx\Db\Adapter;

use Phinx\Db\Table;
use Phinx\Db\Table\Column;
use Phinx\Db\Table\ForeignKey;
use Phinx\Db\Table\Index;

class PostgresAdapter extends PdoAdapter implements AdapterInterface
{
    const INT_SMALL = 65535;

    /**
     * Columns with comments
     *
     * @var array
     */
    protected $columnsWithComments = [];

    /**
     * {@inheritdoc}
     */
    public function connect()
    {
        if ($this->connection === null) {
            if (!class_exists('PDO') || !in_array('pgsql', \PDO::getAvailableDrivers(), true)) {
                // @codeCoverageIgnoreStart
                throw new \RuntimeException('You need to enable the PDO_Pgsql extension for Phinx to run properly.');
                // @codeCoverageIgnoreEnd
            }

            $db = null;
            $options = $this->getOptions();

            // if port is specified use it, otherwise use the PostgreSQL default
            if (isset($options['port'])) {
                $dsn = 'pgsql:host=' . $options['host'] . ';port=' . $options['port'] . ';dbname=' . $options['name'];
            } else {
                $dsn = 'pgsql:host=' . $options['host'] . ';dbname=' . $options['name'];
            }

            try {
                $db = new \PDO($dsn, $options['user'], $options['pass'], [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]);
            } catch (\PDOException $exception) {
                throw new \InvalidArgumentException(sprintf(
                    'There was a problem connecting to the database: %s',
                    $exception->getMessage()
                ));
            }

            $this->setConnection($db);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function disconnect()
    {
        $this->connection = null;
    }

    /**
     * {@inheritdoc}
     */
    public function hasTransactions()
    {
        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function beginTransaction()
    {
        $this->execute('BEGIN');
    }

    /**
     * {@inheritdoc}
     */
    public function commitTransaction()
    {
        $this->execute('COMMIT');
    }

    /**
     * {@inheritdoc}
     */
    public function rollbackTransaction()
    {
        $this->execute('ROLLBACK');
    }

    /**
     * Quotes a schema name for use in a query.
     *
     * @param string $schemaName Schema Name
     * @return string
     */
    public function quoteSchemaName($schemaName)
    {
        return $this->quoteColumnName($schemaName);
    }

    /**
     * {@inheritdoc}
     */
    public function quoteTableName($tableName)
    {
        return $this->quoteSchemaName($this->getSchemaName()) . '.' . $this->quoteColumnName($tableName);
    }

    /**
     * {@inheritdoc}
     */
    public function quoteColumnName($columnName)
    {
        return '"' . $columnName . '"';
    }

    /**
     * {@inheritdoc}
     */
    public function hasTable($tableName)
    {
        $result = $this->getConnection()->query(
            sprintf(
                'SELECT *
                FROM information_schema.tables
                WHERE table_schema = %s
                AND lower(table_name) = lower(%s)',
                $this->getConnection()->quote($this->getSchemaName()),
                $this->getConnection()->quote($tableName)
            )
        );

        return $result->rowCount() === 1;
    }

    /**
     * {@inheritdoc}
     */
    public function createTable(Table $table)
    {
        $options = $table->getOptions();

         // Add the default primary key
        $columns = $table->getPendingColumns();
        if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
            $column = new Column();
            $column->setName('id')
                   ->setType('integer')
                   ->setIdentity(true);

            array_unshift($columns, $column);
            $options['primary_key'] = 'id';
        } elseif (isset($options['id']) && is_string($options['id'])) {
            // Handle id => "field_name" to support AUTO_INCREMENT
            $column = new Column();
            $column->setName($options['id'])
                   ->setType('integer')
                   ->setIdentity(true);

            array_unshift($columns, $column);
            $options['primary_key'] = $options['id'];
        }

        // TODO - process table options like collation etc
        $sql = 'CREATE TABLE ';
        $sql .= $this->quoteTableName($table->getName()) . ' (';

        $this->columnsWithComments = [];
        foreach ($columns as $column) {
            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';

            // set column comments, if needed
            if ($column->getComment()) {
                $this->columnsWithComments[] = $column;
            }
        }

         // set the primary key(s)
        if (isset($options['primary_key'])) {
            $sql = rtrim($sql);
            $sql .= sprintf(' CONSTRAINT %s_pkey PRIMARY KEY (', $table->getName());
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
                $sql .= $this->quoteColumnName($options['primary_key']);
            } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
            }
            $sql .= ')';
        } else {
            $sql = substr(rtrim($sql), 0, -1); // no primary keys
        }

        // set the foreign keys
        $foreignKeys = $table->getForeignKeys();
        if (!empty($foreignKeys)) {
            foreach ($foreignKeys as $foreignKey) {
                $sql .= ', ' . $this->getForeignKeySqlDefinition($foreignKey, $table->getName());
            }
        }

        $sql .= ');';

        // process column comments
        if (!empty($this->columnsWithComments)) {
            foreach ($this->columnsWithComments as $column) {
                $sql .= $this->getColumnCommentSqlDefinition($column, $table->getName());
            }
        }

        // set the indexes
        $indexes = $table->getIndexes();
        if (!empty($indexes)) {
            foreach ($indexes as $index) {
                $sql .= $this->getIndexSqlDefinition($index, $table->getName());
            }
        }

        // execute the sql
        $this->execute($sql);

        // process table comments
        if (isset($options['comment'])) {
            $sql = sprintf(
                'COMMENT ON TABLE %s IS %s',
                $this->quoteTableName($table->getName()),
                $this->getConnection()->quote($options['comment'])
            );
            $this->execute($sql);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function renameTable($tableName, $newTableName)
    {
        $sql = sprintf(
            'ALTER TABLE %s RENAME TO %s',
            $this->quoteTableName($tableName),
            $this->quoteColumnName($newTableName)
        );
        $this->execute($sql);
    }

    /**
     * {@inheritdoc}
     */
    public function dropTable($tableName)
    {
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName)));
    }

    /**
     * {@inheritdoc}
     */
    public function truncateTable($tableName)
    {
        $sql = sprintf(
            'TRUNCATE TABLE %s',
            $this->quoteTableName($tableName)
        );

        $this->execute($sql);
    }

    /**
     * {@inheritdoc}
     */
    public function getColumns($tableName)
    {
        $columns = [];
        $sql = sprintf(
            "SELECT column_name, data_type, is_identity, is_nullable,
             column_default, character_maximum_length, numeric_precision, numeric_scale
             FROM information_schema.columns
             WHERE table_name ='%s'",
            $tableName
        );
        $columnsInfo = $this->fetchAll($sql);

        foreach ($columnsInfo as $columnInfo) {
            $column = new Column();
            $column->setName($columnInfo['column_name'])
                   ->setType($this->getPhinxType($columnInfo['data_type']))
                   ->setNull($columnInfo['is_nullable'] === 'YES')
                   ->setDefault($columnInfo['column_default'])
                   ->setIdentity($columnInfo['is_identity'] === 'YES')
                   ->setPrecision($columnInfo['numeric_precision'])
                   ->setScale($columnInfo['numeric_scale']);

            if (preg_match('/\bwith time zone$/', $columnInfo['data_type'])) {
                $column->setTimezone(true);
            }

            if (isset($columnInfo['character_maximum_length'])) {
                $column->setLimit($columnInfo['character_maximum_length']);
            }
            $columns[] = $column;
        }

        return $columns;
    }

    /**
     * {@inheritdoc}
     */
    public function hasColumn($tableName, $columnName)
    {
        $sql = sprintf(
            "SELECT count(*)
            FROM information_schema.columns
            WHERE table_schema = '%s' AND table_name = '%s' AND column_name = '%s'",
            $this->getSchemaName(),
            $tableName,
            $columnName
        );

        $result = $this->fetchRow($sql);

        return $result['count'] > 0;
    }

    /**
     * {@inheritdoc}
     */
    public function addColumn(Table $table, Column $column)
    {
        $sql = sprintf(
            'ALTER TABLE %s ADD %s %s;',
            $this->quoteTableName($table->getName()),
            $this->quoteColumnName($column->getName()),
            $this->getColumnSqlDefinition($column)
        );

        if ($column->getComment()) {
            $sql .= $this->getColumnCommentSqlDefinition($column, $table->getName());
        }

        $this->execute($sql);
    }

    /**
     * {@inheritdoc}
     */
    public function renameColumn($tableName, $columnName, $newColumnName)
    {
        $sql = sprintf(
            "SELECT CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END AS column_exists
             FROM information_schema.columns
             WHERE table_name ='%s' AND column_name = '%s'",
            $tableName,
            $columnName
        );
        $result = $this->fetchRow($sql);
        if (!(bool)$result['column_exists']) {
            throw new \InvalidArgumentException("The specified column does not exist: $columnName");
        }
        $this->execute(
            sprintf(
                'ALTER TABLE %s RENAME COLUMN %s TO %s',
                $this->quoteTableName($tableName),
                $this->quoteColumnName($columnName),
                $this->quoteColumnName($newColumnName)
            )
        );
    }

    /**
     * {@inheritdoc}
     */
    public function changeColumn($tableName, $columnName, Column $newColumn)
    {
        // TODO - is it possible to merge these 3 queries into less?
        // change data type
        $sql = sprintf(
            'ALTER TABLE %s ALTER COLUMN %s TYPE %s',
            $this->quoteTableName($tableName),
            $this->quoteColumnName($columnName),
            $this->getColumnSqlDefinition($newColumn)
        );
        //NULL and DEFAULT cannot be set while changing column type
        $sql = preg_replace('/ NOT NULL/', '', $sql);
        $sql = preg_replace('/ NULL/', '', $sql);
        //If it is set, DEFAULT is the last definition
        $sql = preg_replace('/DEFAULT .*/', '', $sql);
        $this->execute($sql);
        // process null
        $sql = sprintf(
            'ALTER TABLE %s ALTER COLUMN %s',
            $this->quoteTableName($tableName),
            $this->quoteColumnName($columnName)
        );
        if ($newColumn->isNull()) {
            $sql .= ' DROP NOT NULL';
        } else {
            $sql .= ' SET NOT NULL';
        }
        $this->execute($sql);
        if (!is_null($newColumn->getDefault())) {
            //change default
            $this->execute(
                sprintf(
                    'ALTER TABLE %s ALTER COLUMN %s SET %s',
                    $this->quoteTableName($tableName),
                    $this->quoteColumnName($columnName),
                    $this->getDefaultValueDefinition($newColumn->getDefault())
                )
            );
        } else {
            //drop default
            $this->execute(
                sprintf(
                    'ALTER TABLE %s ALTER COLUMN %s DROP DEFAULT',
                    $this->quoteTableName($tableName),
                    $this->quoteColumnName($columnName)
                )
            );
        }
        // rename column
        if ($columnName !== $newColumn->getName()) {
            $this->execute(
                sprintf(
                    'ALTER TABLE %s RENAME COLUMN %s TO %s',
                    $this->quoteTableName($tableName),
                    $this->quoteColumnName($columnName),
                    $this->quoteColumnName($newColumn->getName())
                )
            );
        }

        // change column comment if needed
        if ($newColumn->getComment()) {
            $sql = $this->getColumnCommentSqlDefinition($newColumn, $tableName);
            $this->execute($sql);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function dropColumn($tableName, $columnName)
    {
        $this->execute(
            sprintf(
                'ALTER TABLE %s DROP COLUMN %s',
                $this->quoteTableName($tableName),
                $this->quoteColumnName($columnName)
            )
        );
    }

    /**
     * Get an array of indexes from a particular table.
     *
     * @param string $tableName Table Name
     * @return array
     */
    protected function getIndexes($tableName)
    {
        $indexes = [];
        $sql = "SELECT
            i.relname AS index_name,
            a.attname AS column_name
        FROM
            pg_class t,
            pg_class i,
            pg_index ix,
            pg_attribute a
        WHERE
            t.oid = ix.indrelid
            AND i.oid = ix.indexrelid
            AND a.attrelid = t.oid
            AND a.attnum = ANY(ix.indkey)
            AND t.relkind = 'r'
            AND t.relname = '$tableName'
        ORDER BY
            t.relname,
            i.relname;";
        $rows = $this->fetchAll($sql);
        foreach ($rows as $row) {
            if (!isset($indexes[$row['index_name']])) {
                $indexes[$row['index_name']] = ['columns' => []];
            }
            $indexes[$row['index_name']]['columns'][] = strtolower($row['column_name']);
        }

        return $indexes;
    }

    /**
     * {@inheritdoc}
     */
    public function hasIndex($tableName, $columns)
    {
        if (is_string($columns)) {
            $columns = [$columns];
        }
        $columns = array_map('strtolower', $columns);
        $indexes = $this->getIndexes($tableName);
        foreach ($indexes as $index) {
            if (array_diff($index['columns'], $columns) === array_diff($columns, $index['columns'])) {
                return true;
            }
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function hasIndexByName($tableName, $indexName)
    {
        $indexes = $this->getIndexes($tableName);
        foreach ($indexes as $name => $index) {
            if ($name === $indexName) {
                return true;
            }
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function addIndex(Table $table, Index $index)
    {
        $sql = $this->getIndexSqlDefinition($index, $table->getName());
        $this->execute($sql);
    }

    /**
     * {@inheritdoc}
     */
    public function dropIndex($tableName, $columns)
    {
        if (is_string($columns)) {
            $columns = [$columns]; // str to array
        }

        $indexes = $this->getIndexes($tableName);
        $columns = array_map('strtolower', $columns);

        foreach ($indexes as $indexName => $index) {
            $a = array_diff($columns, $index['columns']);
            if (empty($a)) {
                $this->execute(
                    sprintf(
                        'DROP INDEX IF EXISTS %s',
                        $this->quoteColumnName($indexName)
                    )
                );

                return;
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function dropIndexByName($tableName, $indexName)
    {
        $sql = sprintf(
            'DROP INDEX IF EXISTS %s',
            $indexName
        );
        $this->execute($sql);
    }

    /**
     * {@inheritdoc}
     */
    public function hasForeignKey($tableName, $columns, $constraint = null)
    {
        if (is_string($columns)) {
            $columns = [$columns]; // str to array
        }
        $foreignKeys = $this->getForeignKeys($tableName);
        if ($constraint) {
            if (isset($foreignKeys[$constraint])) {
                return !empty($foreignKeys[$constraint]);
            }

            return false;
        } else {
            foreach ($foreignKeys as $key) {
                $a = array_diff($columns, $key['columns']);
                if (empty($a)) {
                    return true;
                }
            }

            return false;
        }
    }

    /**
     * Get an array of foreign keys from a particular table.
     *
     * @param string $tableName Table Name
     * @return array
     */
    protected function getForeignKeys($tableName)
    {
        $foreignKeys = [];
        $rows = $this->fetchAll(sprintf(
            "SELECT
                    tc.constraint_name,
                    tc.table_name, kcu.column_name,
                    ccu.table_name AS referenced_table_name,
                    ccu.column_name AS referenced_column_name
                FROM
                    information_schema.table_constraints AS tc
                    JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
                    JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
                WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s'
                ORDER BY kcu.position_in_unique_constraint",
            $tableName
        ));
        foreach ($rows as $row) {
            $foreignKeys[$row['constraint_name']]['table'] = $row['table_name'];
            $foreignKeys[$row['constraint_name']]['columns'][] = $row['column_name'];
            $foreignKeys[$row['constraint_name']]['referenced_table'] = $row['referenced_table_name'];
            $foreignKeys[$row['constraint_name']]['referenced_columns'][] = $row['referenced_column_name'];
        }

        return $foreignKeys;
    }

    /**
     * {@inheritdoc}
     */
    public function addForeignKey(Table $table, ForeignKey $foreignKey)
    {
        $sql = sprintf(
            'ALTER TABLE %s ADD %s',
            $this->quoteTableName($table->getName()),
            $this->getForeignKeySqlDefinition($foreignKey, $table->getName())
        );
        $this->execute($sql);
    }

    /**
     * {@inheritdoc}
     */
    public function dropForeignKey($tableName, $columns, $constraint = null)
    {
        if (is_string($columns)) {
            $columns = [$columns]; // str to array
        }

        if ($constraint) {
            $this->execute(
                sprintf(
                    'ALTER TABLE %s DROP CONSTRAINT %s',
                    $this->quoteTableName($tableName),
                    $constraint
                )
            );
        } else {
            foreach ($columns as $column) {
                $rows = $this->fetchAll(sprintf(
                    "SELECT CONSTRAINT_NAME
                      FROM information_schema.KEY_COLUMN_USAGE
                      WHERE TABLE_SCHEMA = CURRENT_SCHEMA()
                        AND TABLE_NAME IS NOT NULL
                        AND TABLE_NAME = '%s'
                        AND COLUMN_NAME = '%s'
                      ORDER BY POSITION_IN_UNIQUE_CONSTRAINT",
                    $tableName,
                    $column
                ));

                foreach ($rows as $row) {
                    $this->dropForeignKey($tableName, $columns, $row['constraint_name']);
                }
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getSqlType($type, $limit = null)
    {
        switch ($type) {
            case static::PHINX_TYPE_INTEGER:
                if ($limit && $limit == static::INT_SMALL) {
                    return [
                        'name' => 'smallint',
                        'limit' => static::INT_SMALL
                    ];
                }

                return ['name' => $type];
            case static::PHINX_TYPE_TEXT:
            case static::PHINX_TYPE_TIME:
            case static::PHINX_TYPE_DATE:
            case static::PHINX_TYPE_BOOLEAN:
            case static::PHINX_TYPE_JSON:
            case static::PHINX_TYPE_JSONB:
            case static::PHINX_TYPE_UUID:
            case static::PHINX_TYPE_CIDR:
            case static::PHINX_TYPE_INET:
            case static::PHINX_TYPE_MACADDR:
                return ['name' => $type];
            case static::PHINX_TYPE_DECIMAL:
                return ['name' => $type, 'precision' => 18, 'scale' => 0];
            case static::PHINX_TYPE_STRING:
                return ['name' => 'character varying', 'limit' => 255];
            case static::PHINX_TYPE_CHAR:
                return ['name' => 'character', 'limit' => 255];
            case static::PHINX_TYPE_BIG_INTEGER:
                return ['name' => 'bigint'];
            case static::PHINX_TYPE_FLOAT:
                return ['name' => 'real'];
            case static::PHINX_TYPE_DATETIME:
            case static::PHINX_TYPE_TIMESTAMP:
                return ['name' => 'timestamp'];
            case static::PHINX_TYPE_BLOB:
            case static::PHINX_TYPE_BINARY:
                return ['name' => 'bytea'];
            case static::PHINX_TYPE_INTERVAL:
                return ['name' => 'interval'];
            // Geospatial database types
            // Spatial storage in Postgres is done via the PostGIS extension,
            // which enables the use of the "geography" type in combination
            // with SRID 4326.
            case static::PHINX_TYPE_GEOMETRY:
                return ['name' => 'geography', 'type' => 'geometry', 'srid' => 4326];
            case static::PHINX_TYPE_POINT:
                return ['name' => 'geography', 'type' => 'point', 'srid' => 4326];
            case static::PHINX_TYPE_LINESTRING:
                return ['name' => 'geography', 'type' => 'linestring', 'srid' => 4326];
            case static::PHINX_TYPE_POLYGON:
                return ['name' => 'geography', 'type' => 'polygon', 'srid' => 4326];
            default:
                if ($this->isArrayType($type)) {
                    return ['name' => $type];
                }
                // Return array type
                throw new \RuntimeException('The type: "' . $type . '" is not supported');
        }
    }

    /**
     * Returns Phinx type by SQL type
     *
     * @param string $sqlType SQL type
     * @returns string Phinx type
     */
    public function getPhinxType($sqlType)
    {
        switch ($sqlType) {
            case 'character varying':
            case 'varchar':
                return static::PHINX_TYPE_STRING;
            case 'character':
            case 'char':
                return static::PHINX_TYPE_CHAR;
            case 'text':
                return static::PHINX_TYPE_TEXT;
            case 'json':
                return static::PHINX_TYPE_JSON;
            case 'jsonb':
                return static::PHINX_TYPE_JSONB;
            case 'smallint':
                return [
                    'name' => 'smallint',
                    'limit' => static::INT_SMALL
                ];
            case 'int':
            case 'int4':
            case 'integer':
                return static::PHINX_TYPE_INTEGER;
            case 'decimal':
            case 'numeric':
                return static::PHINX_TYPE_DECIMAL;
            case 'bigint':
            case 'int8':
                return static::PHINX_TYPE_BIG_INTEGER;
            case 'real':
            case 'float4':
                return static::PHINX_TYPE_FLOAT;
            case 'bytea':
                return static::PHINX_TYPE_BINARY;
            case 'interval':
                return static::PHINX_TYPE_INTERVAL;
            case 'time':
            case 'timetz':
            case 'time with time zone':
            case 'time without time zone':
                return static::PHINX_TYPE_TIME;
            case 'date':
                return static::PHINX_TYPE_DATE;
            case 'timestamp':
            case 'timestamptz':
            case 'timestamp with time zone':
            case 'timestamp without time zone':
                return static::PHINX_TYPE_DATETIME;
            case 'bool':
            case 'boolean':
                return static::PHINX_TYPE_BOOLEAN;
            case 'uuid':
                return static::PHINX_TYPE_UUID;
            case 'cidr':
                return static::PHINX_TYPE_CIDR;
            case 'inet':
                return static::PHINX_TYPE_INET;
            case 'macaddr':
                return static::PHINX_TYPE_MACADDR;
            default:
                throw new \RuntimeException('The PostgreSQL type: "' . $sqlType . '" is not supported');
        }
    }

    /**
     * {@inheritdoc}
     */
    public function createDatabase($name, $options = [])
    {
        $charset = isset($options['charset']) ? $options['charset'] : 'utf8';
        $this->execute(sprintf("CREATE DATABASE %s WITH ENCODING = '%s'", $name, $charset));
    }

    /**
     * {@inheritdoc}
     */
    public function hasDatabase($databaseName)
    {
        $sql = sprintf("SELECT count(*) FROM pg_database WHERE datname = '%s'", $databaseName);
        $result = $this->fetchRow($sql);

        return $result['count'] > 0;
    }

    /**
     * {@inheritdoc}
     */
    public function dropDatabase($name)
    {
        $this->disconnect();
        $this->execute(sprintf('DROP DATABASE IF EXISTS %s', $name));
        $this->connect();
    }

    /**
     * Get the defintion for a `DEFAULT` statement.
     *
     * @param  mixed $default
     * @return string
     */
    protected function getDefaultValueDefinition($default)
    {
        if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) {
            $default = $this->getConnection()->quote($default);
        } elseif (is_bool($default)) {
            $default = $this->castToBool($default);
        }

        return isset($default) ? 'DEFAULT ' . $default : '';
    }

    /**
     * Gets the PostgreSQL Column Definition for a Column object.
     *
     * @param \Phinx\Db\Table\Column $column Column
     * @return string
     */
    protected function getColumnSqlDefinition(Column $column)
    {
        $buffer = [];
        if ($column->isIdentity()) {
            $buffer[] = $column->getType() == 'biginteger' ? 'BIGSERIAL' : 'SERIAL';
        } else {
            $sqlType = $this->getSqlType($column->getType(), $column->getLimit());
            $buffer[] = strtoupper($sqlType['name']);
            // integers cant have limits in postgres
            if (static::PHINX_TYPE_DECIMAL === $sqlType['name'] && ($column->getPrecision() || $column->getScale())) {
                $buffer[] = sprintf(
                    '(%s, %s)',
                    $column->getPrecision() ?: $sqlType['precision'],
                    $column->getScale() ?: $sqlType['scale']
                );
            } elseif (in_array($sqlType['name'], ['geography'])) {
                // geography type must be written with geometry type and srid, like this: geography(POLYGON,4326)
                $buffer[] = sprintf(
                    '(%s,%s)',
                    strtoupper($sqlType['type']),
                    $sqlType['srid']
                );
            } elseif (!in_array($sqlType['name'], ['integer', 'smallint', 'bigint'])) {
                if ($column->getLimit() || isset($sqlType['limit'])) {
                    $buffer[] = sprintf('(%s)', $column->getLimit() ?: $sqlType['limit']);
                }
            }

            $timeTypes = [
                'time',
                'timestamp',
            ];
            if (in_array($sqlType['name'], $timeTypes) && $column->isTimezone()) {
                $buffer[] = strtoupper('with time zone');
            }
        }

        $buffer[] = $column->isNull() ? 'NULL' : 'NOT NULL';

        if (!is_null($column->getDefault())) {
            $buffer[] = $this->getDefaultValueDefinition($column->getDefault());
        }

        return implode(' ', $buffer);
    }

    /**
     * Gets the PostgreSQL Column Comment Defininition for a column object.
     *
     * @param \Phinx\Db\Table\Column $column Column
     * @param string $tableName Table name
     * @return string
     */
    protected function getColumnCommentSqlDefinition(Column $column, $tableName)
    {
        // passing 'null' is to remove column comment
        $comment = (strcasecmp($column->getComment(), 'NULL') !== 0)
                 ? $this->getConnection()->quote($column->getComment())
                 : 'NULL';

        return sprintf(
            'COMMENT ON COLUMN %s.%s IS %s;',
            $this->quoteSchemaName($tableName),
            $this->quoteColumnName($column->getName()),
            $comment
        );
    }

    /**
     * Gets the PostgreSQL Index Definition for an Index object.
     *
     * @param \Phinx\Db\Table\Index  $index Index
     * @param string $tableName Table name
     * @return string
     */
    protected function getIndexSqlDefinition(Index $index, $tableName)
    {
        if (is_string($index->getName())) {
            $indexName = $index->getName();
        } else {
            $columnNames = $index->getColumns();
            if (is_string($columnNames)) {
                $columnNames = [$columnNames];
            }
            $indexName = sprintf('%s_%s', $tableName, implode('_', $columnNames));
        }
        $def = sprintf(
            "CREATE %s INDEX %s ON %s (%s);",
            ($index->getType() === Index::UNIQUE ? 'UNIQUE' : ''),
            $indexName,
            $this->quoteTableName($tableName),
            implode(',', array_map([$this, 'quoteColumnName'], $index->getColumns()))
        );

        return $def;
    }

    /**
     * Gets the MySQL Foreign Key Definition for an ForeignKey object.
     *
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
     * @param string     $tableName  Table name
     * @return string
     */
    protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, $tableName)
    {
        $constraintName = $foreignKey->getConstraint() ?: $tableName . '_' . implode('_', $foreignKey->getColumns());

        $def = ' CONSTRAINT "' . $constraintName . '" FOREIGN KEY ("' . implode('", "', $foreignKey->getColumns()) . '")';
        $def .= " REFERENCES {$this->quoteTableName($foreignKey->getReferencedTable()->getName())} (\"" . implode('", "', $foreignKey->getReferencedColumns()) . '")';
        if ($foreignKey->getOnDelete()) {
            $def .= " ON DELETE {$foreignKey->getOnDelete()}";
        }
        if ($foreignKey->getOnUpdate()) {
            $def .= " ON UPDATE {$foreignKey->getOnUpdate()}";
        }

        return $def;
    }

    /**
     * {@inheritdoc}
     */
    public function createSchemaTable()
    {
        // Create the public/custom schema if it doesn't already exist
        if ($this->hasSchema($this->getSchemaName()) === false) {
            $this->createSchema($this->getSchemaName());
        }

        $this->fetchAll(sprintf('SET search_path TO %s', $this->getSchemaName()));

        parent::createSchemaTable();
    }

    /**
     * Creates the specified schema.
     *
     * @param  string $schemaName Schema Name
     * @return void
     */
    public function createSchema($schemaName = 'public')
    {
        $sql = sprintf('CREATE SCHEMA %s;', $this->quoteSchemaName($schemaName)); // from postgres 9.3 we can use "CREATE SCHEMA IF NOT EXISTS schema_name"
        $this->execute($sql);
    }

    /**
     * Checks to see if a schema exists.
     *
     * @param string $schemaName Schema Name
     * @return bool
     */
    public function hasSchema($schemaName)
    {
        $sql = sprintf(
            "SELECT count(*)
             FROM pg_namespace
             WHERE nspname = '%s'",
            $schemaName
        );
        $result = $this->fetchRow($sql);

        return $result['count'] > 0;
    }

    /**
     * Drops the specified schema table.
     *
     * @param string $schemaName Schema name
     * @return void
     */
    public function dropSchema($schemaName)
    {
        $sql = sprintf("DROP SCHEMA IF EXISTS %s CASCADE;", $this->quoteSchemaName($schemaName));
        $this->execute($sql);
    }

    /**
     * Drops all schemas.
     *
     * @return void
     */
    public function dropAllSchemas()
    {
        foreach ($this->getAllSchemas() as $schema) {
            $this->dropSchema($schema);
        }
    }

    /**
     * Returns schemas.
     *
     * @return array
     */
    public function getAllSchemas()
    {
        $sql = "SELECT schema_name
                FROM information_schema.schemata
                WHERE schema_name <> 'information_schema' AND schema_name !~ '^pg_'";
        $items = $this->fetchAll($sql);
        $schemaNames = [];
        foreach ($items as $item) {
            $schemaNames[] = $item['schema_name'];
        }

        return $schemaNames;
    }

    /**
     * {@inheritdoc}
     */
    public function getColumnTypes()
    {
        return array_merge(parent::getColumnTypes(), ['json', 'jsonb', 'cidr', 'inet', 'macaddr', 'interval']);
    }

    /**
     * {@inheritdoc}
     */
    public function isValidColumnType(Column $column)
    {
        // If not a standard column type, maybe it is array type?
        return (parent::isValidColumnType($column) || $this->isArrayType($column->getType()));
    }

    /**
     * Check if the given column is an array of a valid type.
     *
     * @param  string $columnType
     * @return bool
     */
    protected function isArrayType($columnType)
    {
        if (!preg_match('/^([a-z]+)(?:\[\]){1,}$/', $columnType, $matches)) {
            return false;
        }

        $baseType = $matches[1];

        return in_array($baseType, $this->getColumnTypes());
    }

    /**
     * Gets the schema name.
     *
     * @return string
     */
    private function getSchemaName()
    {
        $options = $this->getOptions();

        return empty($options['schema']) ? 'public' : $options['schema'];
    }

    /**
     * {@inheritdoc}
     */
    public function castToBool($value)
    {
        return (bool)$value ? 'TRUE' : 'FALSE';
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Db\Adapter
 */
namespace Phinx\Db\Adapter;

use Phinx\Db\Table;
use Phinx\Db\Table\Column;
use Phinx\Db\Table\ForeignKey;
use Phinx\Db\Table\Index;
use Phinx\Migration\MigrationInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Adapter Wrapper.
 *
 * Proxy commands through to another adapter, allowing modification of
 * parameters during calls.
 *
 * @author Woody Gilk <woody.gilk@gmail.com>
 */
abstract class AdapterWrapper implements AdapterInterface, WrapperInterface
{
    /**
     * @var \Phinx\Db\Adapter\AdapterInterface
     */
    protected $adapter;

    /**
     * {@inheritdoc}
     */
    public function __construct(AdapterInterface $adapter)
    {
        $this->setAdapter($adapter);
    }

    /**
     * {@inheritdoc}
     */
    public function setAdapter(AdapterInterface $adapter)
    {
        $this->adapter = $adapter;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getAdapter()
    {
        return $this->adapter;
    }

    /**
     * {@inheritdoc}
     */
    public function setOptions(array $options)
    {
        $this->adapter->setOptions($options);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getOptions()
    {
        return $this->adapter->getOptions();
    }

    /**
     * {@inheritdoc}
     */
    public function hasOption($name)
    {
        return $this->adapter->hasOption($name);
    }

    /**
     * {@inheritdoc}
     */
    public function getOption($name)
    {
        return $this->adapter->getOption($name);
    }

    /**
     * {@inheritdoc}
     */
    public function setInput(InputInterface $input)
    {
        $this->adapter->setInput($input);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getInput()
    {
        return $this->adapter->getInput();
    }

    /**
     * {@inheritdoc}
     */
    public function setOutput(OutputInterface $output)
    {
        $this->adapter->setOutput($output);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getOutput()
    {
        return $this->adapter->getOutput();
    }

    /**
     * {@inheritdoc}
     */
    public function connect()
    {
        $this->getAdapter()->connect();
    }

    /**
     * {@inheritdoc}
     */
    public function disconnect()
    {
        $this->getAdapter()->disconnect();
    }

    /**
     * {@inheritdoc}
     */
    public function execute($sql)
    {
        return $this->getAdapter()->execute($sql);
    }

    /**
     * {@inheritdoc}
     */
    public function query($sql)
    {
        return $this->getAdapter()->query($sql);
    }

    /**
     * {@inheritdoc}
     */
    public function insert(Table $table, $row)
    {
        $this->getAdapter()->insert($table, $row);
    }

    /**
     * {@inheritdoc}
     */
    public function bulkinsert(Table $table, $rows)
    {
        $this->getAdapter()->bulkinsert($table, $rows);
    }

    /**
     * {@inheritdoc}
     */
    public function fetchRow($sql)
    {
        return $this->getAdapter()->fetchRow($sql);
    }

    /**
     * {@inheritdoc}
     */
    public function fetchAll($sql)
    {
        return $this->getAdapter()->fetchAll($sql);
    }

    /**
     * {@inheritdoc}
     */
    public function getVersions()
    {
        return $this->getAdapter()->getVersions();
    }

    /**
     * {@inheritdoc}
     */
    public function getVersionLog()
    {
        return $this->getAdapter()->getVersionLog();
    }

    /**
     * {@inheritdoc}
     */
    public function migrated(MigrationInterface $migration, $direction, $startTime, $endTime)
    {
        $this->getAdapter()->migrated($migration, $direction, $startTime, $endTime);

        return $this;
    }

    /**
     * @inheritDoc
     */
    public function toggleBreakpoint(MigrationInterface $migration)
    {
        $this->getAdapter()->toggleBreakpoint($migration);

        return $this;
    }

    /**
     * @inheritDoc
     */
    public function resetAllBreakpoints()
    {
        return $this->getAdapter()->resetAllBreakpoints();
    }

    /**
     * {@inheritdoc}
     */
    public function hasSchemaTable()
    {
        return $this->getAdapter()->hasSchemaTable();
    }

    /**
     * {@inheritdoc}
     */
    public function createSchemaTable()
    {
        $this->getAdapter()->createSchemaTable();
    }

    /**
     * {@inheritdoc}
     */
    public function getColumnTypes()
    {
        return $this->getAdapter()->getColumnTypes();
    }

    /**
     * {@inheritdoc}
     */
    public function isValidColumnType(Column $column)
    {
        return $this->getAdapter()->isValidColumnType($column);
    }

    /**
     * {@inheritdoc}
     */
    public function hasTransactions()
    {
        return $this->getAdapter()->hasTransactions();
    }

    /**
     * {@inheritdoc}
     */
    public function beginTransaction()
    {
        $this->getAdapter()->beginTransaction();
    }

    /**
     * {@inheritdoc}
     */
    public function commitTransaction()
    {
        $this->getAdapter()->commitTransaction();
    }

    /**
     * {@inheritdoc}
     */
    public function rollbackTransaction()
    {
        $this->getAdapter()->rollbackTransaction();
    }

    /**
     * {@inheritdoc}
     */
    public function quoteTableName($tableName)
    {
        return $this->getAdapter()->quoteTableName($tableName);
    }

    /**
     * {@inheritdoc}
     */
    public function quoteColumnName($columnName)
    {
        return $this->getAdapter()->quoteColumnName($columnName);
    }

    /**
     * {@inheritdoc}
     */
    public function hasTable($tableName)
    {
        return $this->getAdapter()->hasTable($tableName);
    }

    /**
     * {@inheritdoc}
     */
    public function createTable(Table $table)
    {
        $this->getAdapter()->createTable($table);
    }

    /**
     * {@inheritdoc}
     */
    public function renameTable($tableName, $newTableName)
    {
        $this->getAdapter()->renameTable($tableName, $newTableName);
    }

    /**
     * {@inheritdoc}
     */
    public function dropTable($tableName)
    {
        $this->getAdapter()->dropTable($tableName);
    }

    /**
     * {@inheritdoc}
     */
    public function truncateTable($tableName)
    {
        $this->getAdapter()->truncateTable($tableName);
    }

    /**
     * {@inheritdoc}
     */
    public function getColumns($tableName)
    {
        return $this->getAdapter()->getColumns($tableName);
    }

    /**
     * {@inheritdoc}
     */
    public function hasColumn($tableName, $columnName)
    {
        return $this->getAdapter()->hasColumn($tableName, $columnName);
    }

    /**
     * {@inheritdoc}
     */
    public function addColumn(Table $table, Column $column)
    {
        $this->getAdapter()->addColumn($table, $column);
    }

    /**
     * {@inheritdoc}
     */
    public function renameColumn($tableName, $columnName, $newColumnName)
    {
        $this->getAdapter()->renameColumn($tableName, $columnName, $newColumnName);
    }

    /**
     * {@inheritdoc}
     */
    public function changeColumn($tableName, $columnName, Column $newColumn)
    {
        return $this->getAdapter()->changeColumn($tableName, $columnName, $newColumn);
    }

    /**
     * {@inheritdoc}
     */
    public function dropColumn($tableName, $columnName)
    {
        $this->getAdapter()->dropColumn($tableName, $columnName);
    }

    /**
     * {@inheritdoc}
     */
    public function hasIndex($tableName, $columns)
    {
        return $this->getAdapter()->hasIndex($tableName, $columns);
    }

    /**
     * {@inheritdoc}
     */
    public function hasIndexByName($tableName, $indexName)
    {
        return $this->getAdapter()->hasIndexByName($tableName, $indexName);
    }

    /**
     * {@inheritdoc}
     */
    public function addIndex(Table $table, Index $index)
    {
        $this->getAdapter()->addIndex($table, $index);
    }

    /**
     * {@inheritdoc}
     */
    public function dropIndex($tableName, $columns)
    {
        $this->getAdapter()->dropIndex($tableName, $columns);
    }

    /**
     * {@inheritdoc}
     */
    public function dropIndexByName($tableName, $indexName)
    {
        $this->getAdapter()->dropIndexByName($tableName, $indexName);
    }

    /**
     * {@inheritdoc}
     */
    public function hasForeignKey($tableName, $columns, $constraint = null)
    {
        return $this->getAdapter()->hasForeignKey($tableName, $columns, $constraint);
    }

    /**
     * {@inheritdoc}
     */
    public function addForeignKey(Table $table, ForeignKey $foreignKey)
    {
        $this->getAdapter()->addForeignKey($table, $foreignKey);
    }

    /**
     * {@inheritdoc}
     */
    public function dropForeignKey($tableName, $columns, $constraint = null)
    {
        $this->getAdapter()->dropForeignKey($tableName, $columns, $constraint);
    }

    /**
     * {@inheritdoc}
     */
    public function getSqlType($type, $limit = null)
    {
        return $this->getAdapter()->getSqlType($type, $limit);
    }

    /**
     * {@inheritdoc}
     */
    public function createDatabase($name, $options = [])
    {
        $this->getAdapter()->createDatabase($name, $options);
    }

    /**
     * {@inheritdoc}
     */
    public function hasDatabase($name)
    {
        $this->getAdapter()->hasDatabase($name);
    }

    /**
     * {@inheritdoc}
     */
    public function dropDatabase($name)
    {
        $this->getAdapter()->dropDatabase($name);
    }

    /**
     * {@inheritdoc}
     */
    public function createSchema($schemaName = 'public')
    {
        $this->getAdapter()->createSchema($schemaName);
    }

    /**
     * {@inheritdoc}
     */
    public function dropSchema($schemaName)
    {
        $this->getAdapter()->dropSchema($schemaName);
    }

    /**
     * {@inheritdoc}
     */
    public function castToBool($value)
    {
        return $this->getAdapter()->castToBool($value);
    }

    /**
     * {@inheritdoc}
     */
    public function getConnection()
    {
        return $this->getAdapter()->getConnection();
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Db\Adapter
 */
namespace Phinx\Db\Adapter;

use Phinx\Db\Table;
use Phinx\Db\Table\Column;
use Phinx\Db\Table\ForeignKey;
use Phinx\Db\Table\Index;

/**
 * Phinx SqlServer Adapter.
 *
 * @author Rob Morgan <robbym@gmail.com>
 */
class SqlServerAdapter extends PdoAdapter implements AdapterInterface
{
    protected $schema = 'dbo';

    protected $signedColumnTypes = ['integer' => true, 'biginteger' => true, 'float' => true, 'decimal' => true];

    /**
     * {@inheritdoc}
     */
    public function connect()
    {
        if ($this->connection === null) {
            if (!class_exists('PDO') || !in_array('sqlsrv', \PDO::getAvailableDrivers(), true)) {
                // try our connection via freetds (Mac/Linux)
                $this->connectDblib();

                return;
            }

            $db = null;
            $options = $this->getOptions();

            // if port is specified use it, otherwise use the SqlServer default
            if (empty($options['port'])) {
                $dsn = 'sqlsrv:server=' . $options['host'] . ';database=' . $options['name'];
            } else {
                $dsn = 'sqlsrv:server=' . $options['host'] . ',' . $options['port'] . ';database=' . $options['name'];
            }
            $dsn .= ';MultipleActiveResultSets=false';

            $driverOptions = [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION];

            // charset support
            if (isset($options['charset'])) {
                $driverOptions[\PDO::SQLSRV_ATTR_ENCODING] = $options['charset'];
            }

            // support arbitrary \PDO::SQLSRV_ATTR_* driver options and pass them to PDO
            // http://php.net/manual/en/ref.pdo-sqlsrv.php#pdo-sqlsrv.constants
            foreach ($options as $key => $option) {
                if (strpos($key, 'sqlsrv_attr_') === 0) {
                    $driverOptions[constant('\PDO::' . strtoupper($key))] = $option;
                }
            }

            try {
                $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions);
            } catch (\PDOException $exception) {
                throw new \InvalidArgumentException(sprintf(
                    'There was a problem connecting to the database: %s',
                    $exception->getMessage()
                ));
            }

            $this->setConnection($db);
        }
    }

    /**
     * Connect to MSSQL using dblib/freetds.
     *
     * The "sqlsrv" driver is not available on Unix machines.
     *
     * @throws \InvalidArgumentException
     */
    protected function connectDblib()
    {
        if (!class_exists('PDO') || !in_array('dblib', \PDO::getAvailableDrivers(), true)) {
            // @codeCoverageIgnoreStart
            throw new \RuntimeException('You need to enable the PDO_Dblib extension for Phinx to run properly.');
            // @codeCoverageIgnoreEnd
        }

        $options = $this->getOptions();

        // if port is specified use it, otherwise use the SqlServer default
        if (empty($options['port'])) {
            $dsn = 'dblib:host=' . $options['host'] . ';dbname=' . $options['name'];
        } else {
            $dsn = 'dblib:host=' . $options['host'] . ':' . $options['port'] . ';dbname=' . $options['name'];
        }

        $driverOptions = [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION];

        try {
            $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions);
        } catch (\PDOException $exception) {
            throw new \InvalidArgumentException(sprintf(
                'There was a problem connecting to the database: %s',
                $exception->getMessage()
            ));
        }

        $this->setConnection($db);
    }

    /**
     * {@inheritdoc}
     */
    public function disconnect()
    {
        $this->connection = null;
    }

    /**
     * {@inheritdoc}
     */
    public function hasTransactions()
    {
        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function beginTransaction()
    {
        $this->execute('BEGIN TRANSACTION');
    }

    /**
     * {@inheritdoc}
     */
    public function commitTransaction()
    {
        $this->execute('COMMIT TRANSACTION');
    }

    /**
     * {@inheritdoc}
     */
    public function rollbackTransaction()
    {
        $this->execute('ROLLBACK TRANSACTION');
    }

    /**
     * {@inheritdoc}
     */
    public function quoteTableName($tableName)
    {
        return str_replace('.', '].[', $this->quoteColumnName($tableName));
    }

    /**
     * {@inheritdoc}
     */
    public function quoteColumnName($columnName)
    {
        return '[' . str_replace(']', '\]', $columnName) . ']';
    }

    /**
     * {@inheritdoc}
     */
    public function hasTable($tableName)
    {
        $result = $this->fetchRow(sprintf('SELECT count(*) as [count] FROM information_schema.tables WHERE table_name = \'%s\';', $tableName));

        return $result['count'] > 0;
    }

    /**
     * {@inheritdoc}
     */
    public function createTable(Table $table)
    {
        $options = $table->getOptions();

        // Add the default primary key
        $columns = $table->getPendingColumns();
        if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
            $column = new Column();
            $column->setName('id')
                   ->setType('integer')
                   ->setIdentity(true);

            array_unshift($columns, $column);
            $options['primary_key'] = 'id';
        } elseif (isset($options['id']) && is_string($options['id'])) {
            // Handle id => "field_name" to support AUTO_INCREMENT
            $column = new Column();
            $column->setName($options['id'])
                   ->setType('integer')
                   ->setIdentity(true);

            array_unshift($columns, $column);
            $options['primary_key'] = $options['id'];
        }

        $sql = 'CREATE TABLE ';
        $sql .= $this->quoteTableName($table->getName()) . ' (';
        $sqlBuffer = [];
        $columnsWithComments = [];
        foreach ($columns as $column) {
            $sqlBuffer[] = $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column);

            // set column comments, if needed
            if ($column->getComment()) {
                $columnsWithComments[] = $column;
            }
        }

        // set the primary key(s)
        if (isset($options['primary_key'])) {
            $pkSql = sprintf('CONSTRAINT PK_%s PRIMARY KEY (', $table->getName());
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
                $pkSql .= $this->quoteColumnName($options['primary_key']);
            } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
                $pkSql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
            }
            $pkSql .= ')';
            $sqlBuffer[] = $pkSql;
        }

        // set the foreign keys
        $foreignKeys = $table->getForeignKeys();
        foreach ($foreignKeys as $foreignKey) {
            $sqlBuffer[] = $this->getForeignKeySqlDefinition($foreignKey, $table->getName());
        }

        $sql .= implode(', ', $sqlBuffer);
        $sql .= ');';

        // process column comments
        foreach ($columnsWithComments as $column) {
            $sql .= $this->getColumnCommentSqlDefinition($column, $table->getName());
        }

        // set the indexes
        $indexes = $table->getIndexes();
        foreach ($indexes as $index) {
            $sql .= $this->getIndexSqlDefinition($index, $table->getName());
        }

        // execute the sql
        $this->execute($sql);
    }

    /**
     * Gets the SqlServer Column Comment Defininition for a column object.
     *
     * @param \Phinx\Db\Table\Column $column    Column
     * @param string $tableName Table name
     *
     * @return string
     */
    protected function getColumnCommentSqlDefinition(Column $column, $tableName)
    {
        // passing 'null' is to remove column comment
        $currentComment = $this->getColumnComment($tableName, $column->getName());

        $comment = (strcasecmp($column->getComment(), 'NULL') !== 0) ? $this->getConnection()->quote($column->getComment()) : '\'\'';
        $command = $currentComment === false ? 'sp_addextendedproperty' : 'sp_updateextendedproperty';

        return sprintf(
            "EXECUTE %s N'MS_Description', N%s, N'SCHEMA', N'%s', N'TABLE', N'%s', N'COLUMN', N'%s';",
            $command,
            $comment,
            $this->schema,
            $tableName,
            $column->getName()
        );
    }

    /**
     * {@inheritdoc}
     */
    public function renameTable($tableName, $newTableName)
    {
        $this->execute(sprintf('EXEC sp_rename \'%s\', \'%s\'', $tableName, $newTableName));
    }

    /**
     * {@inheritdoc}
     */
    public function dropTable($tableName)
    {
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName)));
    }

    /**
     * {@inheritdoc}
     */
    public function truncateTable($tableName)
    {
        $sql = sprintf(
            'TRUNCATE TABLE %s',
            $this->quoteTableName($tableName)
        );

        $this->execute($sql);
    }

    public function getColumnComment($tableName, $columnName)
    {
        $sql = sprintf("SELECT cast(extended_properties.[value] as nvarchar(4000)) comment
  FROM sys.schemas
 INNER JOIN sys.tables
    ON schemas.schema_id = tables.schema_id
 INNER JOIN sys.columns
    ON tables.object_id = columns.object_id
 INNER JOIN sys.extended_properties
    ON tables.object_id = extended_properties.major_id
   AND columns.column_id = extended_properties.minor_id
   AND extended_properties.name = 'MS_Description'
   WHERE schemas.[name] = '%s' AND tables.[name] = '%s' AND columns.[name] = '%s'", $this->schema, $tableName, $columnName);
        $row = $this->fetchRow($sql);

        if ($row) {
            return $row['comment'];
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function getColumns($tableName)
    {
        $columns = [];
        $sql = sprintf(
            "SELECT DISTINCT TABLE_SCHEMA AS [schema], TABLE_NAME as [table_name], COLUMN_NAME AS [name], DATA_TYPE AS [type],
            IS_NULLABLE AS [null], COLUMN_DEFAULT AS [default],
            CHARACTER_MAXIMUM_LENGTH AS [char_length],
            NUMERIC_PRECISION AS [precision],
            NUMERIC_SCALE AS [scale], ORDINAL_POSITION AS [ordinal_position],
            COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsIdentity') as [identity]
        FROM INFORMATION_SCHEMA.COLUMNS
        WHERE TABLE_NAME = '%s'
        ORDER BY ordinal_position",
            $tableName
        );
        $rows = $this->fetchAll($sql);
        foreach ($rows as $columnInfo) {
            $column = new Column();
            $column->setName($columnInfo['name'])
                   ->setType($this->getPhinxType($columnInfo['type']))
                   ->setNull($columnInfo['null'] !== 'NO')
                   ->setDefault($this->parseDefault($columnInfo['default']))
                   ->setIdentity($columnInfo['identity'] === '1')
                   ->setComment($this->getColumnComment($columnInfo['table_name'], $columnInfo['name']));

            if (!empty($columnInfo['char_length'])) {
                $column->setLimit($columnInfo['char_length']);
            }

            $columns[$columnInfo['name']] = $column;
        }

        return $columns;
    }

    protected function parseDefault($default)
    {
        $default = preg_replace(["/\('(.*)'\)/", "/\(\((.*)\)\)/", "/\((.*)\)/"], '$1', $default);

        if (strtoupper($default) === 'NULL') {
            $default = null;
        } elseif (is_numeric($default)) {
            $default = (int)$default;
        }

        return $default;
    }

    /**
     * {@inheritdoc}
     */
    public function hasColumn($tableName, $columnName)
    {
        $sql = sprintf(
            "SELECT count(*) as [count]
             FROM information_schema.columns
             WHERE table_name = '%s' AND column_name = '%s'",
            $tableName,
            $columnName
        );
        $result = $this->fetchRow($sql);

        return $result['count'] > 0;
    }

    /**
     * {@inheritdoc}
     */
    public function addColumn(Table $table, Column $column)
    {
        $sql = sprintf(
            'ALTER TABLE %s ADD %s %s',
            $this->quoteTableName($table->getName()),
            $this->quoteColumnName($column->getName()),
            $this->getColumnSqlDefinition($column)
        );

        $this->execute($sql);
    }

    /**
     * {@inheritdoc}
     */
    public function renameColumn($tableName, $columnName, $newColumnName)
    {
        if (!$this->hasColumn($tableName, $columnName)) {
            throw new \InvalidArgumentException("The specified column does not exist: $columnName");
        }
        $this->renameDefault($tableName, $columnName, $newColumnName);
        $this->execute(
            sprintf(
                "EXECUTE sp_rename N'%s.%s', N'%s', 'COLUMN' ",
                $tableName,
                $columnName,
                $newColumnName
            )
        );
    }

    protected function renameDefault($tableName, $columnName, $newColumnName)
    {
        $oldConstraintName = "DF_{$tableName}_{$columnName}";
        $newConstraintName = "DF_{$tableName}_{$newColumnName}";
        $sql = <<<SQL
IF (OBJECT_ID('$oldConstraintName', 'D') IS NOT NULL)
BEGIN
     EXECUTE sp_rename N'%s', N'%s', N'OBJECT'
END
SQL;
        $this->execute(sprintf(
            $sql,
            $oldConstraintName,
            $newConstraintName
        ));
    }

    public function changeDefault($tableName, Column $newColumn)
    {
        $constraintName = "DF_{$tableName}_{$newColumn->getName()}";
        $default = $newColumn->getDefault();

        if ($default === null) {
            $default = 'DEFAULT NULL';
        } else {
            $default = $this->getDefaultValueDefinition($default);
        }

        if (empty($default)) {
            return;
        }

        $this->execute(sprintf(
            'ALTER TABLE %s ADD CONSTRAINT %s %s FOR %s',
            $this->quoteTableName($tableName),
            $constraintName,
            $default,
            $this->quoteColumnName($newColumn->getName())
        ));
    }

    /**
     * {@inheritdoc}
     */
    public function changeColumn($tableName, $columnName, Column $newColumn)
    {
        $columns = $this->getColumns($tableName);
        $changeDefault = $newColumn->getDefault() !== $columns[$columnName]->getDefault() || $newColumn->getType() !== $columns[$columnName]->getType();
        if ($columnName !== $newColumn->getName()) {
            $this->renameColumn($tableName, $columnName, $newColumn->getName());
        }

        if ($changeDefault) {
            $this->dropDefaultConstraint($tableName, $newColumn->getName());
        }

        $this->execute(
            sprintf(
                'ALTER TABLE %s ALTER COLUMN %s %s',
                $this->quoteTableName($tableName),
                $this->quoteColumnName($newColumn->getName()),
                $this->getColumnSqlDefinition($newColumn, false)
            )
        );
        // change column comment if needed
        if ($newColumn->getComment()) {
            $sql = $this->getColumnCommentSqlDefinition($newColumn, $tableName);
            $this->execute($sql);
        }

        if ($changeDefault) {
            $this->changeDefault($tableName, $newColumn);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function dropColumn($tableName, $columnName)
    {
        $this->dropDefaultConstraint($tableName, $columnName);

        $this->execute(
            sprintf(
                'ALTER TABLE %s DROP COLUMN %s',
                $this->quoteTableName($tableName),
                $this->quoteColumnName($columnName)
            )
        );
    }

    protected function dropDefaultConstraint($tableName, $columnName)
    {
        $defaultConstraint = $this->getDefaultConstraint($tableName, $columnName);

        if (!$defaultConstraint) {
            return;
        }

        $this->dropForeignKey($tableName, $columnName, $defaultConstraint);
    }

    protected function getDefaultConstraint($tableName, $columnName)
    {
        $sql = "SELECT
    default_constraints.name
FROM
    sys.all_columns

        INNER JOIN
    sys.tables
        ON all_columns.object_id = tables.object_id

        INNER JOIN
    sys.schemas
        ON tables.schema_id = schemas.schema_id

        INNER JOIN
    sys.default_constraints
        ON all_columns.default_object_id = default_constraints.object_id

WHERE
        schemas.name = 'dbo'
    AND tables.name = '{$tableName}'
    AND all_columns.name = '{$columnName}'";

        $rows = $this->fetchAll($sql);

        return empty($rows) ? false : $rows[0]['name'];
    }

    protected function getIndexColums($tableId, $indexId)
    {
        $sql = "SELECT AC.[name] AS [column_name]
FROM sys.[index_columns] IC
  INNER JOIN sys.[all_columns] AC ON IC.[column_id] = AC.[column_id]
WHERE AC.[object_id] = {$tableId} AND IC.[index_id] = {$indexId}  AND IC.[object_id] = {$tableId}
ORDER BY IC.[key_ordinal];";

        $rows = $this->fetchAll($sql);
        $columns = [];
        foreach ($rows as $row) {
            $columns[] = strtolower($row['column_name']);
        }

        return $columns;
    }

    /**
     * Get an array of indexes from a particular table.
     *
     * @param string $tableName Table Name
     * @return array
     */
    public function getIndexes($tableName)
    {
        $indexes = [];
        $sql = "SELECT I.[name] AS [index_name], I.[index_id] as [index_id], T.[object_id] as [table_id]
FROM sys.[tables] AS T
  INNER JOIN sys.[indexes] I ON T.[object_id] = I.[object_id]
WHERE T.[is_ms_shipped] = 0 AND I.[type_desc] <> 'HEAP'  AND T.[name] = '{$tableName}'
ORDER BY T.[name], I.[index_id];";

        $rows = $this->fetchAll($sql);
        foreach ($rows as $row) {
            $columns = $this->getIndexColums($row['table_id'], $row['index_id']);
            $indexes[$row['index_name']] = ['columns' => $columns];
        }

        return $indexes;
    }

    /**
     * {@inheritdoc}
     */
    public function hasIndex($tableName, $columns)
    {
        if (is_string($columns)) {
            $columns = [$columns]; // str to array
        }

        $columns = array_map('strtolower', $columns);
        $indexes = $this->getIndexes($tableName);

        foreach ($indexes as $index) {
            $a = array_diff($columns, $index['columns']);

            if (empty($a)) {
                return true;
            }
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function hasIndexByName($tableName, $indexName)
    {
        $indexes = $this->getIndexes($tableName);

        foreach ($indexes as $name => $index) {
            if ($name === $indexName) {
                 return true;
            }
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function addIndex(Table $table, Index $index)
    {
        $sql = $this->getIndexSqlDefinition($index, $table->getName());
        $this->execute($sql);
    }

    /**
     * {@inheritdoc}
     */
    public function dropIndex($tableName, $columns)
    {
        if (is_string($columns)) {
            $columns = [$columns]; // str to array
        }

        $indexes = $this->getIndexes($tableName);
        $columns = array_map('strtolower', $columns);

        foreach ($indexes as $indexName => $index) {
            $a = array_diff($columns, $index['columns']);
            if (empty($a)) {
                $this->execute(
                    sprintf(
                        'DROP INDEX %s ON %s',
                        $this->quoteColumnName($indexName),
                        $this->quoteTableName($tableName)
                    )
                );

                return;
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function dropIndexByName($tableName, $indexName)
    {
        $indexes = $this->getIndexes($tableName);

        foreach ($indexes as $name => $index) {
            if ($name === $indexName) {
                $this->execute(
                    sprintf(
                        'DROP INDEX %s ON %s',
                        $this->quoteColumnName($indexName),
                        $this->quoteTableName($tableName)
                    )
                );

                return;
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function hasForeignKey($tableName, $columns, $constraint = null)
    {
        if (is_string($columns)) {
            $columns = [$columns]; // str to array
        }
        $foreignKeys = $this->getForeignKeys($tableName);
        if ($constraint) {
            if (isset($foreignKeys[$constraint])) {
                return !empty($foreignKeys[$constraint]);
            }

            return false;
        } else {
            foreach ($foreignKeys as $key) {
                $a = array_diff($columns, $key['columns']);
                if (empty($a)) {
                    return true;
                }
            }

            return false;
        }
    }

    /**
     * Get an array of foreign keys from a particular table.
     *
     * @param string $tableName Table Name
     * @return array
     */
    protected function getForeignKeys($tableName)
    {
        $foreignKeys = [];
        $rows = $this->fetchAll(sprintf(
            "SELECT
                    tc.constraint_name,
                    tc.table_name, kcu.column_name,
                    ccu.table_name AS referenced_table_name,
                    ccu.column_name AS referenced_column_name
                FROM
                    information_schema.table_constraints AS tc
                    JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
                    JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
                WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s'
                ORDER BY kcu.ordinal_position",
            $tableName
        ));
        foreach ($rows as $row) {
            $foreignKeys[$row['constraint_name']]['table'] = $row['table_name'];
            $foreignKeys[$row['constraint_name']]['columns'][] = $row['column_name'];
            $foreignKeys[$row['constraint_name']]['referenced_table'] = $row['referenced_table_name'];
            $foreignKeys[$row['constraint_name']]['referenced_columns'][] = $row['referenced_column_name'];
        }

        return $foreignKeys;
    }

    /**
     * {@inheritdoc}
     */
    public function addForeignKey(Table $table, ForeignKey $foreignKey)
    {
        $this->execute(
            sprintf(
                'ALTER TABLE %s ADD %s',
                $this->quoteTableName($table->getName()),
                $this->getForeignKeySqlDefinition($foreignKey, $table->getName())
            )
        );
    }

    /**
     * {@inheritdoc}
     */
    public function dropForeignKey($tableName, $columns, $constraint = null)
    {
        if (is_string($columns)) {
            $columns = [$columns]; // str to array
        }

        if ($constraint) {
            $this->execute(
                sprintf(
                    'ALTER TABLE %s DROP CONSTRAINT %s',
                    $this->quoteTableName($tableName),
                    $constraint
                )
            );

            return;
        } else {
            foreach ($columns as $column) {
                $rows = $this->fetchAll(sprintf(
                    "SELECT
                    tc.constraint_name,
                    tc.table_name, kcu.column_name,
                    ccu.table_name AS referenced_table_name,
                    ccu.column_name AS referenced_column_name
                FROM
                    information_schema.table_constraints AS tc
                    JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
                    JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
                WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s' and ccu.column_name='%s'
                ORDER BY kcu.ordinal_position",
                    $tableName,
                    $column
                ));
                foreach ($rows as $row) {
                    $this->dropForeignKey($tableName, $columns, $row['constraint_name']);
                }
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getSqlType($type, $limit = null)
    {
        switch ($type) {
            case static::PHINX_TYPE_STRING:
                return ['name' => 'nvarchar', 'limit' => 255];
            case static::PHINX_TYPE_CHAR:
                return ['name' => 'nchar', 'limit' => 255];
            case static::PHINX_TYPE_TEXT:
                return ['name' => 'ntext'];
            case static::PHINX_TYPE_INTEGER:
                return ['name' => 'int'];
            case static::PHINX_TYPE_BIG_INTEGER:
                return ['name' => 'bigint'];
            case static::PHINX_TYPE_FLOAT:
                return ['name' => 'float'];
            case static::PHINX_TYPE_DECIMAL:
                return ['name' => 'decimal'];
            case static::PHINX_TYPE_DATETIME:
            case static::PHINX_TYPE_TIMESTAMP:
                return ['name' => 'datetime'];
            case static::PHINX_TYPE_TIME:
                return ['name' => 'time'];
            case static::PHINX_TYPE_DATE:
                return ['name' => 'date'];
            case static::PHINX_TYPE_BLOB:
            case static::PHINX_TYPE_BINARY:
                return ['name' => 'varbinary'];
            case static::PHINX_TYPE_BOOLEAN:
                return ['name' => 'bit'];
            case static::PHINX_TYPE_UUID:
                return ['name' => 'uniqueidentifier'];
            case static::PHINX_TYPE_FILESTREAM:
                return ['name' => 'varbinary', 'limit' => 'max'];
            // Geospatial database types
            case static::PHINX_TYPE_GEOMETRY:
            case static::PHINX_TYPE_POINT:
            case static::PHINX_TYPE_LINESTRING:
            case static::PHINX_TYPE_POLYGON:
                // SQL Server stores all spatial data using a single data type.
                // Specific types (point, polygon, etc) are set at insert time.
                return ['name' => 'geography'];
            default:
                throw new \RuntimeException('The type: "' . $type . '" is not supported.');
        }
    }

    /**
     * Returns Phinx type by SQL type
     *
     * @param string $sqlType SQL Type definition
     * @throws \RuntimeException
     * @internal param string $sqlType SQL type
     * @returns string Phinx type
     */
    public function getPhinxType($sqlType)
    {
        switch ($sqlType) {
            case 'nvarchar':
            case 'varchar':
                return static::PHINX_TYPE_STRING;
            case 'char':
            case 'nchar':
                return static::PHINX_TYPE_CHAR;
            case 'text':
            case 'ntext':
                return static::PHINX_TYPE_TEXT;
            case 'int':
            case 'integer':
                return static::PHINX_TYPE_INTEGER;
            case 'decimal':
            case 'numeric':
            case 'money':
                return static::PHINX_TYPE_DECIMAL;
            case 'bigint':
                return static::PHINX_TYPE_BIG_INTEGER;
            case 'real':
            case 'float':
                return static::PHINX_TYPE_FLOAT;
            case 'binary':
            case 'image':
            case 'varbinary':
                return static::PHINX_TYPE_BINARY;
            case 'time':
                return static::PHINX_TYPE_TIME;
            case 'date':
                return static::PHINX_TYPE_DATE;
            case 'datetime':
            case 'timestamp':
                return static::PHINX_TYPE_DATETIME;
            case 'bit':
                return static::PHINX_TYPE_BOOLEAN;
            case 'uniqueidentifier':
                return static::PHINX_TYPE_UUID;
            case 'filestream':
                return static::PHINX_TYPE_FILESTREAM;
            default:
                throw new \RuntimeException('The SqlServer type: "' . $sqlType . '" is not supported');
        }
    }

    /**
     * {@inheritdoc}
     */
    public function createDatabase($name, $options = [])
    {
        if (isset($options['collation'])) {
            $this->execute(sprintf('CREATE DATABASE [%s] COLLATE [%s]', $name, $options['collation']));
        } else {
            $this->execute(sprintf('CREATE DATABASE [%s]', $name));
        }
        $this->execute(sprintf('USE [%s]', $name));
    }

    /**
     * {@inheritdoc}
     */
    public function hasDatabase($name)
    {
        $result = $this->fetchRow(
            sprintf(
                'SELECT count(*) as [count] FROM master.dbo.sysdatabases WHERE [name] = \'%s\'',
                $name
            )
        );

        return $result['count'] > 0;
    }

    /**
     * {@inheritdoc}
     */
    public function dropDatabase($name)
    {
        $sql = <<<SQL
USE master;
IF EXISTS(select * from sys.databases where name=N'$name')
ALTER DATABASE [$name] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE [$name];
SQL;
        $this->execute($sql);
    }

    /**
     * Get the defintion for a `DEFAULT` statement.
     *
     * @param  mixed $default
     * @return string
     */
    protected function getDefaultValueDefinition($default)
    {
        if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) {
            $default = $this->getConnection()->quote($default);
        } elseif (is_bool($default)) {
            $default = $this->castToBool($default);
        }

        return isset($default) ? ' DEFAULT ' . $default : '';
    }

    /**
     * Gets the SqlServer Column Definition for a Column object.
     *
     * @param \Phinx\Db\Table\Column $column Column
     * @return string
     */
    protected function getColumnSqlDefinition(Column $column, $create = true)
    {
        $buffer = [];

        $sqlType = $this->getSqlType($column->getType());
        $buffer[] = strtoupper($sqlType['name']);
        // integers cant have limits in SQlServer
        $noLimits = [
            'bigint',
            'int',
            'tinyint'
        ];
        if (!in_array($sqlType['name'], $noLimits) && ($column->getLimit() || isset($sqlType['limit']))) {
            $buffer[] = sprintf('(%s)', $column->getLimit() ?: $sqlType['limit']);
        }
        if ($column->getPrecision() && $column->getScale()) {
            $buffer[] = '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
        }

        $properties = $column->getProperties();
        $buffer[] = $column->getType() === 'filestream' ? 'FILESTREAM' : '';
        $buffer[] = isset($properties['rowguidcol']) ? 'ROWGUIDCOL' : '';

        $buffer[] = $column->isNull() ? 'NULL' : 'NOT NULL';

        if ($create === true) {
            if ($column->getDefault() === null && $column->isNull()) {
                $buffer[] = ' DEFAULT NULL';
            } else {
                $buffer[] = $this->getDefaultValueDefinition($column->getDefault());
            }
        }

        if ($column->isIdentity()) {
            $buffer[] = 'IDENTITY(1, 1)';
        }

        return implode(' ', $buffer);
    }

    /**
     * Gets the SqlServer Index Definition for an Index object.
     *
     * @param \Phinx\Db\Table\Index $index Index
     * @return string
     */
    protected function getIndexSqlDefinition(Index $index, $tableName)
    {
        if (is_string($index->getName())) {
            $indexName = $index->getName();
        } else {
            $columnNames = $index->getColumns();
            if (is_string($columnNames)) {
                $columnNames = [$columnNames];
            }
            $indexName = sprintf('%s_%s', $tableName, implode('_', $columnNames));
        }
        $def = sprintf(
            "CREATE %s INDEX %s ON %s (%s);",
            ($index->getType() === Index::UNIQUE ? 'UNIQUE' : ''),
            $indexName,
            $this->quoteTableName($tableName),
            '[' . implode('],[', $index->getColumns()) . ']'
        );

        return $def;
    }

    /**
     * Gets the SqlServer Foreign Key Definition for an ForeignKey object.
     *
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
     * @return string
     */
    protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, $tableName)
    {
        $constraintName = $foreignKey->getConstraint() ?: $tableName . '_' . implode('_', $foreignKey->getColumns());
        $def = ' CONSTRAINT ' . $this->quoteColumnName($constraintName);
        $def .= ' FOREIGN KEY ("' . implode('", "', $foreignKey->getColumns()) . '")';
        $def .= " REFERENCES {$this->quoteTableName($foreignKey->getReferencedTable()->getName())} (\"" . implode('", "', $foreignKey->getReferencedColumns()) . '")';
        if ($foreignKey->getOnDelete()) {
            $def .= " ON DELETE {$foreignKey->getOnDelete()}";
        }
        if ($foreignKey->getOnUpdate()) {
            $def .= " ON UPDATE {$foreignKey->getOnUpdate()}";
        }

        return $def;
    }

    /**
     * {@inheritdoc}
     */
    public function getColumnTypes()
    {
        return array_merge(parent::getColumnTypes(), ['filestream']);
    }

    /**
     * Records a migration being run.
     *
     * @param \Phinx\Migration\MigrationInterface $migration Migration
     * @param string $direction Direction
     * @param int $startTime Start Time
     * @param int $endTime End Time
     * @return \Phinx\Db\Adapter\AdapterInterface
     */
    public function migrated(\Phinx\Migration\MigrationInterface $migration, $direction, $startTime, $endTime)
    {
        $startTime = str_replace(' ', 'T', $startTime);
        $endTime = str_replace(' ', 'T', $endTime);

        return parent::migrated($migration, $direction, $startTime, $endTime);
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Db\Adapter
 */
namespace Phinx\Db\Adapter;

use Phinx\Db\Table;
use Phinx\Db\Table\Column;
use Phinx\Db\Table\ForeignKey;
use Phinx\Db\Table\Index;

/**
 * Phinx SQLite Adapter.
 *
 * @author Rob Morgan <robbym@gmail.com>
 * @author Richard McIntyre <richard.mackstars@gmail.com>
 */
class SQLiteAdapter extends PdoAdapter implements AdapterInterface
{
    protected $definitionsWithLimits = [
        'CHARACTER',
        'VARCHAR',
        'VARYING CHARACTER',
        'NCHAR',
        'NATIVE CHARACTER',
        'NVARCHAR'
    ];

    /**
     * {@inheritdoc}
     */
    public function connect()
    {
        if ($this->connection === null) {
            if (!class_exists('PDO') || !in_array('sqlite', \PDO::getAvailableDrivers(), true)) {
                // @codeCoverageIgnoreStart
                throw new \RuntimeException('You need to enable the PDO_SQLITE extension for Phinx to run properly.');
                // @codeCoverageIgnoreEnd
            }

            $db = null;
            $options = $this->getOptions();

            // if port is specified use it, otherwise use the MySQL default
            if (isset($options['memory'])) {
                $dsn = 'sqlite::memory:';
            } else {
                $dsn = 'sqlite:' . $options['name'];
                if (file_exists($options['name'] . '.sqlite3')) {
                    $dsn = 'sqlite:' . $options['name'] . '.sqlite3';
                }
            }

            try {
                $db = new \PDO($dsn);
            } catch (\PDOException $exception) {
                throw new \InvalidArgumentException(sprintf(
                    'There was a problem connecting to the database: %s',
                    $exception->getMessage()
                ));
            }

            $this->setConnection($db);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function disconnect()
    {
        $this->connection = null;
    }

    /**
     * {@inheritdoc}
     */
    public function hasTransactions()
    {
        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function beginTransaction()
    {
        $this->execute('BEGIN TRANSACTION');
    }

    /**
     * {@inheritdoc}
     */
    public function commitTransaction()
    {
        $this->execute('COMMIT');
    }

    /**
     * {@inheritdoc}
     */
    public function rollbackTransaction()
    {
        $this->execute('ROLLBACK');
    }

    /**
     * {@inheritdoc}
     */
    public function quoteTableName($tableName)
    {
        return str_replace('.', '`.`', $this->quoteColumnName($tableName));
    }

    /**
     * {@inheritdoc}
     */
    public function quoteColumnName($columnName)
    {
        return '`' . str_replace('`', '``', $columnName) . '`';
    }

    /**
     * {@inheritdoc}
     */
    public function hasTable($tableName)
    {
        $tables = [];
        $rows = $this->fetchAll(sprintf('SELECT name FROM sqlite_master WHERE type=\'table\' AND name=\'%s\'', $tableName));
        foreach ($rows as $row) {
            $tables[] = strtolower($row[0]);
        }

        return in_array(strtolower($tableName), $tables);
    }

    /**
     * {@inheritdoc}
     */
    public function createTable(Table $table)
    {
        // Add the default primary key
        $columns = $table->getPendingColumns();
        $options = $table->getOptions();
        if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
            $column = new Column();
            $column->setName('id')
                   ->setType('integer')
                   ->setIdentity(true);

            array_unshift($columns, $column);
        } elseif (isset($options['id']) && is_string($options['id'])) {
            // Handle id => "field_name" to support AUTO_INCREMENT
            $column = new Column();
            $column->setName($options['id'])
                   ->setType('integer')
                   ->setIdentity(true);

            array_unshift($columns, $column);
        }

        $sql = 'CREATE TABLE ';
        $sql .= $this->quoteTableName($table->getName()) . ' (';
        foreach ($columns as $column) {
            $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', ';
        }

        // set the primary key(s)
        if (isset($options['primary_key'])) {
            $sql = rtrim($sql);
            $sql .= ' PRIMARY KEY (';
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
                $sql .= $this->quoteColumnName($options['primary_key']);
            } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
            }
            $sql .= ')';
        } else {
            $sql = substr(rtrim($sql), 0, -1); // no primary keys
        }

        // set the foreign keys
        $foreignKeys = $table->getForeignKeys();
        if (!empty($foreignKeys)) {
            foreach ($foreignKeys as $foreignKey) {
                $sql .= ', ' . $this->getForeignKeySqlDefinition($foreignKey);
            }
        }

        $sql = rtrim($sql) . ');';
        // execute the sql
        $this->execute($sql);

        foreach ($table->getIndexes() as $index) {
            $this->addIndex($table, $index);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function renameTable($tableName, $newTableName)
    {
        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($tableName), $this->quoteTableName($newTableName)));
    }

    /**
     * {@inheritdoc}
     */
    public function dropTable($tableName)
    {
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName)));
    }

    /**
     * {@inheritdoc}
     */
    public function truncateTable($tableName)
    {
        $sql = sprintf(
            'DELETE FROM %s',
            $this->quoteTableName($tableName)
        );

        $this->execute($sql);
    }

    /**
     * {@inheritdoc}
     */
    public function getColumns($tableName)
    {
        $columns = [];
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));

        foreach ($rows as $columnInfo) {
            $column = new Column();
            $type = strtolower($columnInfo['type']);
            $column->setName($columnInfo['name'])
                   ->setNull($columnInfo['notnull'] !== '1')
                   ->setDefault($columnInfo['dflt_value']);

            $phinxType = $this->getPhinxType($type);
            $column->setType($phinxType['name'])
                   ->setLimit($phinxType['limit']);

            if ($columnInfo['pk'] == 1) {
                $column->setIdentity(true);
            }

            $columns[] = $column;
        }

        return $columns;
    }

    /**
     * {@inheritdoc}
     */
    public function hasColumn($tableName, $columnName)
    {
        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
        foreach ($rows as $column) {
            if (strcasecmp($column['name'], $columnName) === 0) {
                return true;
            }
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function addColumn(Table $table, Column $column)
    {
        $sql = sprintf(
            'ALTER TABLE %s ADD COLUMN %s %s',
            $this->quoteTableName($table->getName()),
            $this->quoteColumnName($column->getName()),
            $this->getColumnSqlDefinition($column)
        );

        $this->execute($sql);
    }

    /**
     * {@inheritdoc}
     */
    public function renameColumn($tableName, $columnName, $newColumnName)
    {
        $tmpTableName = 'tmp_' . $tableName;

        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');

        $sql = '';
        foreach ($rows as $table) {
            if ($table['tbl_name'] === $tableName) {
                $sql = $table['sql'];
            }
        }

        $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
        $selectColumns = [];
        $writeColumns = [];
        foreach ($columns as $column) {
            $selectName = $column['name'];
            $writeName = ($selectName == $columnName)? $newColumnName : $selectName;
            $selectColumns[] = $this->quoteColumnName($selectName);
            $writeColumns[] = $this->quoteColumnName($writeName);
        }

        if (!in_array($this->quoteColumnName($columnName), $selectColumns)) {
            throw new \InvalidArgumentException(sprintf(
                'The specified column doesn\'t exist: ' . $columnName
            ));
        }

        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName));

        $sql = str_replace(
            $this->quoteColumnName($columnName),
            $this->quoteColumnName($newColumnName),
            $sql
        );
        $this->execute($sql);

        $sql = sprintf(
            'INSERT INTO %s(%s) SELECT %s FROM %s',
            $tableName,
            implode(', ', $writeColumns),
            implode(', ', $selectColumns),
            $tmpTableName
        );

        $this->execute($sql);

        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
    }

    /**
     * {@inheritdoc}
     */
    public function changeColumn($tableName, $columnName, Column $newColumn)
    {
        // TODO: DRY this up....
        $tmpTableName = 'tmp_' . $tableName;

        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');

        $sql = '';
        foreach ($rows as $table) {
            if ($table['tbl_name'] === $tableName) {
                $sql = $table['sql'];
            }
        }

        $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
        $selectColumns = [];
        $writeColumns = [];
        foreach ($columns as $column) {
            $selectName = $column['name'];
            $writeName = ($selectName === $columnName)? $newColumn->getName() : $selectName;
            $selectColumns[] = $this->quoteColumnName($selectName);
            $writeColumns[] = $this->quoteColumnName($writeName);
        }

        if (!in_array($this->quoteColumnName($columnName), $selectColumns)) {
            throw new \InvalidArgumentException(sprintf(
                'The specified column doesn\'t exist: ' . $columnName
            ));
        }

        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName));

        $sql = preg_replace(
            sprintf("/%s(?:\/\*.*?\*\/|\([^)]+\)|'[^']*?'|[^,])+([,)])/", $this->quoteColumnName($columnName)),
            sprintf('%s %s$1', $this->quoteColumnName($newColumn->getName()), $this->getColumnSqlDefinition($newColumn)),
            $sql,
            1
        );

        $this->execute($sql);

        $sql = sprintf(
            'INSERT INTO %s(%s) SELECT %s FROM %s',
            $tableName,
            implode(', ', $writeColumns),
            implode(', ', $selectColumns),
            $tmpTableName
        );

        $this->execute($sql);
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
    }

    /**
     * {@inheritdoc}
     */
    public function dropColumn($tableName, $columnName)
    {
        // TODO: DRY this up....
        $tmpTableName = 'tmp_' . $tableName;

        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');

        $sql = '';
        foreach ($rows as $table) {
            if ($table['tbl_name'] === $tableName) {
                $sql = $table['sql'];
            }
        }

        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
        $columns = [];
        $columnType = null;
        foreach ($rows as $row) {
            if ($row['name'] !== $columnName) {
                $columns[] = $row['name'];
            } else {
                $found = true;
                $columnType = $row['type'];
            }
        }

        if (!isset($found)) {
            throw new \InvalidArgumentException(sprintf(
                'The specified column doesn\'t exist: ' . $columnName
            ));
        }

        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $tableName, $tmpTableName));

        $sql = preg_replace(
            sprintf("/%s\s%s.*(,\s(?!')|\)$)/U", preg_quote($this->quoteColumnName($columnName)), preg_quote($columnType)),
            "",
            $sql
        );

        if (substr($sql, -2) === ', ') {
            $sql = substr($sql, 0, -2) . ')';
        }

        $this->execute($sql);

        $sql = sprintf(
            'INSERT INTO %s(%s) SELECT %s FROM %s',
            $tableName,
            implode(', ', $columns),
            implode(', ', $columns),
            $tmpTableName
        );

        $this->execute($sql);
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
    }

    /**
     * Get an array of indexes from a particular table.
     *
     * @param string $tableName Table Name
     * @return array
     */
    protected function getIndexes($tableName)
    {
        $indexes = [];
        $rows = $this->fetchAll(sprintf('pragma index_list(%s)', $tableName));

        foreach ($rows as $row) {
            $indexData = $this->fetchAll(sprintf('pragma index_info(%s)', $row['name']));
            if (!isset($indexes[$tableName])) {
                $indexes[$tableName] = ['index' => $row['name'], 'columns' => []];
            }
            foreach ($indexData as $indexItem) {
                $indexes[$tableName]['columns'][] = strtolower($indexItem['name']);
            }
        }

        return $indexes;
    }

    /**
     * {@inheritdoc}
     */
    public function hasIndex($tableName, $columns)
    {
        if (is_string($columns)) {
            $columns = [$columns]; // str to array
        }

        $columns = array_map('strtolower', $columns);
        $indexes = $this->getIndexes($tableName);

        foreach ($indexes as $index) {
            $a = array_diff($columns, $index['columns']);
            if (empty($a)) {
                return true;
            }
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function hasIndexByName($tableName, $indexName)
    {
        $indexes = $this->getIndexes($tableName);

        foreach ($indexes as $index) {
            if ($indexName === $index['index']) {
                return true;
            }
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function addIndex(Table $table, Index $index)
    {
        $indexColumnArray = [];
        foreach ($index->getColumns() as $column) {
            $indexColumnArray[] = sprintf('`%s` ASC', $column);
        }
        $indexColumns = implode(',', $indexColumnArray);
        $this->execute(
            sprintf(
                'CREATE %s ON %s (%s)',
                $this->getIndexSqlDefinition($table, $index),
                $this->quoteTableName($table->getName()),
                $indexColumns
            )
        );
    }

    /**
     * {@inheritdoc}
     */
    public function dropIndex($tableName, $columns)
    {
        if (is_string($columns)) {
            $columns = [$columns]; // str to array
        }

        $indexes = $this->getIndexes($tableName);
        $columns = array_map('strtolower', $columns);

        foreach ($indexes as $index) {
            $a = array_diff($columns, $index['columns']);
            if (empty($a)) {
                $this->execute(
                    sprintf(
                        'DROP INDEX %s',
                        $this->quoteColumnName($index['index'])
                    )
                );

                return;
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function dropIndexByName($tableName, $indexName)
    {
        $indexes = $this->getIndexes($tableName);

        foreach ($indexes as $index) {
            if ($indexName === $index['index']) {
                $this->execute(
                    sprintf(
                        'DROP INDEX %s',
                        $this->quoteColumnName($indexName)
                    )
                );

                return;
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function hasForeignKey($tableName, $columns, $constraint = null)
    {
        if (is_string($columns)) {
            $columns = [$columns]; // str to array
        }
        $foreignKeys = $this->getForeignKeys($tableName);

        $a = array_diff($columns, $foreignKeys);
        if (empty($a)) {
            return true;
        }

        return false;
    }

    /**
     * Get an array of foreign keys from a particular table.
     *
     * @param string $tableName Table Name
     * @return array
     */
    protected function getForeignKeys($tableName)
    {
        $foreignKeys = [];
        $rows = $this->fetchAll(
            "SELECT sql, tbl_name
              FROM (
                    SELECT sql sql, type type, tbl_name tbl_name, name name
                      FROM sqlite_master
                     UNION ALL
                    SELECT sql, type, tbl_name, name
                      FROM sqlite_temp_master
                   )
             WHERE type != 'meta'
               AND sql NOTNULL
               AND name NOT LIKE 'sqlite_%'
             ORDER BY substr(type, 2, 1), name"
        );

        foreach ($rows as $row) {
            if ($row['tbl_name'] === $tableName) {
                if (strpos($row['sql'], 'REFERENCES') !== false) {
                    preg_match_all("/\(`([^`]*)`\) REFERENCES/", $row['sql'], $matches);
                    foreach ($matches[1] as $match) {
                        $foreignKeys[] = $match;
                    }
                }
            }
        }

        return $foreignKeys;
    }

    /**
     * {@inheritdoc}
     */
    public function addForeignKey(Table $table, ForeignKey $foreignKey)
    {
        // TODO: DRY this up....
        $this->execute('pragma foreign_keys = ON');

        $tmpTableName = 'tmp_' . $table->getName();
        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');

        $sql = '';
        foreach ($rows as $row) {
            if ($row['tbl_name'] === $table->getName()) {
                $sql = $row['sql'];
            }
        }

        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($table->getName())));
        $columns = [];
        foreach ($rows as $column) {
            $columns[] = $this->quoteColumnName($column['name']);
        }

        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($table->getName()), $tmpTableName));

        $sql = substr($sql, 0, -1) . ',' . $this->getForeignKeySqlDefinition($foreignKey) . ')';
        $this->execute($sql);

        $sql = sprintf(
            'INSERT INTO %s(%s) SELECT %s FROM %s',
            $this->quoteTableName($table->getName()),
            implode(', ', $columns),
            implode(', ', $columns),
            $this->quoteTableName($tmpTableName)
        );

        $this->execute($sql);
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
    }

    /**
     * {@inheritdoc}
     */
    public function dropForeignKey($tableName, $columns, $constraint = null)
    {
        // TODO: DRY this up....
        if (is_string($columns)) {
            $columns = [$columns]; // str to array
        }

        $tmpTableName = 'tmp_' . $tableName;

        $rows = $this->fetchAll('select * from sqlite_master where `type` = \'table\'');

        $sql = '';
        foreach ($rows as $table) {
            if ($table['tbl_name'] === $tableName) {
                $sql = $table['sql'];
            }
        }

        $rows = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName)));
        $replaceColumns = [];
        foreach ($rows as $row) {
            if (!in_array($row['name'], $columns)) {
                $replaceColumns[] = $row['name'];
            } else {
                $found = true;
            }
        }

        if (!isset($found)) {
            throw new \InvalidArgumentException(sprintf(
                'The specified column doesn\'t exist: '
            ));
        }

        $this->execute(sprintf('ALTER TABLE %s RENAME TO %s', $this->quoteTableName($tableName), $tmpTableName));

        foreach ($columns as $columnName) {
            $search = sprintf(
                "/,[^,]*\(%s(?:,`?(.*)`?)?\) REFERENCES[^,]*\([^\)]*\)[^,)]*/",
                $this->quoteColumnName($columnName)
            );
            $sql = preg_replace($search, '', $sql, 1);
        }

        $this->execute($sql);

        $sql = sprintf(
            'INSERT INTO %s(%s) SELECT %s FROM %s',
            $tableName,
            implode(', ', $columns),
            implode(', ', $columns),
            $tmpTableName
        );

        $this->execute($sql);
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tmpTableName)));
    }

    /**
     * {@inheritdoc}
     */
    public function insert(Table $table, $row)
    {
        $sql = sprintf(
            "INSERT INTO %s ",
            $this->quoteTableName($table->getName())
        );

        $columns = array_keys($row);
        $sql .= "(" . implode(', ', array_map([$this, 'quoteColumnName'], $columns)) . ")";
        $sql .= " VALUES ";

        $sql .= "(" . implode(', ', array_map(function ($value) {
            if (is_numeric($value)) {
                return $value;
            }

            if ($value === null) {
                return 'null';
            }

                return $this->getConnection()->quote($value);
        }, $row)) . ")";

        $this->execute($sql);
    }

    /**
     * {@inheritdoc}
     */
    public function getSqlType($type, $limit = null)
    {
        switch ($type) {
            case static::PHINX_TYPE_STRING:
                return ['name' => 'varchar', 'limit' => 255];
            case static::PHINX_TYPE_CHAR:
                return ['name' => 'char', 'limit' => 255];
            case static::PHINX_TYPE_TEXT:
                return ['name' => 'text'];
            case static::PHINX_TYPE_INTEGER:
                return ['name' => 'integer'];
            case static::PHINX_TYPE_BIG_INTEGER:
                return ['name' => 'bigint'];
            case static::PHINX_TYPE_FLOAT:
                return ['name' => 'float'];
            case static::PHINX_TYPE_DECIMAL:
                return ['name' => 'decimal'];
            case static::PHINX_TYPE_DATETIME:
                return ['name' => 'datetime'];
            case static::PHINX_TYPE_TIMESTAMP:
                return ['name' => 'datetime'];
            case static::PHINX_TYPE_TIME:
                return ['name' => 'time'];
            case static::PHINX_TYPE_DATE:
                return ['name' => 'date'];
            case static::PHINX_TYPE_BLOB:
            case static::PHINX_TYPE_BINARY:
                return ['name' => 'blob'];
            case static::PHINX_TYPE_BOOLEAN:
                return ['name' => 'boolean'];
            case static::PHINX_TYPE_UUID:
                return ['name' => 'char', 'limit' => 36];
            case static::PHINX_TYPE_ENUM:
                return ['name' => 'enum'];
            // Geospatial database types
            // No specific data types exist in SQLite, instead all geospatial
            // functionality is handled in the client. See also: SpatiaLite.
            case static::PHINX_TYPE_GEOMETRY:
            case static::PHINX_TYPE_POLYGON:
                return ['name' => 'text'];
            case static::PHINX_TYPE_LINESTRING:
                return ['name' => 'varchar', 'limit' => 255];
            case static::PHINX_TYPE_POINT:
                return ['name' => 'float'];
            default:
                throw new \RuntimeException('The type: "' . $type . '" is not supported.');
        }
    }

    /**
     * Returns Phinx type by SQL type
     *
     * @param string $sqlTypeDef SQL type
     * @returns string Phinx type
     */
    public function getPhinxType($sqlTypeDef)
    {
        if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*$/', $sqlTypeDef, $matches)) {
            throw new \RuntimeException('Column type ' . $sqlTypeDef . ' is not supported');
        } else {
            $limit = null;
            $precision = null;
            $type = $matches[1];
            if (count($matches) > 2) {
                $limit = $matches[3] ?: null;
            }
            if (count($matches) > 4) {
                $precision = $matches[5];
            }
            switch ($matches[1]) {
                case 'varchar':
                    $type = static::PHINX_TYPE_STRING;
                    if ($limit === 255) {
                        $limit = null;
                    }
                    break;
                case 'char':
                    $type = static::PHINX_TYPE_CHAR;
                    if ($limit === 255) {
                        $limit = null;
                    }
                    if ($limit === 36) {
                        $type = static::PHINX_TYPE_UUID;
                    }
                    break;
                case 'int':
                    $type = static::PHINX_TYPE_INTEGER;
                    if ($limit === 11) {
                        $limit = null;
                    }
                    break;
                case 'bigint':
                    if ($limit === 11) {
                        $limit = null;
                    }
                    $type = static::PHINX_TYPE_BIG_INTEGER;
                    break;
                case 'blob':
                    $type = static::PHINX_TYPE_BINARY;
                    break;
            }
            if ($type === 'tinyint') {
                if ($matches[3] === 1) {
                    $type = static::PHINX_TYPE_BOOLEAN;
                    $limit = null;
                }
            }

            $this->getSqlType($type);

            return [
                'name' => $type,
                'limit' => $limit,
                'precision' => $precision
            ];
        }
    }

    /**
     * {@inheritdoc}
     */
    public function createDatabase($name, $options = [])
    {
        touch($name . '.sqlite3');
    }

    /**
     * {@inheritdoc}
     */
    public function hasDatabase($name)
    {
        return is_file($name . '.sqlite3');
    }

    /**
     * {@inheritdoc}
     */
    public function dropDatabase($name)
    {
        if (file_exists($name . '.sqlite3')) {
            unlink($name . '.sqlite3');
        }
    }

    /**
     * Get the definition for a `DEFAULT` statement.
     *
     * @param  mixed $default
     * @return string
     */
    protected function getDefaultValueDefinition($default)
    {
        if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) {
            $default = $this->getConnection()->quote($default);
        } elseif (is_bool($default)) {
            $default = $this->castToBool($default);
        }

        return isset($default) ? ' DEFAULT ' . $default : '';
    }

    /**
     * Gets the SQLite Column Definition for a Column object.
     *
     * @param \Phinx\Db\Table\Column $column Column
     * @return string
     */
    protected function getColumnSqlDefinition(Column $column)
    {
        $sqlType = $this->getSqlType($column->getType());
        $def = '';
        $def .= strtoupper($sqlType['name']);
        if ($column->getPrecision() && $column->getScale()) {
            $def .= '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
        }
        $limitable = in_array(strtoupper($sqlType['name']), $this->definitionsWithLimits);
        if (($column->getLimit() || isset($sqlType['limit'])) && $limitable) {
            $def .= '(' . ($column->getLimit() ?: $sqlType['limit']) . ')';
        }
        if (($values = $column->getValues()) && is_array($values)) {
            $def .= " CHECK({$column->getName()} IN ('" . implode("', '", $values) . "'))";
        }

        $default = $column->getDefault();

        $def .= ($column->isNull() || is_null($default)) ? ' NULL' : ' NOT NULL';
        $def .= $this->getDefaultValueDefinition($default);
        $def .= ($column->isIdentity()) ? ' PRIMARY KEY AUTOINCREMENT' : '';

        if ($column->getUpdate()) {
            $def .= ' ON UPDATE ' . $column->getUpdate();
        }

        $def .= $this->getCommentDefinition($column);

        return $def;
    }

    /**
     * Gets the comment Definition for a Column object.
     *
     * @param \Phinx\Db\Table\Column $column Column
     * @return string
     */
    protected function getCommentDefinition(Column $column)
    {
        if ($column->getComment()) {
            return ' /* ' . $column->getComment() . ' */ ';
        }

        return '';
    }

    /**
     * Gets the SQLite Index Definition for an Index object.
     *
     * @param \Phinx\Db\Table $table Table
     * @param \Phinx\Db\Table\Index $index Index
     * @return string
     */
    protected function getIndexSqlDefinition(Table $table, Index $index)
    {
        if ($index->getType() === Index::UNIQUE) {
            $def = 'UNIQUE INDEX';
        } else {
            $def = 'INDEX';
        }
        if (is_string($index->getName())) {
            $indexName = $index->getName();
        } else {
            $indexName = $table->getName() . '_';
            foreach ($index->getColumns() as $column) {
                $indexName .= $column . '_';
            }
            $indexName .= 'index';
        }
        $def .= ' `' . $indexName . '`';

        return $def;
    }

    /**
     * {@inheritdoc}
     */
    public function getColumnTypes()
    {
        return array_merge(parent::getColumnTypes(), ['enum']);
    }

    /**
     * Gets the SQLite Foreign Key Definition for an ForeignKey object.
     *
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
     * @return string
     */
    protected function getForeignKeySqlDefinition(ForeignKey $foreignKey)
    {
        $def = '';
        if ($foreignKey->getConstraint()) {
            $def .= ' CONSTRAINT ' . $this->quoteColumnName($foreignKey->getConstraint());
        } else {
            $columnNames = [];
            foreach ($foreignKey->getColumns() as $column) {
                $columnNames[] = $this->quoteColumnName($column);
            }
            $def .= ' FOREIGN KEY (' . implode(',', $columnNames) . ')';
            $refColumnNames = [];
            foreach ($foreignKey->getReferencedColumns() as $column) {
                $refColumnNames[] = $this->quoteColumnName($column);
            }
            $def .= ' REFERENCES ' . $this->quoteTableName($foreignKey->getReferencedTable()->getName()) . ' (' . implode(',', $refColumnNames) . ')';
            if ($foreignKey->getOnDelete()) {
                $def .= ' ON DELETE ' . $foreignKey->getOnDelete();
            }
            if ($foreignKey->getOnUpdate()) {
                $def .= ' ON UPDATE ' . $foreignKey->getOnUpdate();
            }
        }

        return $def;
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2017 Cake Software Foundation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Db\Adapter
 */
namespace Phinx\Db\Adapter;

use Phinx\Db\Table;
use Phinx\Db\Table\Column;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Base Abstract Database Adapter.
 */
abstract class AbstractAdapter implements AdapterInterface
{
    /**
     * @var array
     */
    protected $options = [];

    /**
     * @var \Symfony\Component\Console\Input\InputInterface
     */
    protected $input;

    /**
     * @var \Symfony\Component\Console\Output\OutputInterface
     */
    protected $output;

    /**
     * @var string
     */
    protected $schemaTableName = 'phinxlog';

    /**
     * Class Constructor.
     *
     * @param array $options Options
     * @param \Symfony\Component\Console\Input\InputInterface $input Input Interface
     * @param \Symfony\Component\Console\Output\OutputInterface  $output Output Interface
     */
    public function __construct(array $options, InputInterface $input = null, OutputInterface $output = null)
    {
        $this->setOptions($options);
        if ($input !== null) {
            $this->setInput($input);
        }
        if ($output !== null) {
            $this->setOutput($output);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function setOptions(array $options)
    {
        $this->options = $options;

        if (isset($options['default_migration_table'])) {
            $this->setSchemaTableName($options['default_migration_table']);
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getOptions()
    {
        return $this->options;
    }

    /**
     * {@inheritdoc}
     */
    public function hasOption($name)
    {
        return isset($this->options[$name]);
    }

    /**
     * {@inheritdoc}
     */
    public function getOption($name)
    {
        if (!$this->hasOption($name)) {
            return null;
        }

        return $this->options[$name];
    }

    /**
     * {@inheritdoc}
     */
    public function setInput(InputInterface $input)
    {
        $this->input = $input;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getInput()
    {
        return $this->input;
    }

    /**
     * {@inheritdoc}
     */
    public function setOutput(OutputInterface $output)
    {
        $this->output = $output;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getOutput()
    {
        if ($this->output === null) {
            $output = new NullOutput();
            $this->setOutput($output);
        }

        return $this->output;
    }

    /**
     * {@inheritdoc}
     *
     * @return array
     */
    public function getVersions()
    {
        $rows = $this->getVersionLog();

        return array_keys($rows);
    }

    /**
     * Gets the schema table name.
     *
     * @return string
     */
    public function getSchemaTableName()
    {
        return $this->schemaTableName;
    }

    /**
     * Sets the schema table name.
     *
     * @param string $schemaTableName Schema Table Name
     * @return $this
     */
    public function setSchemaTableName($schemaTableName)
    {
        $this->schemaTableName = $schemaTableName;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function hasSchemaTable()
    {
        return $this->hasTable($this->getSchemaTableName());
    }

    /**
     * {@inheritdoc}
     */
    public function createSchemaTable()
    {
        try {
            $options = [
                'id' => false,
                'primary_key' => 'version'
            ];

            $table = new Table($this->getSchemaTableName(), $options, $this);
            $table->addColumn('version', 'biginteger')
                ->addColumn('migration_name', 'string', ['limit' => 100, 'default' => null, 'null' => true])
                ->addColumn('start_time', 'timestamp', ['default' => null, 'null' => true])
                ->addColumn('end_time', 'timestamp', ['default' => null, 'null' => true])
                ->addColumn('breakpoint', 'boolean', ['default' => false])
                ->save();
        } catch (\Exception $exception) {
            throw new \InvalidArgumentException('There was a problem creating the schema table: ' . $exception->getMessage());
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getAdapterType()
    {
        return $this->getOption('adapter');
    }

    /**
     * {@inheritdoc}
     */
    public function isValidColumnType(Column $column)
    {
        return in_array($column->getType(), $this->getColumnTypes());
    }

    /**
     * Determines if instead of executing queries a dump to standard output is needed
     *
     * @return bool
     */
    public function isDryRunEnabled()
    {
        $input = $this->getInput();

        return ($input && $input->hasOption('dry-run')) ? $input->getOption('dry-run') : false;
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Db\Adapter
 */
namespace Phinx\Db\Adapter;

/**
 * Wrapper Interface.
 *
 * @author Woody Gilk <woody.gilk@gmail.com>
 */
interface WrapperInterface
{
    /**
     * Class constructor, must always wrap another adapter.
     *
     * @param  \Phinx\Db\Adapter\AdapterInterface $adapter
     */
    public function __construct(AdapterInterface $adapter);

    /**
     * Sets the database adapter to proxy commands to.
     *
     * @param  \Phinx\Db\Adapter\AdapterInterface $adapter
     * @return \Phinx\Db\Adapter\AdapterInterface
     */
    public function setAdapter(AdapterInterface $adapter);

    /**
     * Gets the database adapter.
     *
     * @throws \RuntimeException if the adapter has not been set
     * @return \Phinx\Db\Adapter\AdapterInterface
     */
    public function getAdapter();
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Db
 */
namespace Phinx\Db\Table;

class Index
{
    /**
     * @var string
     */
    const UNIQUE = 'unique';

    /**
     * @var string
     */
    const INDEX = 'index';

    /**
     * @var string
     */
    const FULLTEXT = 'fulltext';

    /**
     * @var array
     */
    protected $columns;

    /**
     * @var string
     */
    protected $type = self::INDEX;

    /**
     * @var string
     */
    protected $name = null;

    /**
     * @var integer
     */
    protected $limit = null;

    /**
     * Sets the index columns.
     *
     * @param array $columns
     * @return \Phinx\Db\Table\Index
     */
    public function setColumns($columns)
    {
        $this->columns = $columns;

        return $this;
    }

    /**
     * Gets the index columns.
     *
     * @return array
     */
    public function getColumns()
    {
        return $this->columns;
    }

    /**
     * Sets the index type.
     *
     * @param string $type
     * @return \Phinx\Db\Table\Index
     */
    public function setType($type)
    {
        $this->type = $type;

        return $this;
    }

    /**
     * Gets the index type.
     *
     * @return string
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * Sets the index name.
     *
     * @param string $name
     * @return \Phinx\Db\Table\Index
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Gets the index name.
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Sets the index limit.
     *
     * @param int $limit
     * @return \Phinx\Db\Table\Index
     */
    public function setLimit($limit)
    {
        $this->limit = $limit;

        return $this;
    }

    /**
     * Gets the index limit.
     *
     * @return int
     */
    public function getLimit()
    {
        return $this->limit;
    }

    /**
     * Utility method that maps an array of index options to this objects methods.
     *
     * @param array $options Options
     * @throws \RuntimeException
     * @return \Phinx\Db\Table\Index
     */
    public function setOptions($options)
    {
        // Valid Options
        $validOptions = ['type', 'unique', 'name', 'limit'];
        foreach ($options as $option => $value) {
            if (!in_array($option, $validOptions, true)) {
                throw new \RuntimeException(sprintf('"%s" is not a valid index option.', $option));
            }

            // handle $options['unique']
            if (strcasecmp($option, self::UNIQUE) === 0) {
                if ((bool)$value) {
                    $this->setType(self::UNIQUE);
                }
                continue;
            }

            $method = 'set' . ucfirst($option);
            $this->$method($value);
        }

        return $this;
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Db
 * @author     Leonid Kuzmin <lndkuzmin@gmail.com>
 */
namespace Phinx\Db\Table;

use Phinx\Db\Table;

class ForeignKey
{
    const CASCADE = 'CASCADE';
    const RESTRICT = 'RESTRICT';
    const SET_NULL = 'SET NULL';
    const NO_ACTION = 'NO ACTION';

    /**
     * @var array
     */
    protected $columns = [];

    /**
     * @var \Phinx\Db\Table
     */
    protected $referencedTable;

    /**
     * @var array
     */
    protected $referencedColumns = [];

    /**
     * @var string
     */
    protected $onDelete;

    /**
     * @var string
     */
    protected $onUpdate;

    /**
     * @var string|boolean
     */
    protected $constraint;

    /**
     * Sets the foreign key columns.
     *
     * @param array|string $columns
     * @return \Phinx\Db\Table\ForeignKey
     */
    public function setColumns($columns)
    {
        $this->columns = is_string($columns) ? [$columns] : $columns;

        return $this;
    }

    /**
     * Gets the foreign key columns.
     *
     * @return array
     */
    public function getColumns()
    {
        return $this->columns;
    }

    /**
     * Sets the foreign key referenced table.
     *
     * @param \Phinx\Db\Table $table
     * @return \Phinx\Db\Table\ForeignKey
     */
    public function setReferencedTable(Table $table)
    {
        $this->referencedTable = $table;

        return $this;
    }

    /**
     * Gets the foreign key referenced table.
     *
     * @return \Phinx\Db\Table
     */
    public function getReferencedTable()
    {
        return $this->referencedTable;
    }

    /**
     * Sets the foreign key referenced columns.
     *
     * @param array $referencedColumns
     * @return \Phinx\Db\Table\ForeignKey
     */
    public function setReferencedColumns(array $referencedColumns)
    {
        $this->referencedColumns = $referencedColumns;

        return $this;
    }

    /**
     * Gets the foreign key referenced columns.
     *
     * @return array
     */
    public function getReferencedColumns()
    {
        return $this->referencedColumns;
    }

    /**
     * Sets ON DELETE action for the foreign key.
     *
     * @param string $onDelete
     * @return \Phinx\Db\Table\ForeignKey
     */
    public function setOnDelete($onDelete)
    {
        $this->onDelete = $this->normalizeAction($onDelete);

        return $this;
    }

    /**
     * Gets ON DELETE action for the foreign key.
     *
     * @return string
     */
    public function getOnDelete()
    {
        return $this->onDelete;
    }

    /**
     * Gets ON UPDATE action for the foreign key.
     *
     * @return string
     */
    public function getOnUpdate()
    {
        return $this->onUpdate;
    }

    /**
     * Sets ON UPDATE action for the foreign key.
     *
     * @param string $onUpdate
     * @return \Phinx\Db\Table\ForeignKey
     */
    public function setOnUpdate($onUpdate)
    {
        $this->onUpdate = $this->normalizeAction($onUpdate);

        return $this;
    }

    /**
     * Sets constraint for the foreign key.
     *
     * @param string $constraint
     * @return \Phinx\Db\Table\ForeignKey
     */
    public function setConstraint($constraint)
    {
        $this->constraint = $constraint;

        return $this;
    }

    /**
     * Gets constraint name for the foreign key.
     *
     * @return string|bool
     */
    public function getConstraint()
    {
        return $this->constraint;
    }

    /**
     * Utility method that maps an array of index options to this objects methods.
     *
     * @param array $options Options
     * @throws \RuntimeException
     * @throws \InvalidArgumentException
     * @return \Phinx\Db\Table\ForeignKey
     */
    public function setOptions($options)
    {
        // Valid Options
        $validOptions = ['delete', 'update', 'constraint'];
        foreach ($options as $option => $value) {
            if (!in_array($option, $validOptions, true)) {
                throw new \RuntimeException(sprintf('"%s" is not a valid foreign key option.', $option));
            }

            // handle $options['delete'] as $options['update']
            if ('delete' === $option) {
                $this->setOnDelete($value);
            } elseif ('update' === $option) {
                $this->setOnUpdate($value);
            } else {
                $method = 'set' . ucfirst($option);
                $this->$method($value);
            }
        }

        return $this;
    }

    /**
     * From passed value checks if it's correct and fixes if needed
     *
     * @param string $action
     * @throws \InvalidArgumentException
     * @return string
     */
    protected function normalizeAction($action)
    {
        $constantName = 'static::' . str_replace(' ', '_', strtoupper(trim($action)));
        if (!defined($constantName)) {
            throw new \InvalidArgumentException('Unknown action passed: ' . $action);
        }

        return constant($constantName);
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Db
 */
namespace Phinx\Db\Table;

use Phinx\Db\Adapter\AdapterInterface;

/**
 *
 * This object is based loosely on: http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html.
 */
class Column
{
    /**
     * @var string
     */
    protected $name;

    /**
     * @var string
     */
    protected $type;

    /**
     * @var integer
     */
    protected $limit = null;

    /**
     * @var boolean
     */
    protected $null = false;

    /**
     * @var mixed
     */
    protected $default = null;

    /**
     * @var boolean
     */
    protected $identity = false;

    /**
     * @var integer
     */
    protected $precision;

    /**
     * @var integer
     */
    protected $scale;

    /**
     * @var string
     */
    protected $after;

    /**
     * @var string
     */
    protected $update;

    /**
     * @var string
     */
    protected $comment;

    /**
     * @var boolean
     */
    protected $signed = true;

    /**
     * @var boolean
     */
    protected $timezone = false;

    /**
     * @var array
     */
    protected $properties = [];

    /**
     * @var string
     */
    protected $collation;

    /**
     * @var string
     */
    protected $encoding;

    /**
     * @var array
     */
    protected $values;

    /**
     * Sets the column name.
     *
     * @param string $name
     * @return \Phinx\Db\Table\Column
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Gets the column name.
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Sets the column type.
     *
     * @param string $type
     * @return \Phinx\Db\Table\Column
     */
    public function setType($type)
    {
        $this->type = $type;

        return $this;
    }

    /**
     * Gets the column type.
     *
     * @return string
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * Sets the column limit.
     *
     * @param int $limit
     * @return \Phinx\Db\Table\Column
     */
    public function setLimit($limit)
    {
        $this->limit = $limit;

        return $this;
    }

    /**
     * Gets the column limit.
     *
     * @return int
     */
    public function getLimit()
    {
        return $this->limit;
    }

    /**
     * Sets whether the column allows nulls.
     *
     * @param bool $null
     * @return \Phinx\Db\Table\Column
     */
    public function setNull($null)
    {
        $this->null = (bool)$null;

        return $this;
    }

    /**
     * Gets whether the column allows nulls.
     *
     * @return bool
     */
    public function getNull()
    {
        return $this->null;
    }

    /**
     * Does the column allow nulls?
     *
     * @return bool
     */
    public function isNull()
    {
        return $this->getNull();
    }

    /**
     * Sets the default column value.
     *
     * @param mixed $default
     * @return \Phinx\Db\Table\Column
     */
    public function setDefault($default)
    {
        $this->default = $default;

        return $this;
    }

    /**
     * Gets the default column value.
     *
     * @return mixed
     */
    public function getDefault()
    {
        return $this->default;
    }

    /**
     * Sets whether or not the column is an identity column.
     *
     * @param bool $identity
     * @return \Phinx\Db\Table\Column
     */
    public function setIdentity($identity)
    {
        $this->identity = $identity;

        return $this;
    }

    /**
     * Gets whether or not the column is an identity column.
     *
     * @return bool
     */
    public function getIdentity()
    {
        return $this->identity;
    }

    /**
     * Is the column an identity column?
     *
     * @return bool
     */
    public function isIdentity()
    {
        return $this->getIdentity();
    }

    /**
     * Sets the name of the column to add this column after.
     *
     * @param string $after After
     * @return \Phinx\Db\Table\Column
     */
    public function setAfter($after)
    {
        $this->after = $after;

        return $this;
    }

    /**
     * Returns the name of the column to add this column after.
     *
     * @return string
     */
    public function getAfter()
    {
        return $this->after;
    }

    /**
     * Sets the 'ON UPDATE' mysql column function.
     *
     * @param  string $update On Update function
     * @return \Phinx\Db\Table\Column
     */
    public function setUpdate($update)
    {
        $this->update = $update;

        return $this;
    }

    /**
     * Returns the value of the ON UPDATE column function.
     *
     * @return string
     */
    public function getUpdate()
    {
        return $this->update;
    }

    /**
     * Sets the column precision for decimal.
     *
     * @param int $precision
     * @return \Phinx\Db\Table\Column
     */
    public function setPrecision($precision)
    {
        $this->precision = $precision;

        return $this;
    }

    /**
     * Gets the column precision for decimal.
     *
     * @return int
     */
    public function getPrecision()
    {
        return $this->precision;
    }

    /**
     * Sets the column scale for decimal.
     *
     * @param int $scale
     * @return \Phinx\Db\Table\Column
     */
    public function setScale($scale)
    {
        $this->scale = $scale;

        return $this;
    }

    /**
     * Gets the column scale for decimal.
     *
     * @return int
     */
    public function getScale()
    {
        return $this->scale;
    }

    /**
     * Sets the column comment.
     *
     * @param string $comment
     * @return \Phinx\Db\Table\Column
     */
    public function setComment($comment)
    {
        $this->comment = $comment;

        return $this;
    }

    /**
     * Gets the column comment.
     *
     * @return string
     */
    public function getComment()
    {
        return $this->comment;
    }

    /**
     * Sets whether field should be signed.
     *
     * @param bool $signed
     * @return \Phinx\Db\Table\Column
     */
    public function setSigned($signed)
    {
        $this->signed = (bool)$signed;

        return $this;
    }

    /**
     * Gets whether field should be signed.
     *
     * @return bool
     */
    public function getSigned()
    {
        return $this->signed;
    }

    /**
     * Should the column be signed?
     *
     * @return bool
     */
    public function isSigned()
    {
        return $this->getSigned();
    }

    /**
     * Sets whether the field should have a timezone identifier.
     * Used for date/time columns only!
     *
     * @param bool $timezone
     * @return \Phinx\Db\Table\Column
     */
    public function setTimezone($timezone)
    {
        $this->timezone = (bool)$timezone;

        return $this;
    }

    /**
     * Gets whether field has a timezone identifier.
     *
     * @return bool
     */
    public function getTimezone()
    {
        return $this->timezone;
    }

    /**
     * Should the column have a timezone?
     *
     * @return bool
     */
    public function isTimezone()
    {
        return $this->getTimezone();
    }

    /**
     * Sets field properties.
     *
     * @param array $properties
     *
     * @return \Phinx\Db\Table\Column
     */
    public function setProperties($properties)
    {
        $this->properties = $properties;

        return $this;
    }

    /**
     * Gets field properties
     *
     * @return array
     */
    public function getProperties()
    {
        return $this->properties;
    }

    /**
     * Sets field values.
     *
     * @param mixed (array|string) $values
     *
     * @return \Phinx\Db\Table\Column
     */
    public function setValues($values)
    {
        if (!is_array($values)) {
            $values = preg_split('/,\s*/', $values);
        }
        $this->values = $values;

        return $this;
    }

    /**
     * Gets field values
     *
     * @return array
     */
    public function getValues()
    {
        return $this->values;
    }

    /**
     * Sets the column collation.
     *
     * @param string $collation
     *
     * @throws \UnexpectedValueException If collation not allowed for type
     * @return $this
     */
    public function setCollation($collation)
    {
        $allowedTypes = [
            AdapterInterface::PHINX_TYPE_CHAR,
            AdapterInterface::PHINX_TYPE_STRING,
            AdapterInterface::PHINX_TYPE_TEXT,
        ];
        if (!in_array($this->getType(), $allowedTypes)) {
            throw new \UnexpectedValueException('Collation may be set only for types: ' . implode(', ', $allowedTypes));
        }

        $this->collation = $collation;

        return $this;
    }

    /**
     * Gets the column collation.
     *
     * @return string
     */
    public function getCollation()
    {
        return $this->collation;
    }

    /**
     * Sets the column character set.
     *
     * @param string $encoding
     *
     * @throws \UnexpectedValueException If character set not allowed for type
     * @return $this
     */
    public function setEncoding($encoding)
    {
        $allowedTypes = [
            AdapterInterface::PHINX_TYPE_CHAR,
            AdapterInterface::PHINX_TYPE_STRING,
            AdapterInterface::PHINX_TYPE_TEXT,
        ];
        if (!in_array($this->getType(), $allowedTypes)) {
            throw new \UnexpectedValueException('Character set may be set only for types: ' . implode(', ', $allowedTypes));
        }

        $this->encoding = $encoding;

        return $this;
    }

    /**
     * Gets the column character set.
     *
     * @return string
     */
    public function getEncoding()
    {
        return $this->encoding;
    }

    /**
     * Gets all allowed options. Each option must have a corresponding `setFoo` method.
     *
     * @return array
     */
    protected function getValidOptions()
    {
        return [
            'limit',
            'default',
            'null',
            'identity',
            'precision',
            'scale',
            'after',
            'update',
            'comment',
            'signed',
            'timezone',
            'properties',
            'values',
            'collation',
            'encoding',
        ];
    }

    /**
     * Gets all aliased options. Each alias must reference a valid option.
     *
     * @return array
     */
    protected function getAliasedOptions()
    {
        return [
            'length' => 'limit',
        ];
    }

    /**
     * Utility method that maps an array of column options to this objects methods.
     *
     * @param array $options Options
     * @return \Phinx\Db\Table\Column
     */
    public function setOptions($options)
    {
        $validOptions = $this->getValidOptions();
        $aliasOptions = $this->getAliasedOptions();

        foreach ($options as $option => $value) {
            if (isset($aliasOptions[$option])) {
                // proxy alias -> option
                $option = $aliasOptions[$option];
            }

            if (!in_array($option, $validOptions, true)) {
                throw new \RuntimeException(sprintf('"%s" is not a valid column option.', $option));
            }

            $method = 'set' . ucfirst($option);
            $this->$method($value);
        }

        return $this;
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Db
 */
namespace Phinx\Db;

use Phinx\Db\Adapter\AdapterInterface;
use Phinx\Db\Table\Column;
use Phinx\Db\Table\ForeignKey;
use Phinx\Db\Table\Index;

/**
 *
 * This object is based loosely on: http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html.
 */
class Table
{
    /**
     * @var string
     */
    protected $name;

    /**
     * @var array
     */
    protected $options = [];

    /**
     * @var \Phinx\Db\Adapter\AdapterInterface
     */
    protected $adapter;

    /**
     * @var array
     */
    protected $columns = [];

    /**
     * @var array
     */
    protected $indexes = [];

    /**
     * @var \Phinx\Db\Table\ForeignKey[]
     */
    protected $foreignKeys = [];

    /**
     * @var array
     */
    protected $data = [];

    /**
     * Class Constuctor.
     *
     * @param string $name Table Name
     * @param array $options Options
     * @param \Phinx\Db\Adapter\AdapterInterface $adapter Database Adapter
     */
    public function __construct($name, $options = [], AdapterInterface $adapter = null)
    {
        $this->setName($name);
        $this->setOptions($options);

        if ($adapter !== null) {
            $this->setAdapter($adapter);
        }
    }

    /**
     * Sets the table name.
     *
     * @param string $name Table Name
     * @return \Phinx\Db\Table
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Gets the table name.
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Sets the table options.
     *
     * @param array $options
     * @return \Phinx\Db\Table
     */
    public function setOptions($options)
    {
        $this->options = $options;

        return $this;
    }

    /**
     * Gets the table options.
     *
     * @return array
     */
    public function getOptions()
    {
        return $this->options;
    }

    /**
     * Sets the database adapter.
     *
     * @param \Phinx\Db\Adapter\AdapterInterface $adapter Database Adapter
     * @return \Phinx\Db\Table
     */
    public function setAdapter(AdapterInterface $adapter)
    {
        $this->adapter = $adapter;

        return $this;
    }

    /**
     * Gets the database adapter.
     *
     * @return \Phinx\Db\Adapter\AdapterInterface
     */
    public function getAdapter()
    {
        return $this->adapter;
    }

    /**
     * Does the table exist?
     *
     * @return bool
     */
    public function exists()
    {
        return $this->getAdapter()->hasTable($this->getName());
    }

    /**
     * Drops the database table.
     *
     * @return void
     */
    public function drop()
    {
        $this->getAdapter()->dropTable($this->getName());
    }

    /**
     * Renames the database table.
     *
     * @param string $newTableName New Table Name
     * @return \Phinx\Db\Table
     */
    public function rename($newTableName)
    {
        $this->getAdapter()->renameTable($this->getName(), $newTableName);
        $this->setName($newTableName);

        return $this;
    }

    /**
     * Sets an array of columns waiting to be committed.
     * Use setPendingColumns
     *
     * @deprecated
     * @param array $columns Columns
     * @return \Phinx\Db\Table
     */
    public function setColumns($columns)
    {
        $this->setPendingColumns($columns);

        return $this;
    }

    /**
     * Gets an array of the table columns.
     *
     * @return \Phinx\Db\Table\Column[]
     */
    public function getColumns()
    {
        return $this->getAdapter()->getColumns($this->getName());
    }

    /**
     * Sets an array of columns waiting to be committed.
     *
     * @param array $columns Columns
     * @return \Phinx\Db\Table
     */
    public function setPendingColumns($columns)
    {
        $this->columns = $columns;

        return $this;
    }

    /**
     * Gets an array of columns waiting to be committed.
     *
     * @return \Phinx\Db\Table\Column[]
     */
    public function getPendingColumns()
    {
        return $this->columns;
    }

    /**
     * Sets an array of columns waiting to be indexed.
     *
     * @param array $indexes Indexes
     * @return \Phinx\Db\Table
     */
    public function setIndexes($indexes)
    {
        $this->indexes = $indexes;

        return $this;
    }

    /**
     * Gets an array of indexes waiting to be committed.
     *
     * @return array
     */
    public function getIndexes()
    {
        return $this->indexes;
    }

    /**
     * Sets an array of foreign keys waiting to be commited.
     *
     * @param \Phinx\Db\Table\ForeignKey[] $foreignKeys foreign keys
     * @return \Phinx\Db\Table
     */
    public function setForeignKeys($foreignKeys)
    {
        $this->foreignKeys = $foreignKeys;

        return $this;
    }

    /**
     * Gets an array of foreign keys waiting to be commited.
     *
     * @return array|\Phinx\Db\Table\ForeignKey[]
     */
    public function getForeignKeys()
    {
        return $this->foreignKeys;
    }

    /**
     * Sets an array of data to be inserted.
     *
     * @param array $data Data
     * @return \Phinx\Db\Table
     */
    public function setData($data)
    {
        $this->data = $data;

        return $this;
    }

    /**
     * Gets the data waiting to be inserted.
     *
     * @return array
     */
    public function getData()
    {
        return $this->data;
    }

    /**
     * Resets all of the pending table changes.
     *
     * @return void
     */
    public function reset()
    {
        $this->setPendingColumns([]);
        $this->setIndexes([]);
        $this->setForeignKeys([]);
        $this->setData([]);
    }

    /**
     * Add a table column.
     *
     * Type can be: string, text, integer, float, decimal, datetime, timestamp,
     * time, date, binary, boolean.
     *
     * Valid options can be: limit, default, null, precision or scale.
     *
     * @param string|\Phinx\Db\Table\Column $columnName Column Name
     * @param string $type Column Type
     * @param array $options Column Options
     * @throws \RuntimeException
     * @throws \InvalidArgumentException
     * @return \Phinx\Db\Table
     */
    public function addColumn($columnName, $type = null, $options = [])
    {
        // we need an adapter set to add a column
        if ($this->getAdapter() === null) {
            throw new \RuntimeException('An adapter must be specified to add a column.');
        }

        // create a new column object if only strings were supplied
        if (!$columnName instanceof Column) {
            $column = new Column();
            $column->setName($columnName);
            $column->setType($type);
            $column->setOptions($options); // map options to column methods
        } else {
            $column = $columnName;
        }

        // Delegate to Adapters to check column type
        if (!$this->getAdapter()->isValidColumnType($column)) {
            throw new \InvalidArgumentException(sprintf(
                'An invalid column type "%s" was specified for column "%s".',
                $column->getType(),
                $column->getName()
            ));
        }

        $this->columns[] = $column;

        return $this;
    }

    /**
     * Remove a table column.
     *
     * @param string $columnName Column Name
     * @return \Phinx\Db\Table
     */
    public function removeColumn($columnName)
    {
        $this->getAdapter()->dropColumn($this->getName(), $columnName);

        return $this;
    }

    /**
     * Rename a table column.
     *
     * @param string $oldName Old Column Name
     * @param string $newName New Column Name
     * @return \Phinx\Db\Table
     */
    public function renameColumn($oldName, $newName)
    {
        $this->getAdapter()->renameColumn($this->getName(), $oldName, $newName);

        return $this;
    }

    /**
     * Change a table column type.
     *
     * @param string        $columnName    Column Name
     * @param string|\Phinx\Db\Table\Column $newColumnType New Column Type
     * @param array         $options       Options
     * @return \Phinx\Db\Table
     */
    public function changeColumn($columnName, $newColumnType, $options = [])
    {
        // create a column object if one wasn't supplied
        if (!$newColumnType instanceof Column) {
            $newColumn = new Column();
            $newColumn->setType($newColumnType);
            $newColumn->setOptions($options);
        } else {
            $newColumn = $newColumnType;
        }

        // if the name was omitted use the existing column name
        if ($newColumn->getName() === null || strlen($newColumn->getName()) === 0) {
            $newColumn->setName($columnName);
        }

        $this->getAdapter()->changeColumn($this->getName(), $columnName, $newColumn);

        return $this;
    }

    /**
     * Checks to see if a column exists.
     *
     * @param string $columnName Column Name
     * @return bool
     */
    public function hasColumn($columnName)
    {
        return $this->getAdapter()->hasColumn($this->getName(), $columnName);
    }

    /**
     * Add an index to a database table.
     *
     * In $options you can specific unique = true/false or name (index name).
     *
     * @param string|array|\Phinx\Db\Table\Index $columns Table Column(s)
     * @param array $options Index Options
     * @return \Phinx\Db\Table
     */
    public function addIndex($columns, $options = [])
    {
        // create a new index object if strings or an array of strings were supplied
        if (!$columns instanceof Index) {
            $index = new Index();
            if (is_string($columns)) {
                $columns = [$columns]; // str to array
            }
            $index->setColumns($columns);
            $index->setOptions($options);
        } else {
            $index = $columns;
        }

        $this->indexes[] = $index;

        return $this;
    }

    /**
     * Removes the given index from a table.
     *
     * @param array $columns Columns
     * @return \Phinx\Db\Table
     */
    public function removeIndex($columns)
    {
        $this->getAdapter()->dropIndex($this->getName(), $columns);

        return $this;
    }

    /**
     * Removes the given index identified by its name from a table.
     *
     * @param string $name Index name
     * @return \Phinx\Db\Table
     */
    public function removeIndexByName($name)
    {
        $this->getAdapter()->dropIndexByName($this->getName(), $name);

        return $this;
    }

    /**
     * Checks to see if an index exists.
     *
     * @param string|array $columns Columns
     * @param array        $options Options
     * @return bool
     */
    public function hasIndex($columns)
    {
        return $this->getAdapter()->hasIndex($this->getName(), $columns);
    }

    /**
     * Add a foreign key to a database table.
     *
     * In $options you can specify on_delete|on_delete = cascade|no_action ..,
     * on_update, constraint = constraint name.
     *
     * @param string|array $columns Columns
     * @param string|\Phinx\Db\Table $referencedTable   Referenced Table
     * @param string|array $referencedColumns Referenced Columns
     * @param array $options Options
     * @return \Phinx\Db\Table
     */
    public function addForeignKey($columns, $referencedTable, $referencedColumns = ['id'], $options = [])
    {
        if (is_string($referencedColumns)) {
            $referencedColumns = [$referencedColumns]; // str to array
        }
        $fk = new ForeignKey();
        if ($referencedTable instanceof Table) {
            $fk->setReferencedTable($referencedTable);
        } else {
            $fk->setReferencedTable(new Table($referencedTable, [], $this->adapter));
        }
        $fk->setColumns($columns)
           ->setReferencedColumns($referencedColumns)
           ->setOptions($options);
        $this->foreignKeys[] = $fk;

        return $this;
    }

    /**
     * Removes the given foreign key from the table.
     *
     * @param string|array $columns    Column(s)
     * @param null|string  $constraint Constraint names
     * @return \Phinx\Db\Table
     */
    public function dropForeignKey($columns, $constraint = null)
    {
        if (is_string($columns)) {
            $columns = [$columns];
        }
        if ($constraint) {
            $this->getAdapter()->dropForeignKey($this->getName(), [], $constraint);
        } else {
            $this->getAdapter()->dropForeignKey($this->getName(), $columns);
        }

        return $this;
    }

    /**
     * Checks to see if a foreign key exists.
     *
     * @param  string|array $columns    Column(s)
     * @param  null|string  $constraint Constraint names
     * @return bool
     */
    public function hasForeignKey($columns, $constraint = null)
    {
        return $this->getAdapter()->hasForeignKey($this->getName(), $columns, $constraint);
    }

    /**
     * Add timestamp columns created_at and updated_at to the table.
     *
     * @param string $createdAtColumnName
     * @param string $updatedAtColumnName
     *
     * @return \Phinx\Db\Table
     */
    public function addTimestamps($createdAtColumnName = 'created_at', $updatedAtColumnName = 'updated_at')
    {
        $createdAtColumnName = is_null($createdAtColumnName) ? 'created_at' : $createdAtColumnName;
        $updatedAtColumnName = is_null($updatedAtColumnName) ? 'updated_at' : $updatedAtColumnName;
        $this->addColumn($createdAtColumnName, 'timestamp', [
                'default' => 'CURRENT_TIMESTAMP',
                'update' => ''
            ])
             ->addColumn($updatedAtColumnName, 'timestamp', [
                 'null' => true,
                 'default' => null
             ]);

        return $this;
    }

    /**
     * Insert data into the table.
     *
     * @param array $data array of data in the form:
     *              array(
     *                  array("col1" => "value1", "col2" => "anotherValue1"),
     *                  array("col2" => "value2", "col2" => "anotherValue2"),
     *              )
     *              or array("col1" => "value1", "col2" => "anotherValue1")
     *
     * @return \Phinx\Db\Table
     */
    public function insert($data)
    {
        // handle array of array situations
        if (isset($data[0]) && is_array($data[0])) {
            foreach ($data as $row) {
                $this->data[] = $row;
            }

            return $this;
        }
        $this->data[] = $data;

        return $this;
    }

    /**
     * Creates a table from the object instance.
     *
     * @return void
     */
    public function create()
    {
        $this->getAdapter()->createTable($this);
        $this->saveData();
        $this->reset(); // reset pending changes
    }

    /**
     * Updates a table from the object instance.
     *
     * @throws \RuntimeException
     * @return void
     */
    public function update()
    {
        if (!$this->exists()) {
            throw new \RuntimeException('Cannot update a table that doesn\'t exist!');
        }

        // update table
        foreach ($this->getPendingColumns() as $column) {
            $this->getAdapter()->addColumn($this, $column);
        }

        foreach ($this->getIndexes() as $index) {
            $this->getAdapter()->addIndex($this, $index);
        }

        foreach ($this->getForeignKeys() as $foreignKey) {
            $this->getAdapter()->addForeignKey($this, $foreignKey);
        }

        $this->saveData();
        $this->reset(); // reset pending changes
    }

    /**
     * Commit the pending data waiting for insertion.
     *
     * @return void
     */
    public function saveData()
    {
        $rows = $this->getData();
        if (empty($rows)) {
            return;
        }

        $bulk = true;
        $row = current($rows);
        $c = array_keys($row);
        foreach ($this->getData() as $row) {
            $k = array_keys($row);
            if ($k != $c) {
                $bulk = false;
                break;
            }
        }

        if ($bulk) {
            $this->getAdapter()->bulkinsert($this, $this->getData());
        } else {
            foreach ($this->getData() as $row) {
                $this->getAdapter()->insert($this, $row);
            }
        }
    }

    /**
     * Truncates the table.
     *
     * @return void
     */
    public function truncate()
    {
        $this->getAdapter()->truncateTable($this->getName());
    }

    /**
     * Commits the table changes.
     *
     * If the table doesn't exist it is created otherwise it is updated.
     *
     * @return void
     */
    public function save()
    {
        if ($this->exists()) {
            $this->update(); // update the table
        } else {
            $this->create(); // create the table
        }

        $this->reset(); // reset pending changes
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Migration
 */
namespace Phinx\Migration;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

abstract class AbstractTemplateCreation implements CreationInterface
{
    /**
     * @var \Symfony\Component\Console\Input\InputInterface
     */
    protected $input;

    /**
     * @var \Symfony\Component\Console\Output\OutputInterface
     */
    protected $output;

    /**
     * Class Constructor.
     *
     * @param \Symfony\Component\Console\Input\InputInterface|null  $input
     * @param \Symfony\Component\Console\Output\OutputInterface|null $output
     */
    public function __construct(InputInterface $input = null, OutputInterface $output = null)
    {
        if (!is_null($input)) {
            $this->setInput($input);
        }
        if (!is_null($output)) {
            $this->setOutput($output);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getInput()
    {
        return $this->input;
    }

    /**
     * {@inheritdoc}
     */
    public function setInput(InputInterface $input)
    {
        $this->input = $input;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getOutput()
    {
        return $this->output;
    }

    /**
     * {@inheritdoc}
     */
    public function setOutput(OutputInterface $output)
    {
        $this->output = $output;

        return $this;
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Migration
 */
namespace Phinx\Migration;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Migration interface
 *
 * @author Richard Quadling <RQuadling@GMail.com>
 */
interface CreationInterface
{
    /**
     * CreationInterface constructor.
     *
     * @param \Symfony\Component\Console\Input\InputInterface|null  $input
     * @param \Symfony\Component\Console\Output\OutputInterface|null $output
     */
    public function __construct(InputInterface $input = null, OutputInterface $output = null);

    /**
     * @param \Symfony\Component\Console\Input\InputInterface $input
     *
     * @return \Phinx\Migration\CreationInterface
     */
    public function setInput(InputInterface $input);

    /**
     * @param \Symfony\Component\Console\Output\OutputInterface $output
     *
     * @return \Phinx\Migration\CreationInterface
     */
    public function setOutput(OutputInterface $output);

    /**
     * @return \Symfony\Component\Console\Input\InputInterface
     */
    public function getInput();

    /**
     * @return \Symfony\Component\Console\Output\OutputInterface
     */
    public function getOutput();

    /**
     * Get the migration template.
     *
     * This will be the content that Phinx will amend to generate the migration file.
     *
     * @return string The content of the template for Phinx to amend.
     */
    public function getMigrationTemplate();

    /**
     * Post Migration Creation.
     *
     * Once the migration file has been created, this method will be called, allowing any additional
     * processing, specific to the template to be performed.
     *
     * @param string $migrationFilename The name of the newly created migration.
     * @param string $className         The class name.
     * @param string $baseClassName     The name of the base class.
     * @return void
     */
    public function postMigrationCreation($migrationFilename, $className, $baseClassName);
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Migration
 */
namespace Phinx\Migration;

/**
 * Exception class thrown when migrations cannot be reversed using the 'change'
 * feature.
 *
 * @author Rob Morgan <robbym@gmail.com>
 */
class IrreversibleMigrationException extends \Exception
{
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Migration\Manager
 */
namespace Phinx\Migration\Manager;

use Phinx\Db\Adapter\AdapterFactory;
use Phinx\Db\Adapter\AdapterInterface;
use Phinx\Migration\MigrationInterface;
use Phinx\Seed\SeedInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class Environment
{
    /**
     * @var string
     */
    protected $name;

    /**
     * @var array
     */
    protected $options;

    /**
     * @var \Symfony\Component\Console\Input\InputInterface
     */
    protected $input;

    /**
     * @var \Symfony\Component\Console\Output\OutputInterface
     */
    protected $output;

    /**
     * @var int
     */
    protected $currentVersion;

    /**
     * @var string
     */
    protected $schemaTableName = 'phinxlog';

    /**
     * @var \Phinx\Db\Adapter\AdapterInterface
     */
    protected $adapter;

    /**
     * Class Constructor.
     *
     * @param string $name Environment Name
     * @param array $options Options
     * @return \Phinx\Migration\Manager\Environment
     */
    public function __construct($name, $options)
    {
        $this->name = $name;
        $this->options = $options;
    }

    /**
     * Executes the specified migration on this environment.
     *
     * @param \Phinx\Migration\MigrationInterface $migration Migration
     * @param string $direction Direction
     * @return void
     */
    public function executeMigration(MigrationInterface $migration, $direction = MigrationInterface::UP)
    {
        $direction = ($direction === MigrationInterface::UP) ? MigrationInterface::UP : MigrationInterface::DOWN;
        $migration->setMigratingUp($direction === MigrationInterface::UP);

        $startTime = time();
        $migration->setAdapter($this->getAdapter());

        // begin the transaction if the adapter supports it
        if ($this->getAdapter()->hasTransactions()) {
            $this->getAdapter()->beginTransaction();
        }

        // Run the migration
        if (method_exists($migration, MigrationInterface::CHANGE)) {
            if ($direction === MigrationInterface::DOWN) {
                // Create an instance of the ProxyAdapter so we can record all
                // of the migration commands for reverse playback

                /** @var \Phinx\Db\Adapter\ProxyAdapter $proxyAdapter */
                $proxyAdapter = AdapterFactory::instance()
                    ->getWrapper('proxy', $this->getAdapter());
                $migration->setAdapter($proxyAdapter);
                /** @noinspection PhpUndefinedMethodInspection */
                $migration->change();
                $proxyAdapter->executeInvertedCommands();
                $migration->setAdapter($this->getAdapter());
            } else {
                /** @noinspection PhpUndefinedMethodInspection */
                $migration->change();
            }
        } else {
            $migration->{$direction}();
        }

        // commit the transaction if the adapter supports it
        if ($this->getAdapter()->hasTransactions()) {
            $this->getAdapter()->commitTransaction();
        }

        // Record it in the database
        $this->getAdapter()->migrated($migration, $direction, date('Y-m-d H:i:s', $startTime), date('Y-m-d H:i:s', time()));
    }

    /**
     * Executes the specified seeder on this environment.
     *
     * @param \Phinx\Seed\SeedInterface $seed
     * @return void
     */
    public function executeSeed(SeedInterface $seed)
    {
        $seed->setAdapter($this->getAdapter());

        // begin the transaction if the adapter supports it
        if ($this->getAdapter()->hasTransactions()) {
            $this->getAdapter()->beginTransaction();
        }

        // Run the seeder
        if (method_exists($seed, SeedInterface::RUN)) {
            $seed->run();
        }

        // commit the transaction if the adapter supports it
        if ($this->getAdapter()->hasTransactions()) {
            $this->getAdapter()->commitTransaction();
        }
    }

    /**
     * Sets the environment's name.
     *
     * @param string $name Environment Name
     * @return \Phinx\Migration\Manager\Environment
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Gets the environment name.
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Sets the environment's options.
     *
     * @param array $options Environment Options
     * @return \Phinx\Migration\Manager\Environment
     */
    public function setOptions($options)
    {
        $this->options = $options;

        return $this;
    }

    /**
     * Gets the environment's options.
     *
     * @return array
     */
    public function getOptions()
    {
        return $this->options;
    }

    /**
     * Sets the console input.
     *
     * @param \Symfony\Component\Console\Input\InputInterface $input
     * @return \Phinx\Migration\Manager\Environment
     */
    public function setInput(InputInterface $input)
    {
        $this->input = $input;

        return $this;
    }

    /**
     * Gets the console input.
     *
     * @return \Symfony\Component\Console\Input\InputInterface
     */
    public function getInput()
    {
        return $this->input;
    }

    /**
     * Sets the console output.
     *
     * @param \Symfony\Component\Console\Output\OutputInterface $output Output
     * @return \Phinx\Migration\Manager\Environment
     */
    public function setOutput(OutputInterface $output)
    {
        $this->output = $output;

        return $this;
    }

    /**
     * Gets the console output.
     *
     * @return \Symfony\Component\Console\Output\OutputInterface
     */
    public function getOutput()
    {
        return $this->output;
    }

    /**
     * Gets all migrated version numbers.
     *
     * @return array
     */
    public function getVersions()
    {
        return $this->getAdapter()->getVersions();
    }

    /**
     * Get all migration log entries, indexed by version creation time and sorted ascendingly by the configuration's
     * version_order option
     *
     * @return array
     */
    public function getVersionLog()
    {
        return $this->getAdapter()->getVersionLog();
    }

    /**
     * Sets the current version of the environment.
     *
     * @param int $version Environment Version
     * @return \Phinx\Migration\Manager\Environment
     */
    public function setCurrentVersion($version)
    {
        $this->currentVersion = $version;

        return $this;
    }

    /**
     * Gets the current version of the environment.
     *
     * @return int
     */
    public function getCurrentVersion()
    {
        // We don't cache this code as the current version is pretty volatile.
        // TODO - that means they're no point in a setter then?
        // maybe we should cache and call a reset() method everytime a migration is run
        $versions = $this->getVersions();
        $version = 0;

        if (!empty($versions)) {
            $version = end($versions);
        }

        $this->setCurrentVersion($version);

        return $this->currentVersion;
    }

    /**
     * Sets the database adapter.
     *
     * @param \Phinx\Db\Adapter\AdapterInterface $adapter Database Adapter
     * @return \Phinx\Migration\Manager\Environment
     */
    public function setAdapter(AdapterInterface $adapter)
    {
        $this->adapter = $adapter;

        return $this;
    }

    /**
     * Gets the database adapter.
     *
     * @return \Phinx\Db\Adapter\AdapterInterface
     */
    public function getAdapter()
    {
        if (isset($this->adapter)) {
            return $this->adapter;
        }
        if (isset($this->options['connection'])) {
            if (!($this->options['connection'] instanceof \PDO)) {
                throw new \RuntimeException('The specified connection is not a PDO instance');
            }

            $this->options['connection']->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
            $this->options['adapter'] = $this->options['connection']->getAttribute(\PDO::ATTR_DRIVER_NAME);
        }
        if (!isset($this->options['adapter'])) {
            throw new \RuntimeException('No adapter was specified for environment: ' . $this->getName());
        }

        $factory = AdapterFactory::instance();
        $adapter = $factory
            ->getAdapter($this->options['adapter'], $this->options);

        // Automatically time the executed commands
        $adapter = $factory->getWrapper('timed', $adapter);

        if (isset($this->options['wrapper'])) {
            $adapter = $factory
                ->getWrapper($this->options['wrapper'], $adapter);
        }

        if ($this->getInput()) {
            $adapter->setInput($this->getInput());
        }

        if ($this->getOutput()) {
            $adapter->setOutput($this->getOutput());
        }

        // Use the TablePrefixAdapter if table prefix/suffixes are in use
        if ($adapter->hasOption('table_prefix') || $adapter->hasOption('table_suffix')) {
            $adapter = AdapterFactory::instance()
                ->getWrapper('prefix', $adapter);
        }

        $this->setAdapter($adapter);

        return $adapter;
    }

    /**
     * Sets the schema table name.
     *
     * @param string $schemaTableName Schema Table Name
     * @return \Phinx\Migration\Manager\Environment
     */
    public function setSchemaTableName($schemaTableName)
    {
        $this->schemaTableName = $schemaTableName;

        return $this;
    }

    /**
     * Gets the schema table name.
     *
     * @return string
     */
    public function getSchemaTableName()
    {
        return $this->schemaTableName;
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Migration
 */
namespace Phinx\Migration;

use Phinx\Config\ConfigInterface;
use Phinx\Config\NamespaceAwareInterface;
use Phinx\Migration\Manager\Environment;
use Phinx\Seed\AbstractSeed;
use Phinx\Seed\SeedInterface;
use Phinx\Util\Util;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class Manager
{
    /**
     * @var \Phinx\Config\ConfigInterface
     */
    protected $config;

    /**
     * @var \Symfony\Component\Console\Input\InputInterface
     */
    protected $input;

    /**
     * @var \Symfony\Component\Console\Output\OutputInterface
     */
    protected $output;

    /**
     * @var array
     */
    protected $environments;

    /**
     * @var array
     */
    protected $migrations;

    /**
     * @var array
     */
    protected $seeds;

    /**
     * @var integer
     */
    const EXIT_STATUS_DOWN = 1;

    /**
     * @var integer
     */
    const EXIT_STATUS_MISSING = 2;

    /**
     * Class Constructor.
     *
     * @param \Phinx\Config\ConfigInterface $config Configuration Object
     * @param \Symfony\Component\Console\Input\InputInterface $input Console Input
     * @param \Symfony\Component\Console\Output\OutputInterface $output Console Output
     */
    public function __construct(ConfigInterface $config, InputInterface $input, OutputInterface $output)
    {
        $this->setConfig($config);
        $this->setInput($input);
        $this->setOutput($output);
    }

    /**
     * Prints the specified environment's migration status.
     *
     * @param string $environment
     * @param null $format
     * @return int 0 if all migrations are up, or an error code
     */
    public function printStatus($environment, $format = null)
    {
        $output = $this->getOutput();
        $migrations = [];
        $hasDownMigration = false;
        $hasMissingMigration = false;
        $migrations = $this->getMigrations();
        if (count($migrations)) {
            // TODO - rewrite using Symfony Table Helper as we already have this library
            // included and it will fix formatting issues (e.g drawing the lines)
            $output->writeln('');

            switch ($this->getConfig()->getVersionOrder()) {
                case \Phinx\Config\Config::VERSION_ORDER_CREATION_TIME:
                    $migrationIdAndStartedHeader = "<info>[Migration ID]</info>  Started            ";
                    break;
                case \Phinx\Config\Config::VERSION_ORDER_EXECUTION_TIME:
                    $migrationIdAndStartedHeader = "Migration ID    <info>[Started          ]</info>";
                    break;
                default:
                    throw new \RuntimeException('Invalid version_order configuration option');
            }

            $output->writeln(" Status  $migrationIdAndStartedHeader  Finished             Migration Name ");
            $output->writeln('----------------------------------------------------------------------------------');

            $env = $this->getEnvironment($environment);
            $versions = $env->getVersionLog();

            $maxNameLength = $versions ? max(array_map(function ($version) {
                return strlen($version['migration_name']);
            }, $versions)) : 0;

            $missingVersions = array_diff_key($versions, $migrations);

            $hasMissingMigration = !empty($missingVersions);

            // get the migrations sorted in the same way as the versions
            $sortedMigrations = [];

            foreach ($versions as $versionCreationTime => $version) {
                if (isset($migrations[$versionCreationTime])) {
                    array_push($sortedMigrations, $migrations[$versionCreationTime]);
                    unset($migrations[$versionCreationTime]);
                }
            }

            if (empty($sortedMigrations) && !empty($missingVersions)) {
                // this means we have no up migrations, so we write all the missing versions already so they show up
                // before any possible down migration
                foreach ($missingVersions as $missingVersionCreationTime => $missingVersion) {
                    $this->printMissingVersion($missingVersion, $maxNameLength);

                    unset($missingVersions[$missingVersionCreationTime]);
                }
            }

            // any migration left in the migrations (ie. not unset when sorting the migrations by the version order) is
            // a migration that is down, so we add them to the end of the sorted migrations list
            if (!empty($migrations)) {
                $sortedMigrations = array_merge($sortedMigrations, $migrations);
            }

            foreach ($sortedMigrations as $migration) {
                $version = array_key_exists($migration->getVersion(), $versions) ? $versions[$migration->getVersion()] : false;
                if ($version) {
                    // check if there are missing versions before this version
                    foreach ($missingVersions as $missingVersionCreationTime => $missingVersion) {
                        if ($this->getConfig()->isVersionOrderCreationTime()) {
                            if ($missingVersion['version'] > $version['version']) {
                                break;
                            }
                        } else {
                            if ($missingVersion['start_time'] > $version['start_time']) {
                                break;
                            } elseif ($missingVersion['start_time'] == $version['start_time'] &&
                                $missingVersion['version'] > $version['version']) {
                                break;
                            }
                        }

                        $this->printMissingVersion($missingVersion, $maxNameLength);

                        unset($missingVersions[$missingVersionCreationTime]);
                    }

                    $status = '     <info>up</info> ';
                } else {
                    $hasDownMigration = true;
                    $status = '   <error>down</error> ';
                }
                $maxNameLength = max($maxNameLength, strlen($migration->getName()));

                $output->writeln(sprintf(
                    '%s %14.0f  %19s  %19s  <comment>%s</comment>',
                    $status,
                    $migration->getVersion(),
                    $version['start_time'],
                    $version['end_time'],
                    $migration->getName()
                ));

                if ($version && $version['breakpoint']) {
                    $output->writeln('         <error>BREAKPOINT SET</error>');
                }

                $migrations[] = ['migration_status' => trim(strip_tags($status)), 'migration_id' => sprintf('%14.0f', $migration->getVersion()), 'migration_name' => $migration->getName()];
                unset($versions[$migration->getVersion()]);
            }

            // and finally add any possibly-remaining missing migrations
            foreach ($missingVersions as $missingVersionCreationTime => $missingVersion) {
                $this->printMissingVersion($missingVersion, $maxNameLength);

                unset($missingVersions[$missingVersionCreationTime]);
            }
        } else {
            // there are no migrations
            $output->writeln('');
            $output->writeln('There are no available migrations. Try creating one using the <info>create</info> command.');
        }

        // write an empty line
        $output->writeln('');
        if ($format !== null) {
            switch ($format) {
                case 'json':
                    $output->writeln(json_encode(
                        [
                            'pending_count' => count($this->getMigrations()),
                            'migrations' => $migrations
                        ]
                    ));
                    break;
                default:
                    $output->writeln('<info>Unsupported format: ' . $format . '</info>');
            }
        }

        if ($hasMissingMigration) {
            return self::EXIT_STATUS_MISSING;
        } elseif ($hasDownMigration) {
            return self::EXIT_STATUS_DOWN;
        } else {
            return 0;
        }
    }

    /**
     * Print Missing Version
     *
     * @param array     $version        The missing version to print (in the format returned by Environment.getVersionLog).
     * @param int   $maxNameLength  The maximum migration name length.
     */
    private function printMissingVersion($version, $maxNameLength)
    {
        $this->getOutput()->writeln(sprintf(
            '     <error>up</error>  %14.0f  %19s  %19s  <comment>%s</comment>  <error>** MISSING **</error>',
            $version['version'],
            $version['start_time'],
            $version['end_time'],
            str_pad($version['migration_name'], $maxNameLength, ' ')
        ));

        if ($version && $version['breakpoint']) {
            $this->getOutput()->writeln('         <error>BREAKPOINT SET</error>');
        }
    }

    /**
     * Migrate to the version of the database on a given date.
     *
     * @param string    $environment Environment
     * @param \DateTime $dateTime    Date to migrate to
     *
     * @return void
     */
    public function migrateToDateTime($environment, \DateTime $dateTime)
    {
        $versions = array_keys($this->getMigrations());
        $dateString = $dateTime->format('YmdHis');

        $outstandingMigrations = array_filter($versions, function ($version) use ($dateString) {
            return $version <= $dateString;
        });

        if (count($outstandingMigrations) > 0) {
            $migration = max($outstandingMigrations);
            $this->getOutput()->writeln('Migrating to version ' . $migration);
            $this->migrate($environment, $migration);
        }
    }

    /**
     * Migrate an environment to the specified version.
     *
     * @param string $environment Environment
     * @param int $version
     * @return void
     */
    public function migrate($environment, $version = null)
    {
        $migrations = $this->getMigrations();
        $env = $this->getEnvironment($environment);
        $versions = $env->getVersions();
        $current = $env->getCurrentVersion();

        if (empty($versions) && empty($migrations)) {
            return;
        }

        if ($version === null) {
            $version = max(array_merge($versions, array_keys($migrations)));
        } else {
            if (0 != $version && !isset($migrations[$version])) {
                $this->output->writeln(sprintf(
                    '<comment>warning</comment> %s is not a valid version',
                    $version
                ));

                return;
            }
        }

        // are we migrating up or down?
        $direction = $version > $current ? MigrationInterface::UP : MigrationInterface::DOWN;

        if ($direction === MigrationInterface::DOWN) {
            // run downs first
            krsort($migrations);
            foreach ($migrations as $migration) {
                if ($migration->getVersion() <= $version) {
                    break;
                }

                if (in_array($migration->getVersion(), $versions)) {
                    $this->executeMigration($environment, $migration, MigrationInterface::DOWN);
                }
            }
        }

        ksort($migrations);
        foreach ($migrations as $migration) {
            if ($migration->getVersion() > $version) {
                break;
            }

            if (!in_array($migration->getVersion(), $versions)) {
                $this->executeMigration($environment, $migration, MigrationInterface::UP);
            }
        }
    }

    /**
     * Execute a migration against the specified environment.
     *
     * @param string $name Environment Name
     * @param \Phinx\Migration\MigrationInterface $migration Migration
     * @param string $direction Direction
     * @return void
     */
    public function executeMigration($name, MigrationInterface $migration, $direction = MigrationInterface::UP)
    {
        $this->getOutput()->writeln('');
        $this->getOutput()->writeln(
            ' ==' .
            ' <info>' . $migration->getVersion() . ' ' . $migration->getName() . ':</info>' .
            ' <comment>' . ($direction === MigrationInterface::UP ? 'migrating' : 'reverting') . '</comment>'
        );

        // Execute the migration and log the time elapsed.
        $start = microtime(true);
        $this->getEnvironment($name)->executeMigration($migration, $direction);
        $end = microtime(true);

        $this->getOutput()->writeln(
            ' ==' .
            ' <info>' . $migration->getVersion() . ' ' . $migration->getName() . ':</info>' .
            ' <comment>' . ($direction === MigrationInterface::UP ? 'migrated' : 'reverted') .
            ' ' . sprintf('%.4fs', $end - $start) . '</comment>'
        );
    }

    /**
     * Execute a seeder against the specified environment.
     *
     * @param string $name Environment Name
     * @param \Phinx\Seed\SeedInterface $seed Seed
     * @return void
     */
    public function executeSeed($name, SeedInterface $seed)
    {
        $this->getOutput()->writeln('');
        $this->getOutput()->writeln(
            ' ==' .
            ' <info>' . $seed->getName() . ':</info>' .
            ' <comment>seeding</comment>'
        );

        // Execute the seeder and log the time elapsed.
        $start = microtime(true);
        $this->getEnvironment($name)->executeSeed($seed);
        $end = microtime(true);

        $this->getOutput()->writeln(
            ' ==' .
            ' <info>' . $seed->getName() . ':</info>' .
            ' <comment>seeded' .
            ' ' . sprintf('%.4fs', $end - $start) . '</comment>'
        );
    }

    /**
     * Rollback an environment to the specified version.
     *
     * @param string $environment Environment
     * @param int|string $target
     * @param bool $force
     * @param bool $targetMustMatchVersion
     * @return void
     */
    public function rollback($environment, $target = null, $force = false, $targetMustMatchVersion = true)
    {
        // note that the migrations are indexed by name (aka creation time) in ascending order
        $migrations = $this->getMigrations();

        // note that the version log are also indexed by name with the proper ascending order according to the version order
        $executedVersions = $this->getEnvironment($environment)->getVersionLog();

        // get a list of migrations sorted in the opposite way of the executed versions
        $sortedMigrations = [];

        foreach ($executedVersions as $versionCreationTime => &$executedVersion) {
            // if we have a date (ie. the target must not match a version) and we are sorting by execution time, we
            // convert the version start time so we can compare directly with the target date
            if (!$this->getConfig()->isVersionOrderCreationTime() && !$targetMustMatchVersion) {
                $dateTime = \DateTime::createFromFormat('Y-m-d H:i:s', $executedVersion['start_time']);
                $executedVersion['start_time'] = $dateTime->format('YmdHis');
            }

            if (isset($migrations[$versionCreationTime])) {
                array_unshift($sortedMigrations, $migrations[$versionCreationTime]);
            } else {
                // this means the version is missing so we unset it so that we don't consider it when rolling back
                // migrations (or choosing the last up version as target)
                unset($executedVersions[$versionCreationTime]);
            }
        }

        if ($target === 'all' || $target === '0') {
            $target = 0;
        } elseif (!is_numeric($target) && !is_null($target)) { // try to find a target version based on name
            // search through the migrations using the name
            $migrationNames = array_map(function ($item) {
                return $item['migration_name'];
            }, $executedVersions);
            $found = array_search($target, $migrationNames);

            // check on was found
            if ($found !== false) {
                $target = (string)$found;
            } else {
                $this->getOutput()->writeln("<error>No migration found with name ($target)</error>");

                return;
            }
        }

        // Check we have at least 1 migration to revert
        $executedVersionCreationTimes = array_keys($executedVersions);
        if (empty($executedVersionCreationTimes) || $target == end($executedVersionCreationTimes)) {
            $this->getOutput()->writeln('<error>No migrations to rollback</error>');

            return;
        }

        // If no target was supplied, revert the last migration
        if ($target === null) {
            // Get the migration before the last run migration
            $prev = count($executedVersionCreationTimes) - 2;
            $target = $prev >= 0 ? $executedVersionCreationTimes[$prev] : 0;
        }

        // If the target must match a version, check the target version exists
        if ($targetMustMatchVersion && 0 !== $target && !isset($migrations[$target])) {
            $this->getOutput()->writeln("<error>Target version ($target) not found</error>");

            return;
        }

        // Rollback all versions until we find the wanted rollback target
        $rollbacked = false;

        foreach ($sortedMigrations as $migration) {
            if ($targetMustMatchVersion && $migration->getVersion() == $target) {
                break;
            }

            if (in_array($migration->getVersion(), $executedVersionCreationTimes)) {
                $executedVersion = $executedVersions[$migration->getVersion()];

                if (!$targetMustMatchVersion) {
                    if (($this->getConfig()->isVersionOrderCreationTime() && $executedVersion['version'] <= $target) ||
                        (!$this->getConfig()->isVersionOrderCreationTime() && $executedVersion['start_time'] <= $target)) {
                        break;
                    }
                }

                if (0 != $executedVersion['breakpoint'] && !$force) {
                    $this->getOutput()->writeln('<error>Breakpoint reached. Further rollbacks inhibited.</error>');
                    break;
                }
                $this->executeMigration($environment, $migration, MigrationInterface::DOWN);
                $rollbacked = true;
            }
        }

        if (!$rollbacked) {
            $this->getOutput()->writeln('<error>No migrations to rollback</error>');
        }
    }

    /**
     * Run database seeders against an environment.
     *
     * @param string $environment Environment
     * @param string $seed Seeder
     * @return void
     */
    public function seed($environment, $seed = null)
    {
        $seeds = $this->getSeeds();

        if ($seed === null) {
            // run all seeders
            foreach ($seeds as $seeder) {
                if (array_key_exists($seeder->getName(), $seeds)) {
                    $this->executeSeed($environment, $seeder);
                }
            }
        } else {
            // run only one seeder
            if (array_key_exists($seed, $seeds)) {
                $this->executeSeed($environment, $seeds[$seed]);
            } else {
                throw new \InvalidArgumentException(sprintf('The seed class "%s" does not exist', $seed));
            }
        }
    }

    /**
     * Sets the environments.
     *
     * @param array $environments Environments
     * @return \Phinx\Migration\Manager
     */
    public function setEnvironments($environments = [])
    {
        $this->environments = $environments;

        return $this;
    }

    /**
     * Gets the manager class for the given environment.
     *
     * @param string $name Environment Name
     * @throws \InvalidArgumentException
     * @return \Phinx\Migration\Manager\Environment
     */
    public function getEnvironment($name)
    {
        if (isset($this->environments[$name])) {
            return $this->environments[$name];
        }

        // check the environment exists
        if (!$this->getConfig()->hasEnvironment($name)) {
            throw new \InvalidArgumentException(sprintf(
                'The environment "%s" does not exist',
                $name
            ));
        }

        // create an environment instance and cache it
        $envOptions = $this->getConfig()->getEnvironment($name);
        $envOptions['version_order'] = $this->getConfig()->getVersionOrder();

        $environment = new Environment($name, $envOptions);
        $this->environments[$name] = $environment;
        $environment->setInput($this->getInput());
        $environment->setOutput($this->getOutput());

        return $environment;
    }

    /**
     * Sets the console input.
     *
     * @param \Symfony\Component\Console\Input\InputInterface $input Input
     * @return \Phinx\Migration\Manager
     */
    public function setInput(InputInterface $input)
    {
        $this->input = $input;

        return $this;
    }

    /**
     * Gets the console input.
     *
     * @return \Symfony\Component\Console\Input\InputInterface
     */
    public function getInput()
    {
        return $this->input;
    }

    /**
     * Sets the console output.
     *
     * @param \Symfony\Component\Console\Output\OutputInterface $output Output
     * @return \Phinx\Migration\Manager
     */
    public function setOutput(OutputInterface $output)
    {
        $this->output = $output;

        return $this;
    }

    /**
     * Gets the console output.
     *
     * @return \Symfony\Component\Console\Output\OutputInterface
     */
    public function getOutput()
    {
        return $this->output;
    }

    /**
     * Sets the database migrations.
     *
     * @param array $migrations Migrations
     * @return \Phinx\Migration\Manager
     */
    public function setMigrations(array $migrations)
    {
        $this->migrations = $migrations;

        return $this;
    }

    /**
     * Gets an array of the database migrations, indexed by migration name (aka creation time) and sorted in ascending
     * order
     *
     * @throws \InvalidArgumentException
     * @return \Phinx\Migration\AbstractMigration[]
     */
    public function getMigrations()
    {
        if ($this->migrations === null) {
            $phpFiles = $this->getMigrationFiles();

            // filter the files to only get the ones that match our naming scheme
            $fileNames = [];
            /** @var \Phinx\Migration\AbstractMigration[] $versions */
            $versions = [];

            foreach ($phpFiles as $filePath) {
                if (Util::isValidMigrationFileName(basename($filePath))) {
                    $version = Util::getVersionFromFileName(basename($filePath));

                    if (isset($versions[$version])) {
                        throw new \InvalidArgumentException(sprintf('Duplicate migration - "%s" has the same version as "%s"', $filePath, $versions[$version]->getVersion()));
                    }

                    $config = $this->getConfig();
                    $namespace = $config instanceof NamespaceAwareInterface ? $config->getMigrationNamespaceByPath(dirname($filePath)) : null;

                    // convert the filename to a class name
                    $class = ($namespace === null ? '' : $namespace . '\\') . Util::mapFileNameToClassName(basename($filePath));

                    if (isset($fileNames[$class])) {
                        throw new \InvalidArgumentException(sprintf(
                            'Migration "%s" has the same name as "%s"',
                            basename($filePath),
                            $fileNames[$class]
                        ));
                    }

                    $fileNames[$class] = basename($filePath);

                    // load the migration file
                    /** @noinspection PhpIncludeInspection */
                    require_once $filePath;
                    if (!class_exists($class)) {
                        throw new \InvalidArgumentException(sprintf(
                            'Could not find class "%s" in file "%s"',
                            $class,
                            $filePath
                        ));
                    }

                    // instantiate it
                    $migration = new $class($version, $this->getInput(), $this->getOutput());

                    if (!($migration instanceof AbstractMigration)) {
                        throw new \InvalidArgumentException(sprintf(
                            'The class "%s" in file "%s" must extend \Phinx\Migration\AbstractMigration',
                            $class,
                            $filePath
                        ));
                    }

                    $versions[$version] = $migration;
                }
            }

            ksort($versions);
            $this->setMigrations($versions);
        }

        return $this->migrations;
    }

    /**
     * Returns a list of migration files found in the provided migration paths.
     *
     * @return string[]
     */
    protected function getMigrationFiles()
    {
        $config = $this->getConfig();
        $paths = $config->getMigrationPaths();
        $files = [];

        foreach ($paths as $path) {
            $files = array_merge(
                $files,
                Util::glob($path . DIRECTORY_SEPARATOR . '*.php')
            );
        }
        // glob() can return the same file multiple times
        // This will cause the migration to fail with a
        // false assumption of duplicate migrations
        // http://php.net/manual/en/function.glob.php#110340
        $files = array_unique($files);

        return $files;
    }

    /**
     * Sets the database seeders.
     *
     * @param array $seeds Seeders
     * @return \Phinx\Migration\Manager
     */
    public function setSeeds(array $seeds)
    {
        $this->seeds = $seeds;

        return $this;
    }

    /**
     * Get seed dependencies instances from seed dependency array
     *
     * @param AbstractSeed $seed Seed
     *
     * @return AbstractSeed[]
     */
    private function getSeedDependenciesInstances(AbstractSeed $seed)
    {
        $dependenciesInstances = [];
        $dependencies = $seed->getDependencies();
        if (!empty($dependencies)) {
            foreach ($dependencies as $dependency) {
                foreach ($this->seeds as $seed) {
                    if (get_class($seed) === $dependency) {
                        $dependenciesInstances[get_class($seed)] = $seed;
                    }
                }
            }
        }

        return $dependenciesInstances;
    }

    /**
     * Order seeds by dependencies
     *
     * @param AbstractSeed[] $seeds Seeds
     *
     * @return AbstractSeed[]
     */
    private function orderSeedsByDependencies(array $seeds)
    {
        $orderedSeeds = [];
        foreach ($seeds as $seed) {
            $key = get_class($seed);
            $dependencies = $this->getSeedDependenciesInstances($seed);
            if (!empty($dependencies)) {
                $orderedSeeds[$key] = $seed;
                $orderedSeeds = array_merge($this->orderSeedsByDependencies($dependencies), $orderedSeeds);
            } else {
                $orderedSeeds[$key] = $seed;
            }
        }

        return $orderedSeeds;
    }

    /**
     * Gets an array of database seeders.
     *
     * @throws \InvalidArgumentException
     * @return \Phinx\Seed\AbstractSeed[]
     */
    public function getSeeds()
    {
        if ($this->seeds === null) {
            $phpFiles = $this->getSeedFiles();

            // filter the files to only get the ones that match our naming scheme
            $fileNames = [];
            /** @var \Phinx\Seed\AbstractSeed[] $seeds */
            $seeds = [];

            foreach ($phpFiles as $filePath) {
                if (Util::isValidSeedFileName(basename($filePath))) {
                    $config = $this->getConfig();
                    $namespace = $config instanceof NamespaceAwareInterface ? $config->getSeedNamespaceByPath(dirname($filePath)) : null;

                    // convert the filename to a class name
                    $class = ($namespace === null ? '' : $namespace . '\\') . pathinfo($filePath, PATHINFO_FILENAME);
                    $fileNames[$class] = basename($filePath);

                    // load the seed file
                    /** @noinspection PhpIncludeInspection */
                    require_once $filePath;
                    if (!class_exists($class)) {
                        throw new \InvalidArgumentException(sprintf(
                            'Could not find class "%s" in file "%s"',
                            $class,
                            $filePath
                        ));
                    }

                    // instantiate it
                    $seed = new $class($this->getInput(), $this->getOutput());

                    if (!($seed instanceof AbstractSeed)) {
                        throw new \InvalidArgumentException(sprintf(
                            'The class "%s" in file "%s" must extend \Phinx\Seed\AbstractSeed',
                            $class,
                            $filePath
                        ));
                    }

                    $seeds[$class] = $seed;
                }
            }

            ksort($seeds);
            $this->setSeeds($seeds);
        }

        $this->seeds = $this->orderSeedsByDependencies($this->seeds);
        return $this->seeds;
    }

    /**
     * Returns a list of seed files found in the provided seed paths.
     *
     * @return string[]
     */
    protected function getSeedFiles()
    {
        $config = $this->getConfig();
        $paths = $config->getSeedPaths();
        $files = [];

        foreach ($paths as $path) {
            $files = array_merge(
                $files,
                Util::glob($path . DIRECTORY_SEPARATOR . '*.php')
            );
        }
        // glob() can return the same file multiple times
        // This will cause the migration to fail with a
        // false assumption of duplicate migrations
        // http://php.net/manual/en/function.glob.php#110340
        $files = array_unique($files);

        return $files;
    }

    /**
     * Sets the config.
     *
     * @param  \Phinx\Config\ConfigInterface $config Configuration Object
     * @return \Phinx\Migration\Manager
     */
    public function setConfig(ConfigInterface $config)
    {
        $this->config = $config;

        return $this;
    }

    /**
     * Gets the config.
     *
     * @return \Phinx\Config\ConfigInterface
     */
    public function getConfig()
    {
        return $this->config;
    }

    /**
     * Toggles the breakpoint for a specific version.
     *
     * @param string $environment
     * @param int $version
     * @return void
     */
    public function toggleBreakpoint($environment, $version)
    {
        $migrations = $this->getMigrations();
        $this->getMigrations();
        $env = $this->getEnvironment($environment);
        $versions = $env->getVersionLog();

        if (empty($versions) || empty($migrations)) {
            return;
        }

        if ($version === null) {
            $lastVersion = end($versions);
            $version = $lastVersion['version'];
        }

        if (0 != $version && !isset($migrations[$version])) {
            $this->output->writeln(sprintf(
                '<comment>warning</comment> %s is not a valid version',
                $version
            ));

            return;
        }

        $env->getAdapter()->toggleBreakpoint($migrations[$version]);

        $versions = $env->getVersionLog();

        $this->getOutput()->writeln(
            ' Breakpoint ' . ($versions[$version]['breakpoint'] ? 'set' : 'cleared') .
            ' for <info>' . $version . '</info>' .
            ' <comment>' . $migrations[$version]->getName() . '</comment>'
        );
    }

    /**
     * Remove all breakpoints
     *
     * @param string $environment
     * @return void
     */
    public function removeBreakpoints($environment)
    {
        $this->getOutput()->writeln(sprintf(
            ' %d breakpoints cleared.',
            $this->getEnvironment($environment)->getAdapter()->resetAllBreakpoints()
        ));
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Migration
 */
namespace Phinx\Migration;

use Phinx\Db\Adapter\AdapterInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Migration interface
 *
 * @author Rob Morgan <robbym@gmail.com>
 */
interface MigrationInterface
{
    /**
     * @var string
     */
    const CHANGE = 'change';

    /**
     * @var string
     */
    const UP = 'up';

    /**
     * @var string
     */
    const DOWN = 'down';

    /**
     * Migrate Up
     *
     * @return void
     */
    public function up();

    /**
     * Migrate Down
     *
     * @return void
     */
    public function down();

    /**
     * Sets the database adapter.
     *
     * @param \Phinx\Db\Adapter\AdapterInterface $adapter Database Adapter
     * @return \Phinx\Migration\MigrationInterface
     */
    public function setAdapter(AdapterInterface $adapter);

    /**
     * Gets the database adapter.
     *
     * @return \Phinx\Db\Adapter\AdapterInterface
     */
    public function getAdapter();

    /**
     * Sets the input object to be used in migration object
     *
     * @param \Symfony\Component\Console\Input\InputInterface $input
     * @return \Phinx\Migration\MigrationInterface
     */
    public function setInput(InputInterface $input);

    /**
     * Gets the input object to be used in migration object
     *
     * @return \Symfony\Component\Console\Input\InputInterface
     */
    public function getInput();

    /**
     * Sets the output object to be used in migration object
     *
     * @param \Symfony\Component\Console\Output\OutputInterface $output
     * @return \Phinx\Migration\MigrationInterface
     */
    public function setOutput(OutputInterface $output);

    /**
     * Gets the output object to be used in migration object
     *
     * @return \Symfony\Component\Console\Output\OutputInterface
     */
    public function getOutput();

    /**
     * Gets the name.
     *
     * @return string
     */
    public function getName();

    /**
     * Sets the migration version number.
     *
     * @param float $version Version
     * @return \Phinx\Migration\MigrationInterface
     */
    public function setVersion($version);

    /**
     * Gets the migration version number.
     *
     * @return float
     */
    public function getVersion();

    /**
     * Sets whether this migration is being applied or reverted
     *
     * @param bool $isMigratingUp True if the migration is being applied
     * @return \Phinx\Migration\MigrationInterface
     */
    public function setMigratingUp($isMigratingUp);

    /**
     * Gets whether this migration is being applied or reverted.
     * True means that the migration is being applied.
     *
     * @return bool
     */
    public function isMigratingUp();

    /**
     * Executes a SQL statement and returns the number of affected rows.
     *
     * @param string $sql SQL
     * @return int
     */
    public function execute($sql);

    /**
     * Executes a SQL statement and returns the result as an array.
     *
     * @param string $sql SQL
     * @return array
     */
    public function query($sql);

    /**
     * Executes a query and returns only one row as an array.
     *
     * @param string $sql SQL
     * @return array
     */
    public function fetchRow($sql);

    /**
     * Executes a query and returns an array of rows.
     *
     * @param string $sql SQL
     * @return array
     */
    public function fetchAll($sql);

    /**
     * Insert data into a table.
     *
     * @param string $tableName
     * @param array $data
     * @return void
     */
    public function insert($tableName, $data);

    /**
     * Create a new database.
     *
     * @param string $name Database Name
     * @param array $options Options
     * @return void
     */
    public function createDatabase($name, $options);

    /**
     * Drop a database.
     *
     * @param string $name Database Name
     * @return void
     */
    public function dropDatabase($name);

    /**
     * Checks to see if a table exists.
     *
     * @param string $tableName Table Name
     * @return bool
     */
    public function hasTable($tableName);

    /**
     * Returns an instance of the <code>\Table</code> class.
     *
     * You can use this class to create and manipulate tables.
     *
     * @param string $tableName Table Name
     * @param array $options Options
     * @return \Phinx\Db\Table
     */
    public function table($tableName, $options);
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Migration
 */
namespace Phinx\Migration;

use Phinx\Db\Adapter\AdapterInterface;
use Phinx\Db\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Abstract Migration Class.
 *
 * It is expected that the migrations you write extend from this class.
 *
 * This abstract class proxies the various database methods to your specified
 * adapter.
 *
 * @author Rob Morgan <robbym@gmail.com>
 */
abstract class AbstractMigration implements MigrationInterface
{
    /**
     * @var float
     */
    protected $version;

    /**
     * @var \Phinx\Db\Adapter\AdapterInterface
     */
    protected $adapter;

    /**
     * @var \Symfony\Component\Console\Output\OutputInterface
     */
    protected $output;

    /**
     * @var \Symfony\Component\Console\Input\InputInterface
     */
    protected $input;

    /**
     * Whether this migration is being applied or reverted
     *
     * @var bool
     */
    protected $isMigratingUp = true;

    /**
     * Class Constructor.
     *
     * @param int $version Migration Version
     * @param \Symfony\Component\Console\Input\InputInterface|null $input
     * @param \Symfony\Component\Console\Output\OutputInterface|null $output
     */
    final public function __construct($version, InputInterface $input = null, OutputInterface $output = null)
    {
        $this->version = $version;
        if (!is_null($input)) {
            $this->setInput($input);
        }
        if (!is_null($output)) {
            $this->setOutput($output);
        }

        $this->init();
    }

    /**
     * Initialize method.
     *
     * @return void
     */
    protected function init()
    {
    }

    /**
     * {@inheritdoc}
     */
    public function up()
    {
    }

    /**
     * {@inheritdoc}
     */
    public function down()
    {
    }

    /**
     * {@inheritdoc}
     */
    public function setAdapter(AdapterInterface $adapter)
    {
        $this->adapter = $adapter;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getAdapter()
    {
        return $this->adapter;
    }

    /**
     * {@inheritdoc}
     */
    public function setInput(InputInterface $input)
    {
        $this->input = $input;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getInput()
    {
        return $this->input;
    }

    /**
     * {@inheritdoc}
     */
    public function setOutput(OutputInterface $output)
    {
        $this->output = $output;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getOutput()
    {
        return $this->output;
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return get_class($this);
    }

    /**
     * {@inheritdoc}
     */
    public function setVersion($version)
    {
        $this->version = $version;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getVersion()
    {
        return $this->version;
    }

    /**
     * {@inheritdoc}
     */
    public function setMigratingUp($isMigratingUp)
    {
        $this->isMigratingUp = $isMigratingUp;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function isMigratingUp()
    {
        return $this->isMigratingUp;
    }

    /**
     * {@inheritdoc}
     */
    public function execute($sql)
    {
        return $this->getAdapter()->execute($sql);
    }

    /**
     * {@inheritdoc}
     */
    public function query($sql)
    {
        return $this->getAdapter()->query($sql);
    }

    /**
     * {@inheritdoc}
     */
    public function fetchRow($sql)
    {
        return $this->getAdapter()->fetchRow($sql);
    }

    /**
     * {@inheritdoc}
     */
    public function fetchAll($sql)
    {
        return $this->getAdapter()->fetchAll($sql);
    }

    /**
     * {@inheritdoc}
     */
    public function insert($table, $data)
    {
        // convert to table object
        if (is_string($table)) {
            $table = new Table($table, [], $this->getAdapter());
        }
        $table->insert($data)->save();
    }

    /**
     * {@inheritdoc}
     */
    public function createDatabase($name, $options)
    {
        $this->getAdapter()->createDatabase($name, $options);
    }

    /**
     * {@inheritdoc}
     */
    public function dropDatabase($name)
    {
        $this->getAdapter()->dropDatabase($name);
    }

    /**
     * {@inheritdoc}
     */
    public function hasTable($tableName)
    {
        return $this->getAdapter()->hasTable($tableName);
    }

    /**
     * {@inheritdoc}
     */
    public function table($tableName, $options = [])
    {
        return new Table($tableName, $options, $this->getAdapter());
    }

    /**
     * A short-hand method to drop the given database table.
     *
     * @param string $tableName Table Name
     * @return void
     */
    public function dropTable($tableName)
    {
        $this->table($tableName)->drop();
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Console
 */
namespace Phinx\Console;

use Phinx\Console\Command;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Phinx console application.
 *
 * @author Rob Morgan <robbym@gmail.com>
 */
class PhinxApplication extends Application
{
    /**
     * Class Constructor.
     *
     * Initialize the Phinx console application.
     *
     * @param string $version The Application Version
     */
    public function __construct()
    {
        $composerConfig = json_decode(file_get_contents(__DIR__ . '/../../../composer.json'));
        $version = $composerConfig->version;

        parent::__construct('Phinx by CakePHP - https://phinx.org.', $version);

        $this->addCommands([
            new Command\Init(),
            new Command\Create(),
            new Command\Migrate(),
            new Command\Rollback(),
            new Command\Status(),
            new Command\Breakpoint(),
            new Command\Test(),
            new Command\SeedCreate(),
            new Command\SeedRun(),
        ]);
    }

    /**
     * Runs the current application.
     *
     * @param \Symfony\Component\Console\Input\InputInterface $input An Input instance
     * @param \Symfony\Component\Console\Output\OutputInterface $output An Output instance
     * @return int 0 if everything went fine, or an error code
     */
    public function doRun(InputInterface $input, OutputInterface $output)
    {
        // always show the version information except when the user invokes the help
        // command as that already does it
        if ($input->hasParameterOption(['--help', '-h']) === false && $input->getFirstArgument() !== null) {
            $output->writeln($this->getLongVersion());
            $output->writeln('');
        }

        return parent::doRun($input, $output);
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Console
 */
namespace Phinx\Console\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class Init extends Command
{
    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this->setName('init')
            ->setDescription('Initialize the application for Phinx')
            ->addArgument('path', InputArgument::OPTIONAL, 'Which path should we initialize for Phinx?')
            ->setHelp(sprintf(
                '%sInitializes the application for Phinx%s',
                PHP_EOL,
                PHP_EOL
            ));
    }

    /**
     * Initializes the application.
     *
     * @param \Symfony\Component\Console\Input\InputInterface $input
     * @param \Symfony\Component\Console\Output\OutputInterface $output
     * @throws \RuntimeException
     * @throws \InvalidArgumentException
     * @return void
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        // get the migration path from the config
        $path = $input->getArgument('path');

        if ($path === null) {
            $path = getcwd();
        }

        $path = realpath($path);

        if (!is_writable($path)) {
            throw new \InvalidArgumentException(sprintf(
                'The directory "%s" is not writable',
                $path
            ));
        }

        // Compute the file path
        $fileName = 'phinx.yml'; // TODO - maybe in the future we allow custom config names.
        $filePath = $path . DIRECTORY_SEPARATOR . $fileName;

        if (file_exists($filePath)) {
            throw new \InvalidArgumentException(sprintf(
                'The file "%s" already exists',
                $filePath
            ));
        }

        // load the config template
        if (is_dir(__DIR__ . '/../../../data/Phinx')) {
            $contents = file_get_contents(__DIR__ . '/../../../data/Phinx/phinx.yml');
        } else {
            $contents = file_get_contents(__DIR__ . '/../../../../phinx.yml');
        }

        if (file_put_contents($filePath, $contents) === false) {
            throw new \RuntimeException(sprintf(
                'The file "%s" could not be written to',
                $path
            ));
        }

        $output->writeln('<info>created</info> .' . str_replace(getcwd(), '', $filePath));
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Console
 */
namespace Phinx\Console\Command;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class SeedRun extends AbstractCommand
{
    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        parent::configure();

        $this->addOption('--environment', '-e', InputOption::VALUE_REQUIRED, 'The target environment');

        $this->setName('seed:run')
            ->setDescription('Run database seeders')
            ->addOption('--seed', '-s', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'What is the name of the seeder?')
            ->setHelp(
                <<<EOT
The <info>seed:run</info> command runs all available or individual seeders

<info>phinx seed:run -e development</info>
<info>phinx seed:run -e development -s UserSeeder</info>
<info>phinx seed:run -e development -s UserSeeder -s PermissionSeeder -s LogSeeder</info>
<info>phinx seed:run -e development -v</info>

EOT
            );
    }

    /**
     * Run database seeders.
     *
     * @param \Symfony\Component\Console\Input\InputInterface $input
     * @param \Symfony\Component\Console\Output\OutputInterface $output
     * @return void
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $this->bootstrap($input, $output);

        $seedSet = $input->getOption('seed');
        $environment = $input->getOption('environment');

        if ($environment === null) {
            $environment = $this->getConfig()->getDefaultEnvironment();
            $output->writeln('<comment>warning</comment> no environment specified, defaulting to: ' . $environment);
        } else {
            $output->writeln('<info>using environment</info> ' . $environment);
        }

        $envOptions = $this->getConfig()->getEnvironment($environment);
        if (isset($envOptions['adapter'])) {
            $output->writeln('<info>using adapter</info> ' . $envOptions['adapter']);
        }

        if (isset($envOptions['wrapper'])) {
            $output->writeln('<info>using wrapper</info> ' . $envOptions['wrapper']);
        }

        if (isset($envOptions['name'])) {
            $output->writeln('<info>using database</info> ' . $envOptions['name']);
        } else {
            $output->writeln('<error>Could not determine database name! Please specify a database name in your config file.</error>');

            return;
        }

        if (isset($envOptions['table_prefix'])) {
            $output->writeln('<info>using table prefix</info> ' . $envOptions['table_prefix']);
        }
        if (isset($envOptions['table_suffix'])) {
            $output->writeln('<info>using table suffix</info> ' . $envOptions['table_suffix']);
        }

        $start = microtime(true);

        if (empty($seedSet)) {
            // run all the seed(ers)
            $this->getManager()->seed($environment);
        } else {
            // run seed(ers) specified in a comma-separated list of classes
            foreach ($seedSet as $seed) {
                $this->getManager()->seed($environment, trim($seed));
            }
        }

        $end = microtime(true);

        $output->writeln('');
        $output->writeln('<comment>All Done. Took ' . sprintf('%.4fs', $end - $start) . '</comment>');
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Console
 */
namespace Phinx\Console\Command;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class Migrate extends AbstractCommand
{
    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        parent::configure();

        $this->addOption('--environment', '-e', InputOption::VALUE_REQUIRED, 'The target environment');

        $this->setName('migrate')
            ->setDescription('Migrate the database')
            ->addOption('--target', '-t', InputOption::VALUE_REQUIRED, 'The version number to migrate to')
            ->addOption('--date', '-d', InputOption::VALUE_REQUIRED, 'The date to migrate to')
            ->addOption('--dry-run', '-x', InputOption::VALUE_NONE, 'Dump query to standard output instead of executing it')
            ->setHelp(
                <<<EOT
The <info>migrate</info> command runs all available migrations, optionally up to a specific version

<info>phinx migrate -e development</info>
<info>phinx migrate -e development -t 20110103081132</info>
<info>phinx migrate -e development -d 20110103</info>
<info>phinx migrate -e development -v</info>

EOT
            );
    }

    /**
     * Migrate the database.
     *
     * @param \Symfony\Component\Console\Input\InputInterface $input
     * @param \Symfony\Component\Console\Output\OutputInterface $output
     * @return int integer 0 on success, or an error code.
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $this->bootstrap($input, $output);

        $version = $input->getOption('target');
        $environment = $input->getOption('environment');
        $date = $input->getOption('date');

        if ($environment === null) {
            $environment = $this->getConfig()->getDefaultEnvironment();
            $output->writeln('<comment>warning</comment> no environment specified, defaulting to: ' . $environment);
        } else {
            $output->writeln('<info>using environment</info> ' . $environment);
        }

        $envOptions = $this->getConfig()->getEnvironment($environment);
        if (isset($envOptions['adapter'])) {
            $output->writeln('<info>using adapter</info> ' . $envOptions['adapter']);
        }

        if (isset($envOptions['wrapper'])) {
            $output->writeln('<info>using wrapper</info> ' . $envOptions['wrapper']);
        }

        if (isset($envOptions['name'])) {
            $output->writeln('<info>using database</info> ' . $envOptions['name']);
        } else {
            $output->writeln('<error>Could not determine database name! Please specify a database name in your config file.</error>');

            return 1;
        }

        if (isset($envOptions['table_prefix'])) {
            $output->writeln('<info>using table prefix</info> ' . $envOptions['table_prefix']);
        }
        if (isset($envOptions['table_suffix'])) {
            $output->writeln('<info>using table suffix</info> ' . $envOptions['table_suffix']);
        }

        // run the migrations
        $start = microtime(true);
        if ($date !== null) {
            $this->getManager()->migrateToDateTime($environment, new \DateTime($date));
        } else {
            $this->getManager()->migrate($environment, $version);
        }
        $end = microtime(true);

        $output->writeln('');
        $output->writeln('<comment>All Done. Took ' . sprintf('%.4fs', $end - $start) . '</comment>');

        return 0;
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Console
 */
namespace Phinx\Console\Command;

use Phinx\Config\NamespaceAwareInterface;
use Phinx\Util\Util;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;

class Create extends AbstractCommand
{
    /**
     * The name of the interface that any external template creation class is required to implement.
     */
    const CREATION_INTERFACE = 'Phinx\Migration\CreationInterface';

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        parent::configure();

        $this->setName('create')
            ->setDescription('Create a new migration')
            ->addArgument('name', InputArgument::REQUIRED, 'What is the name of the migration (in CamelCase)?')
            ->setHelp(sprintf(
                '%sCreates a new database migration%s',
                PHP_EOL,
                PHP_EOL
            ));

        // An alternative template.
        $this->addOption('template', 't', InputOption::VALUE_REQUIRED, 'Use an alternative template');

        // A classname to be used to gain access to the template content as well as the ability to
        // have a callback once the migration file has been created.
        $this->addOption('class', 'l', InputOption::VALUE_REQUIRED, 'Use a class implementing "' . self::CREATION_INTERFACE . '" to generate the template');

        // Allow the migration path to be chosen non-interactively.
        $this->addOption('path', null, InputOption::VALUE_REQUIRED, 'Specify the path in which to create this migration');
    }

    /**
     * Get the confirmation question asking if the user wants to create the
     * migrations directory.
     *
     * @return \Symfony\Component\Console\Question\ConfirmationQuestion
     */
    protected function getCreateMigrationDirectoryQuestion()
    {
        return new ConfirmationQuestion('Create migrations directory? [y]/n ', true);
    }

    /**
     * Get the question that allows the user to select which migration path to use.
     *
     * @param string[] $paths
     * @return \Symfony\Component\Console\Question\ChoiceQuestion
     */
    protected function getSelectMigrationPathQuestion(array $paths)
    {
        return new ChoiceQuestion('Which migrations path would you like to use?', $paths, 0);
    }

    /**
     * Returns the migration path to create the migration in.
     *
     * @param \Symfony\Component\Console\Input\InputInterface $input
     * @param \Symfony\Component\Console\Output\OutputInterface $output
     * @return mixed
     * @throws \Exception
     */
    protected function getMigrationPath(InputInterface $input, OutputInterface $output)
    {
        // First, try the non-interactive option:
        $path = $input->getOption('path');

        if (!empty($path)) {
            return $path;
        }

        $paths = $this->getConfig()->getMigrationPaths();

        // No paths? That's a problem.
        if (empty($paths)) {
            throw new \Exception('No migration paths set in your Phinx configuration file.');
        }

        $paths = Util::globAll($paths);

        if (empty($paths)) {
            throw new \Exception(
                'You probably used curly braces to define migration path in your Phinx configuration file, ' .
                'but no directories have been matched using this pattern. ' .
                'You need to create a migration directory manually.'
            );
        }

        // Only one path set, so select that:
        if (1 === count($paths)) {
            return array_shift($paths);
        }

        // Ask the user which of their defined paths they'd like to use:
        $helper = $this->getHelper('question');
        $question = $this->getSelectMigrationPathQuestion($paths);

        return $helper->ask($input, $output, $question);
    }

    /**
     * Create the new migration.
     *
     * @param \Symfony\Component\Console\Input\InputInterface $input
     * @param \Symfony\Component\Console\Output\OutputInterface $output
     * @throws \RuntimeException
     * @throws \InvalidArgumentException
     * @return void
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $this->bootstrap($input, $output);

        // get the migration path from the config
        $path = $this->getMigrationPath($input, $output);

        if (!file_exists($path)) {
            $helper = $this->getHelper('question');
            $question = $this->getCreateMigrationDirectoryQuestion();

            if ($helper->ask($input, $output, $question)) {
                mkdir($path, 0755, true);
            }
        }

        $this->verifyMigrationDirectory($path);

        $config = $this->getConfig();
        $namespace = $config instanceof NamespaceAwareInterface ? $config->getMigrationNamespaceByPath($path) : null;

        $path = realpath($path);
        $className = $input->getArgument('name');

        if (!Util::isValidPhinxClassName($className)) {
            throw new \InvalidArgumentException(sprintf(
                'The migration class name "%s" is invalid. Please use CamelCase format.',
                $className
            ));
        }

        if (!Util::isUniqueMigrationClassName($className, $path)) {
            throw new \InvalidArgumentException(sprintf(
                'The migration class name "%s%s" already exists',
                $namespace ? ($namespace . '\\') : '',
                $className
            ));
        }

        // Compute the file path
        $fileName = Util::mapClassNameToFileName($className);
        $filePath = $path . DIRECTORY_SEPARATOR . $fileName;

        if (is_file($filePath)) {
            throw new \InvalidArgumentException(sprintf(
                'The file "%s" already exists',
                $filePath
            ));
        }

        // Get the alternative template and static class options from the config, but only allow one of them.
        $defaultAltTemplate = $this->getConfig()->getTemplateFile();
        $defaultCreationClassName = $this->getConfig()->getTemplateClass();
        if ($defaultAltTemplate && $defaultCreationClassName) {
            throw new \InvalidArgumentException('Cannot define template:class and template:file at the same time');
        }

        // Get the alternative template and static class options from the command line, but only allow one of them.
        $altTemplate = $input->getOption('template');
        $creationClassName = $input->getOption('class');
        if ($altTemplate && $creationClassName) {
            throw new \InvalidArgumentException('Cannot use --template and --class at the same time');
        }

        // If no commandline options then use the defaults.
        if (!$altTemplate && !$creationClassName) {
            $altTemplate = $defaultAltTemplate;
            $creationClassName = $defaultCreationClassName;
        }

        // Verify the alternative template file's existence.
        if ($altTemplate && !is_file($altTemplate)) {
            throw new \InvalidArgumentException(sprintf(
                'The alternative template file "%s" does not exist',
                $altTemplate
            ));
        }

        // Verify that the template creation class (or the aliased class) exists and that it implements the required interface.
        $aliasedClassName = null;
        if ($creationClassName) {
            // Supplied class does not exist, is it aliased?
            if (!class_exists($creationClassName)) {
                $aliasedClassName = $this->getConfig()->getAlias($creationClassName);
                if ($aliasedClassName && !class_exists($aliasedClassName)) {
                    throw new \InvalidArgumentException(sprintf(
                        'The class "%s" via the alias "%s" does not exist',
                        $aliasedClassName,
                        $creationClassName
                    ));
                } elseif (!$aliasedClassName) {
                    throw new \InvalidArgumentException(sprintf(
                        'The class "%s" does not exist',
                        $creationClassName
                    ));
                }
            }

            // Does the class implement the required interface?
            if (!$aliasedClassName && !is_subclass_of($creationClassName, self::CREATION_INTERFACE)) {
                throw new \InvalidArgumentException(sprintf(
                    'The class "%s" does not implement the required interface "%s"',
                    $creationClassName,
                    self::CREATION_INTERFACE
                ));
            } elseif ($aliasedClassName && !is_subclass_of($aliasedClassName, self::CREATION_INTERFACE)) {
                throw new \InvalidArgumentException(sprintf(
                    'The class "%s" via the alias "%s" does not implement the required interface "%s"',
                    $aliasedClassName,
                    $creationClassName,
                    self::CREATION_INTERFACE
                ));
            }
        }

        // Use the aliased class.
        $creationClassName = $aliasedClassName ?: $creationClassName;

        // Determine the appropriate mechanism to get the template
        if ($creationClassName) {
            // Get the template from the creation class
            $creationClass = new $creationClassName($input, $output);
            $contents = $creationClass->getMigrationTemplate();
        } else {
            // Load the alternative template if it is defined.
            $contents = file_get_contents($altTemplate ?: $this->getMigrationTemplateFilename());
        }

        // inject the class names appropriate to this migration
        $classes = [
            '$namespaceDefinition' => $namespace !== null ? ('namespace ' . $namespace . ';') : '',
            '$namespace' => $namespace,
            '$useClassName' => $this->getConfig()->getMigrationBaseClassName(false),
            '$className' => $className,
            '$version' => Util::getVersionFromFileName($fileName),
            '$baseClassName' => $this->getConfig()->getMigrationBaseClassName(true),
        ];
        $contents = strtr($contents, $classes);

        if (file_put_contents($filePath, $contents) === false) {
            throw new \RuntimeException(sprintf(
                'The file "%s" could not be written to',
                $path
            ));
        }

        // Do we need to do the post creation call to the creation class?
        if (isset($creationClass)) {
            $creationClass->postMigrationCreation($filePath, $className, $this->getConfig()->getMigrationBaseClassName());
        }

        $output->writeln('<info>using migration base class</info> ' . $classes['$useClassName']);

        if (!empty($altTemplate)) {
            $output->writeln('<info>using alternative template</info> ' . $altTemplate);
        } elseif (!empty($creationClassName)) {
            $output->writeln('<info>using template creation class</info> ' . $creationClassName);
        } else {
            $output->writeln('<info>using default template</info>');
        }

        $output->writeln('<info>created</info> ' . str_replace(getcwd() . DIRECTORY_SEPARATOR, '', $filePath));
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Console
 */
namespace Phinx\Console\Command;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class Rollback extends AbstractCommand
{
    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        parent::configure();

        $this->addOption('--environment', '-e', InputOption::VALUE_REQUIRED, 'The target environment');

        $this->setName('rollback')
            ->setDescription('Rollback the last or to a specific migration')
            ->addOption('--target', '-t', InputOption::VALUE_REQUIRED, 'The version number to rollback to')
            ->addOption('--date', '-d', InputOption::VALUE_REQUIRED, 'The date to rollback to')
            ->addOption('--force', '-f', InputOption::VALUE_NONE, 'Force rollback to ignore breakpoints')
            ->addOption('--dry-run', '-x', InputOption::VALUE_NONE, 'Dump query to standard output instead of executing it')
            ->setHelp(
                <<<EOT
The <info>rollback</info> command reverts the last migration, or optionally up to a specific version

<info>phinx rollback -e development</info>
<info>phinx rollback -e development -t 20111018185412</info>
<info>phinx rollback -e development -d 20111018</info>
<info>phinx rollback -e development -v</info>
<info>phinx rollback -e development -t 20111018185412 -f</info>

If you have a breakpoint set, then you can rollback to target 0 and the rollbacks will stop at the breakpoint.
<info>phinx rollback -e development -t 0 </info>

The <info>version_order</info> configuration option is used to determine the order of the migrations when rolling back.
This can be used to allow the rolling back of the last executed migration instead of the last created one, or combined
with the <info>-d|--date</info> option to rollback to a certain date using the migration start times to order them.

EOT
            );
    }

    /**
     * Rollback the migration.
     *
     * @param \Symfony\Component\Console\Input\InputInterface $input
     * @param \Symfony\Component\Console\Output\OutputInterface $output
     * @return void
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $this->bootstrap($input, $output);

        $environment = $input->getOption('environment');
        $version = $input->getOption('target');
        $date = $input->getOption('date');
        $force = (bool)$input->getOption('force');

        $config = $this->getConfig();

        if ($environment === null) {
            $environment = $config->getDefaultEnvironment();
            $output->writeln('<comment>warning</comment> no environment specified, defaulting to: ' . $environment);
        } else {
            $output->writeln('<info>using environment</info> ' . $environment);
        }

        $envOptions = $config->getEnvironment($environment);
        if (isset($envOptions['adapter'])) {
            $output->writeln('<info>using adapter</info> ' . $envOptions['adapter']);
        }

        if (isset($envOptions['wrapper'])) {
            $output->writeln('<info>using wrapper</info> ' . $envOptions['wrapper']);
        }

        if (isset($envOptions['name'])) {
            $output->writeln('<info>using database</info> ' . $envOptions['name']);
        }

        $versionOrder = $this->getConfig()->getVersionOrder();
        $output->writeln('<info>ordering by </info>' . $versionOrder . " time");

        // rollback the specified environment
        if ($date === null) {
            $targetMustMatchVersion = true;
            $target = $version;
        } else {
            $targetMustMatchVersion = false;
            $target = $this->getTargetFromDate($date);
        }

        $start = microtime(true);
        $this->getManager()->rollback($environment, $target, $force, $targetMustMatchVersion);
        $end = microtime(true);

        $output->writeln('');
        $output->writeln('<comment>All Done. Took ' . sprintf('%.4fs', $end - $start) . '</comment>');
    }

    /**
     * Get Target from Date
     *
     * @param string $date The date to convert to a target.
     * @return string The target
     */
    public function getTargetFromDate($date)
    {
        if (!preg_match('/^\d{4,14}$/', $date)) {
            throw new \InvalidArgumentException('Invalid date. Format is YYYY[MM[DD[HH[II[SS]]]]].');
        }

        // what we need to append to the date according to the possible date string lengths
        $dateStrlenToAppend = [
            14 => '',
            12 => '00',
            10 => '0000',
            8 => '000000',
            6 => '01000000',
            4 => '0101000000',
        ];

        if (!isset($dateStrlenToAppend[strlen($date)])) {
            throw new \InvalidArgumentException('Invalid date. Format is YYYY[MM[DD[HH[II[SS]]]]].');
        }

        $target = $date . $dateStrlenToAppend[strlen($date)];

        $dateTime = \DateTime::createFromFormat('YmdHis', $target);

        if ($dateTime === false) {
            throw new \InvalidArgumentException('Invalid date. Format is YYYY[MM[DD[HH[II[SS]]]]].');
        }

        return $dateTime->format('YmdHis');
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Console
 */
namespace Phinx\Console\Command;

use Phinx\Migration\Manager\Environment;
use Phinx\Util\Util;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Leonid Kuzmin <lndkuzmin@gmail.com>
 */
class Test extends AbstractCommand
{
    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        parent::configure();

        $this->addOption('--environment', '-e', InputOption::VALUE_REQUIRED, 'The target environment');

        $this->setName('test')
            ->setDescription('Verify the configuration file')
            ->setHelp(
                <<<EOT
The <info>test</info> command verifies the YAML configuration file and optionally an environment

<info>phinx test</info>
<info>phinx test -e development</info>

EOT
            );
    }

    /**
     * Verify configuration file
     *
     * @param \Symfony\Component\Console\Input\InputInterface $input
     * @param \Symfony\Component\Console\Output\OutputInterface $output
     * @throws \RuntimeException
     * @throws \InvalidArgumentException
     * @return void
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $this->loadConfig($input, $output);
        $this->loadManager($input, $output);

        // Verify the migrations path(s)
        array_map(
            [$this, 'verifyMigrationDirectory'],
            Util::globAll($this->getConfig()->getMigrationPaths())
        );

        // Verify the seed path(s)
        array_map(
            [$this, 'verifySeedDirectory'],
            Util::globAll($this->getConfig()->getSeedPaths())
        );

        $envName = $input->getOption('environment');
        if ($envName) {
            if (!$this->getConfig()->hasEnvironment($envName)) {
                throw new \InvalidArgumentException(sprintf(
                    'The environment "%s" does not exist',
                    $envName
                ));
            }

            $output->writeln(sprintf('<info>validating environment</info> %s', $envName));
            $environment = new Environment(
                $envName,
                $this->getConfig()->getEnvironment($envName)
            );
            // validate environment connection
            $environment->getAdapter()->connect();
        }

        $output->writeln('<info>success!</info>');
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Console
 */
namespace Phinx\Console\Command;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class Status extends AbstractCommand
{
    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        parent::configure();

        $this->addOption('--environment', '-e', InputOption::VALUE_REQUIRED, 'The target environment.');

        $this->setName('status')
            ->setDescription('Show migration status')
            ->addOption('--format', '-f', InputOption::VALUE_REQUIRED, 'The output format: text or json. Defaults to text.')
            ->setHelp(
                <<<EOT
The <info>status</info> command prints a list of all migrations, along with their current status

<info>phinx status -e development</info>
<info>phinx status -e development -f json</info>

The <info>version_order</info> configuration option is used to determine the order of the status migrations.
EOT
            );
    }

    /**
     * Show the migration status.
     *
     * @param \Symfony\Component\Console\Input\InputInterface $input
     * @param \Symfony\Component\Console\Output\OutputInterface $output
     * @return int 0 if all migrations are up, or an error code
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $this->bootstrap($input, $output);

        $environment = $input->getOption('environment');
        $format = $input->getOption('format');

        if ($environment === null) {
            $environment = $this->getConfig()->getDefaultEnvironment();
            $output->writeln('<comment>warning</comment> no environment specified, defaulting to: ' . $environment);
        } else {
            $output->writeln('<info>using environment</info> ' . $environment);
        }
        if ($format !== null) {
            $output->writeln('<info>using format</info> ' . $format);
        }

        $output->writeln('<info>ordering by </info>' . $this->getConfig()->getVersionOrder() . " time");

        // print the status
        return $this->getManager()->printStatus($environment, $format);
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @author     Richard Quadling
 * @package    Phinx
 * @subpackage Phinx\Console
 */
namespace Phinx\Console\Command;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class Breakpoint extends AbstractCommand
{
    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        parent::configure();

        $this->addOption('--environment', '-e', InputOption::VALUE_REQUIRED, 'The target environment.');

        $this->setName('breakpoint')
            ->setDescription('Manage breakpoints')
            ->addOption('--target', '-t', InputOption::VALUE_REQUIRED, 'The version number to set or clear a breakpoint against')
            ->addOption('--remove-all', '-r', InputOption::VALUE_NONE, 'Remove all breakpoints')
            ->setHelp(
                <<<EOT
The <info>breakpoint</info> command allows you to set or clear a breakpoint against a specific target to inhibit rollbacks beyond a certain target.
If no target is supplied then the most recent migration will be used.
You cannot specify un-migrated targets

<info>phinx breakpoint -e development</info>
<info>phinx breakpoint -e development -t 20110103081132</info>
<info>phinx breakpoint -e development -r</info>
EOT
            );
    }

    /**
     * Toggle the breakpoint.
     *
     * @param \Symfony\Component\Console\Input\InputInterface $input
     * @param \Symfony\Component\Console\Output\OutputInterface $output
     * @return void
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $this->bootstrap($input, $output);

        $environment = $input->getOption('environment');
        $version = $input->getOption('target');
        $removeAll = $input->getOption('remove-all');

        if ($environment === null) {
            $environment = $this->getConfig()->getDefaultEnvironment();
            $output->writeln('<comment>warning</comment> no environment specified, defaulting to: ' . $environment);
        } else {
            $output->writeln('<info>using environment</info> ' . $environment);
        }

        if ($version && $removeAll) {
            throw new \InvalidArgumentException('Cannot toggle a breakpoint and remove all breakpoints at the same time.');
        }

        // Remove all breakpoints
        if ($removeAll) {
            $this->getManager()->removeBreakpoints($environment);
        } else {
            // Toggle the breakpoint.
            $this->getManager()->toggleBreakpoint($environment, $version);
        }
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Console
 */
namespace Phinx\Console\Command;

use Phinx\Config\Config;
use Phinx\Config\ConfigInterface;
use Phinx\Db\Adapter\AdapterInterface;
use Phinx\Migration\Manager;
use Phinx\Util\Util;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Abstract command, contains bootstrapping info
 *
 * @author Rob Morgan <robbym@gmail.com>
 */
abstract class AbstractCommand extends Command
{
    /**
     * The location of the default migration template.
     */
    const DEFAULT_MIGRATION_TEMPLATE = '/../../Migration/Migration.template.php.dist';

    /**
     * The location of the default seed template.
     */
    const DEFAULT_SEED_TEMPLATE = '/../../Seed/Seed.template.php.dist';

    /**
     * @var \Phinx\Config\ConfigInterface
     */
    protected $config;

    /**
     * @var \Phinx\Db\Adapter\AdapterInterface
     */
    protected $adapter;

    /**
     * @var \Phinx\Migration\Manager
     */
    protected $manager;

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this->addOption('--configuration', '-c', InputOption::VALUE_REQUIRED, 'The configuration file to load');
        $this->addOption('--parser', '-p', InputOption::VALUE_REQUIRED, 'Parser used to read the config file. Defaults to YAML');
    }

    /**
     * Bootstrap Phinx.
     *
     * @param \Symfony\Component\Console\Input\InputInterface $input
     * @param \Symfony\Component\Console\Output\OutputInterface $output
     * @return void
     */
    public function bootstrap(InputInterface $input, OutputInterface $output)
    {
        if (!$this->getConfig()) {
            $this->loadConfig($input, $output);
        }

        $this->loadManager($input, $output);

        // report the paths
        $paths = $this->getConfig()->getMigrationPaths();

        $output->writeln('<info>using migration paths</info> ');

        foreach (Util::globAll($paths) as $path) {
            $output->writeln('<info> - ' . realpath($path) . '</info>');
        }

        try {
            $paths = $this->getConfig()->getSeedPaths();

            $output->writeln('<info>using seed paths</info> ');

            foreach (Util::globAll($paths) as $path) {
                $output->writeln('<info> - ' . realpath($path) . '</info>');
            }
        } catch (\UnexpectedValueException $e) {
            // do nothing as seeds are optional
        }
    }

    /**
     * Sets the config.
     *
     * @param  \Phinx\Config\ConfigInterface $config
     * @return \Phinx\Console\Command\AbstractCommand
     */
    public function setConfig(ConfigInterface $config)
    {
        $this->config = $config;

        return $this;
    }

    /**
     * Gets the config.
     *
     * @return \Phinx\Config\ConfigInterface
     */
    public function getConfig()
    {
        return $this->config;
    }

    /**
     * Sets the database adapter.
     *
     * @param \Phinx\Db\Adapter\AdapterInterface $adapter
     * @return \Phinx\Console\Command\AbstractCommand
     */
    public function setAdapter(AdapterInterface $adapter)
    {
        $this->adapter = $adapter;

        return $this;
    }

    /**
     * Gets the database adapter.
     *
     * @return \Phinx\Db\Adapter\AdapterInterface
     */
    public function getAdapter()
    {
        return $this->adapter;
    }

    /**
     * Sets the migration manager.
     *
     * @param \Phinx\Migration\Manager $manager
     * @return \Phinx\Console\Command\AbstractCommand
     */
    public function setManager(Manager $manager)
    {
        $this->manager = $manager;

        return $this;
    }

    /**
     * Gets the migration manager.
     *
     * @return \Phinx\Migration\Manager
     */
    public function getManager()
    {
        return $this->manager;
    }

    /**
     * Returns config file path
     *
     * @param \Symfony\Component\Console\Input\InputInterface $input
     * @return string
     */
    protected function locateConfigFile(InputInterface $input)
    {
        $configFile = $input->getOption('configuration');

        $useDefault = false;

        if ($configFile === null || $configFile === false) {
            $useDefault = true;
        }

        $cwd = getcwd();

        // locate the phinx config file (default: phinx.yml)
        // TODO - In future walk the tree in reverse (max 10 levels)
        $locator = new FileLocator([
            $cwd . DIRECTORY_SEPARATOR
        ]);

        if (!$useDefault) {
            // Locate() throws an exception if the file does not exist
            return $locator->locate($configFile, $cwd, $first = true);
        }

        $possibleConfigFiles = ['phinx.php', 'phinx.json', 'phinx.yml'];
        foreach ($possibleConfigFiles as $configFile) {
            try {
                return $locator->locate($configFile, $cwd, $first = true);
            } catch (\InvalidArgumentException $exception) {
                $lastException = $exception;
            }
        }
        throw $lastException;
    }

    /**
     * Parse the config file and load it into the config object
     *
     * @param \Symfony\Component\Console\Input\InputInterface $input
     * @param \Symfony\Component\Console\Output\OutputInterface $output
     * @throws \InvalidArgumentException
     * @return void
     */
    protected function loadConfig(InputInterface $input, OutputInterface $output)
    {
        $configFilePath = $this->locateConfigFile($input);
        $output->writeln('<info>using config file</info> .' . str_replace(getcwd(), '', realpath($configFilePath)));

        $parser = $input->getOption('parser');

        // If no parser is specified try to determine the correct one from the file extension.  Defaults to YAML
        if ($parser === null) {
            $extension = pathinfo($configFilePath, PATHINFO_EXTENSION);

            switch (strtolower($extension)) {
                case 'json':
                    $parser = 'json';
                    break;
                case 'php':
                    $parser = 'php';
                    break;
                case 'yml':
                default:
                    $parser = 'yaml';
            }
        }

        switch (strtolower($parser)) {
            case 'json':
                $config = Config::fromJson($configFilePath);
                break;
            case 'php':
                $config = Config::fromPhp($configFilePath);
                break;
            case 'yaml':
                $config = Config::fromYaml($configFilePath);
                break;
            default:
                throw new \InvalidArgumentException(sprintf('\'%s\' is not a valid parser.', $parser));
        }

        $output->writeln('<info>using config parser</info> ' . $parser);

        $this->setConfig($config);
    }

    /**
     * Load the migrations manager and inject the config
     *
     * @param \Symfony\Component\Console\Input\InputInterface $input
     * @param \Symfony\Component\Console\Output\OutputInterface $output
     */
    protected function loadManager(InputInterface $input, OutputInterface $output)
    {
        if ($this->getManager() === null) {
            $manager = new Manager($this->getConfig(), $input, $output);
            $this->setManager($manager);
        } else {
            $manager = $this->getManager();
            $manager->setInput($input);
            $manager->setOutput($output);
        }
    }

    /**
     * Verify that the migration directory exists and is writable.
     *
     * @param string $path
     * @throws \InvalidArgumentException
     * @return void
     */
    protected function verifyMigrationDirectory($path)
    {
        if (!is_dir($path)) {
            throw new \InvalidArgumentException(sprintf(
                'Migration directory "%s" does not exist',
                $path
            ));
        }

        if (!is_writable($path)) {
            throw new \InvalidArgumentException(sprintf(
                'Migration directory "%s" is not writable',
                $path
            ));
        }
    }

    /**
     * Verify that the seed directory exists and is writable.
     *
     * @param string $path
     * @throws \InvalidArgumentException
     * @return void
     */
    protected function verifySeedDirectory($path)
    {
        if (!is_dir($path)) {
            throw new \InvalidArgumentException(sprintf(
                'Seed directory "%s" does not exist',
                $path
            ));
        }

        if (!is_writable($path)) {
            throw new \InvalidArgumentException(sprintf(
                'Seed directory "%s" is not writable',
                $path
            ));
        }
    }

    /**
     * Returns the migration template filename.
     *
     * @return string
     */
    protected function getMigrationTemplateFilename()
    {
        return __DIR__ . self::DEFAULT_MIGRATION_TEMPLATE;
    }

    /**
     * Returns the seed template filename.
     *
     * @return string
     */
    protected function getSeedTemplateFilename()
    {
        return __DIR__ . self::DEFAULT_SEED_TEMPLATE;
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @package    Phinx
 * @subpackage Phinx\Console
 */
namespace Phinx\Console\Command;

use Phinx\Config\NamespaceAwareInterface;
use Phinx\Util\Util;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;

class SeedCreate extends AbstractCommand
{
    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        parent::configure();

        $this->setName('seed:create')
            ->setDescription('Create a new database seeder')
            ->addArgument('name', InputArgument::REQUIRED, 'What is the name of the seeder?')
            ->addOption('path', null, InputOption::VALUE_REQUIRED, 'Specify the path in which to create this seeder')
            ->setHelp(sprintf(
                '%sCreates a new database seeder%s',
                PHP_EOL,
                PHP_EOL
            ));
    }

    /**
     * Get the confirmation question asking if the user wants to create the
     * seeds directory.
     *
     * @return \Symfony\Component\Console\Question\ConfirmationQuestion
     */
    protected function getCreateSeedDirectoryQuestion()
    {
        return new ConfirmationQuestion('Create seeds directory? [y]/n ', true);
    }

    /**
     * Get the question that allows the user to select which seed path to use.
     *
     * @param string[] $paths
     * @return \Symfony\Component\Console\Question\ChoiceQuestion
     */
    protected function getSelectSeedPathQuestion(array $paths)
    {
        return new ChoiceQuestion('Which seeds path would you like to use?', $paths, 0);
    }

    /**
     * Returns the seed path to create the seeder in.
     *
     * @param \Symfony\Component\Console\Input\InputInterface $input
     * @param \Symfony\Component\Console\Output\OutputInterface $output
     * @return mixed
     * @throws \Exception
     */
    protected function getSeedPath(InputInterface $input, OutputInterface $output)
    {
        // First, try the non-interactive option:
        $path = $input->getOption('path');

        if (!empty($path)) {
            return $path;
        }

        $paths = $this->getConfig()->getSeedPaths();

        // No paths? That's a problem.
        if (empty($paths)) {
            throw new \Exception('No seed paths set in your Phinx configuration file.');
        }

        $paths = Util::globAll($paths);

        if (empty($paths)) {
            throw new \Exception(
                'You probably used curly braces to define seed path in your Phinx configuration file, ' .
                'but no directories have been matched using this pattern. ' .
                'You need to create a seed directory manually.'
            );
        }

        // Only one path set, so select that:
        if (1 === count($paths)) {
            return array_shift($paths);
        }

        // Ask the user which of their defined paths they'd like to use:
        $helper = $this->getHelper('question');
        $question = $this->getSelectSeedPathQuestion($paths);

        return $helper->ask($input, $output, $question);
    }

    /**
     * Create the new seeder.
     *
     * @param \Symfony\Component\Console\Input\InputInterface $input
     * @param \Symfony\Component\Console\Output\OutputInterface $output
     * @throws \RuntimeException
     * @throws \InvalidArgumentException
     * @return void
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $this->bootstrap($input, $output);

        // get the seed path from the config
        $path = $this->getSeedPath($input, $output);

        if (!file_exists($path)) {
            $helper = $this->getHelper('question');
            $question = $this->getCreateSeedDirectoryQuestion();

            if ($helper->ask($input, $output, $question)) {
                mkdir($path, 0755, true);
            }
        }

        $this->verifySeedDirectory($path);

        $path = realpath($path);
        $className = $input->getArgument('name');

        if (!Util::isValidPhinxClassName($className)) {
            throw new \InvalidArgumentException(sprintf(
                'The seed class name "%s" is invalid. Please use CamelCase format',
                $className
            ));
        }

        // Compute the file path
        $filePath = $path . DIRECTORY_SEPARATOR . $className . '.php';

        if (is_file($filePath)) {
            throw new \InvalidArgumentException(sprintf(
                'The file "%s" already exists',
                basename($filePath)
            ));
        }

        // inject the class names appropriate to this seeder
        $contents = file_get_contents($this->getSeedTemplateFilename());

        $config = $this->getConfig();
        $namespace = $config instanceof NamespaceAwareInterface ? $config->getSeedNamespaceByPath($path) : null;
        $classes = [
            '$namespaceDefinition' => $namespace !== null ? ('namespace ' . $namespace . ';') : '',
            '$namespace' => $namespace,
            '$useClassName' => 'Phinx\Seed\AbstractSeed',
            '$className' => $className,
            '$baseClassName' => 'AbstractSeed',
        ];
        $contents = strtr($contents, $classes);

        if (file_put_contents($filePath, $contents) === false) {
            throw new \RuntimeException(sprintf(
                'The file "%s" could not be written to',
                $path
            ));
        }

        $output->writeln('<info>using seed base class</info> ' . $classes['$useClassName']);
        $output->writeln('<info>created</info> .' . str_replace(getcwd(), '', $filePath));
    }
}
<?php
/**
 * Phinx
 *
 * (The MIT license)
 * Copyright (c) 2015 Rob Morgan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated * documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

/**
 * Attempts to load Composer's autoload.php as either a dependency or a
 * stand-alone package.
 *
 * @return bool
 */
return function () {
    $files = [
      __DIR__ . '/../../../autoload.php', // composer dependency
      __DIR__ . '/../vendor/autoload.php', // stand-alone package
    ];
    foreach ($files as $file) {
        if (is_file($file)) {
            require_once $file;

            return true;
        }
    }

    return false;
};
{
    "name": "phpunit/php-timer",
    "description": "Utility class for timing",
    "type": "library",
    "keywords": [
        "timer"
    ],
    "homepage": "https://github.com/sebastianbergmann/php-timer/",
    "license": "BSD-3-Clause",
    "authors": [
        {
            "name": "Sebastian Bergmann",
            "email": "sb@sebastian-bergmann.de",
            "role": "lead"
        }
    ],
    "support": {
        "issues": "https://github.com/sebastianbergmann/php-timer/issues"
    },
    "require": {
        "php": "^5.3.3 || ^7.0"
    },
    "require-dev": {
        "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
    },
    "autoload": {
        "classmap": [
            "src/"
        ]
    },
    "extra": {
        "branch-alias": {
            "dev-master": "1.0-dev"
        }
    }
}

<?php
/*
 * This file is part of the PHP_Timer package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Utility class for timing.
 */
class PHP_Timer
{
    /**
     * @var array
     */
    private static $times = array(
      'hour'   => 3600000,
      'minute' => 60000,
      'second' => 1000
    );

    /**
     * @var array
     */
    private static $startTimes = array();

    /**
     * @var float
     */
    public static $requestTime;

    /**
     * Starts the timer.
     */
    public static function start()
    {
        array_push(self::$startTimes, microtime(true));
    }

    /**
     * Stops the timer and returns the elapsed time.
     *
     * @return float
     */
    public static function stop()
    {
        return microtime(true) - array_pop(self::$startTimes);
    }

    /**
     * Formats the elapsed time as a string.
     *
     * @param  float  $time
     * @return string
     */
    public static function secondsToTimeString($time)
    {
        $ms = round($time * 1000);

        foreach (self::$times as $unit => $value) {
            if ($ms >= $value) {
                $time = floor($ms / $value * 100.0) / 100.0;

                return $time . ' ' . ($time == 1 ? $unit : $unit . 's');
            }
        }

        return $ms . ' ms';
    }

    /**
     * Formats the elapsed time since the start of the request as a string.
     *
     * @return string
     */
    public static function timeSinceStartOfRequest()
    {
        return self::secondsToTimeString(microtime(true) - self::$requestTime);
    }

    /**
     * Returns the resources (time, memory) of the request as a string.
     *
     * @return string
     */
    public static function resourceUsage()
    {
        return sprintf(
            'Time: %s, Memory: %4.2fMB',
            self::timeSinceStartOfRequest(),
            memory_get_peak_usage(true) / 1048576
        );
    }
}

if (isset($_SERVER['REQUEST_TIME_FLOAT'])) {
    PHP_Timer::$requestTime = $_SERVER['REQUEST_TIME_FLOAT'];
} elseif (isset($_SERVER['REQUEST_TIME'])) {
    PHP_Timer::$requestTime = $_SERVER['REQUEST_TIME'];
} else {
    PHP_Timer::$requestTime = microtime(true);
}
{
    "name": "phpunit/php-token-stream",
    "description": "Wrapper around PHP's tokenizer extension.",
    "type": "library",
    "keywords": ["tokenizer"],
    "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
    "license": "BSD-3-Clause",
    "authors": [
        {
            "name": "Sebastian Bergmann",
            "email": "sebastian@phpunit.de"
        }
    ],
    "support": {
        "issues": "https://github.com/sebastianbergmann/php-token-stream/issues"
    },
    "require": {
        "php": "^7.0",
        "ext-tokenizer": "*"
    },
    "require-dev": {
        "phpunit/phpunit": "^6.2.4"
    },
    "config": {
        "optimize-autoloader": true,
        "sort-packages": true
    },
    "autoload": {
        "classmap": [
            "src/"
        ]
    },
    "extra": {
        "branch-alias": {
            "dev-master": "2.0-dev"
        }
    }
}
<?php
/*
 * This file is part of php-token-stream.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A caching factory for token stream objects.
 */
class PHP_Token_Stream_CachingFactory
{
    /**
     * @var array
     */
    protected static $cache = [];

    /**
     * @param string $filename
     *
     * @return PHP_Token_Stream
     */
    public static function get($filename)
    {
        if (!isset(self::$cache[$filename])) {
            self::$cache[$filename] = new PHP_Token_Stream($filename);
        }

        return self::$cache[$filename];
    }

    /**
     * @param string $filename
     */
    public static function clear($filename = null)
    {
        if (is_string($filename)) {
            unset(self::$cache[$filename]);
        } else {
            self::$cache = [];
        }
    }
}
<?php
/*
 * This file is part of php-token-stream.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A stream of PHP tokens.
 */
class PHP_Token_Stream implements ArrayAccess, Countable, SeekableIterator
{
    /**
     * @var array
     */
    protected static $customTokens = [
        '(' => 'PHP_Token_OPEN_BRACKET',
        ')' => 'PHP_Token_CLOSE_BRACKET',
        '[' => 'PHP_Token_OPEN_SQUARE',
        ']' => 'PHP_Token_CLOSE_SQUARE',
        '{' => 'PHP_Token_OPEN_CURLY',
        '}' => 'PHP_Token_CLOSE_CURLY',
        ';' => 'PHP_Token_SEMICOLON',
        '.' => 'PHP_Token_DOT',
        ',' => 'PHP_Token_COMMA',
        '=' => 'PHP_Token_EQUAL',
        '<' => 'PHP_Token_LT',
        '>' => 'PHP_Token_GT',
        '+' => 'PHP_Token_PLUS',
        '-' => 'PHP_Token_MINUS',
        '*' => 'PHP_Token_MULT',
        '/' => 'PHP_Token_DIV',
        '?' => 'PHP_Token_QUESTION_MARK',
        '!' => 'PHP_Token_EXCLAMATION_MARK',
        ':' => 'PHP_Token_COLON',
        '"' => 'PHP_Token_DOUBLE_QUOTES',
        '@' => 'PHP_Token_AT',
        '&' => 'PHP_Token_AMPERSAND',
        '%' => 'PHP_Token_PERCENT',
        '|' => 'PHP_Token_PIPE',
        '$' => 'PHP_Token_DOLLAR',
        '^' => 'PHP_Token_CARET',
        '~' => 'PHP_Token_TILDE',
        '`' => 'PHP_Token_BACKTICK'
    ];

    /**
     * @var string
     */
    protected $filename;

    /**
     * @var array
     */
    protected $tokens = [];

    /**
     * @var int
     */
    protected $position = 0;

    /**
     * @var array
     */
    protected $linesOfCode = ['loc' => 0, 'cloc' => 0, 'ncloc' => 0];

    /**
     * @var array
     */
    protected $classes;

    /**
     * @var array
     */
    protected $functions;

    /**
     * @var array
     */
    protected $includes;

    /**
     * @var array
     */
    protected $interfaces;

    /**
     * @var array
     */
    protected $traits;

    /**
     * @var array
     */
    protected $lineToFunctionMap = [];

    /**
     * Constructor.
     *
     * @param string $sourceCode
     */
    public function __construct($sourceCode)
    {
        if (is_file($sourceCode)) {
            $this->filename = $sourceCode;
            $sourceCode     = file_get_contents($sourceCode);
        }

        $this->scan($sourceCode);
    }

    /**
     * Destructor.
     */
    public function __destruct()
    {
        $this->tokens = [];
    }

    /**
     * @return string
     */
    public function __toString()
    {
        $buffer = '';

        foreach ($this as $token) {
            $buffer .= $token;
        }

        return $buffer;
    }

    /**
     * @return string
     */
    public function getFilename()
    {
        return $this->filename;
    }

    /**
     * Scans the source for sequences of characters and converts them into a
     * stream of tokens.
     *
     * @param string $sourceCode
     */
    protected function scan($sourceCode)
    {
        $id        = 0;
        $line      = 1;
        $tokens    = token_get_all($sourceCode);
        $numTokens = count($tokens);

        $lastNonWhitespaceTokenWasDoubleColon = false;

        for ($i = 0; $i < $numTokens; ++$i) {
            $token = $tokens[$i];
            $skip  = 0;

            if (is_array($token)) {
                $name = substr(token_name($token[0]), 2);
                $text = $token[1];

                if ($lastNonWhitespaceTokenWasDoubleColon && $name == 'CLASS') {
                    $name = 'CLASS_NAME_CONSTANT';
                } elseif ($name == 'USE' && isset($tokens[$i + 2][0]) && $tokens[$i + 2][0] == T_FUNCTION) {
                    $name = 'USE_FUNCTION';
                    $text .= $tokens[$i + 1][1] . $tokens[$i + 2][1];
                    $skip = 2;
                }

                $tokenClass = 'PHP_Token_' . $name;
            } else {
                $text       = $token;
                $tokenClass = self::$customTokens[$token];
            }

            $this->tokens[] = new $tokenClass($text, $line, $this, $id++);
            $lines          = substr_count($text, "\n");
            $line += $lines;

            if ($tokenClass == 'PHP_Token_HALT_COMPILER') {
                break;
            } elseif ($tokenClass == 'PHP_Token_COMMENT' ||
                $tokenClass == 'PHP_Token_DOC_COMMENT') {
                $this->linesOfCode['cloc'] += $lines + 1;
            }

            if ($name == 'DOUBLE_COLON') {
                $lastNonWhitespaceTokenWasDoubleColon = true;
            } elseif ($name != 'WHITESPACE') {
                $lastNonWhitespaceTokenWasDoubleColon = false;
            }

            $i += $skip;
        }

        $this->linesOfCode['loc']   = substr_count($sourceCode, "\n");
        $this->linesOfCode['ncloc'] = $this->linesOfCode['loc'] -
                                      $this->linesOfCode['cloc'];
    }

    /**
     * @return int
     */
    public function count()
    {
        return count($this->tokens);
    }

    /**
     * @return PHP_Token[]
     */
    public function tokens()
    {
        return $this->tokens;
    }

    /**
     * @return array
     */
    public function getClasses()
    {
        if ($this->classes !== null) {
            return $this->classes;
        }

        $this->parse();

        return $this->classes;
    }

    /**
     * @return array
     */
    public function getFunctions()
    {
        if ($this->functions !== null) {
            return $this->functions;
        }

        $this->parse();

        return $this->functions;
    }

    /**
     * @return array
     */
    public function getInterfaces()
    {
        if ($this->interfaces !== null) {
            return $this->interfaces;
        }

        $this->parse();

        return $this->interfaces;
    }

    /**
     * @return array
     */
    public function getTraits()
    {
        if ($this->traits !== null) {
            return $this->traits;
        }

        $this->parse();

        return $this->traits;
    }

    /**
     * Gets the names of all files that have been included
     * using include(), include_once(), require() or require_once().
     *
     * Parameter $categorize set to TRUE causing this function to return a
     * multi-dimensional array with categories in the keys of the first dimension
     * and constants and their values in the second dimension.
     *
     * Parameter $category allow to filter following specific inclusion type
     *
     * @param bool   $categorize OPTIONAL
     * @param string $category   OPTIONAL Either 'require_once', 'require',
     *                           'include_once', 'include'.
     *
     * @return array
     */
    public function getIncludes($categorize = false, $category = null)
    {
        if ($this->includes === null) {
            $this->includes = [
              'require_once' => [],
              'require'      => [],
              'include_once' => [],
              'include'      => []
            ];

            foreach ($this->tokens as $token) {
                switch (get_class($token)) {
                    case 'PHP_Token_REQUIRE_ONCE':
                    case 'PHP_Token_REQUIRE':
                    case 'PHP_Token_INCLUDE_ONCE':
                    case 'PHP_Token_INCLUDE':
                        $this->includes[$token->getType()][] = $token->getName();
                        break;
                }
            }
        }

        if (isset($this->includes[$category])) {
            $includes = $this->includes[$category];
        } elseif ($categorize === false) {
            $includes = array_merge(
                $this->includes['require_once'],
                $this->includes['require'],
                $this->includes['include_once'],
                $this->includes['include']
            );
        } else {
            $includes = $this->includes;
        }

        return $includes;
    }

    /**
     * Returns the name of the function or method a line belongs to.
     *
     * @return string or null if the line is not in a function or method
     */
    public function getFunctionForLine($line)
    {
        $this->parse();

        if (isset($this->lineToFunctionMap[$line])) {
            return $this->lineToFunctionMap[$line];
        }
    }

    protected function parse()
    {
        $this->interfaces = [];
        $this->classes    = [];
        $this->traits     = [];
        $this->functions  = [];
        $class            = [];
        $classEndLine     = [];
        $trait            = false;
        $traitEndLine     = false;
        $interface        = false;
        $interfaceEndLine = false;

        foreach ($this->tokens as $token) {
            switch (get_class($token)) {
                case 'PHP_Token_HALT_COMPILER':
                    return;

                case 'PHP_Token_INTERFACE':
                    $interface        = $token->getName();
                    $interfaceEndLine = $token->getEndLine();

                    $this->interfaces[$interface] = [
                      'methods'   => [],
                      'parent'    => $token->getParent(),
                      'keywords'  => $token->getKeywords(),
                      'docblock'  => $token->getDocblock(),
                      'startLine' => $token->getLine(),
                      'endLine'   => $interfaceEndLine,
                      'package'   => $token->getPackage(),
                      'file'      => $this->filename
                    ];
                    break;

                case 'PHP_Token_CLASS':
                case 'PHP_Token_TRAIT':
                    $tmp = [
                      'methods'   => [],
                      'parent'    => $token->getParent(),
                      'interfaces'=> $token->getInterfaces(),
                      'keywords'  => $token->getKeywords(),
                      'docblock'  => $token->getDocblock(),
                      'startLine' => $token->getLine(),
                      'endLine'   => $token->getEndLine(),
                      'package'   => $token->getPackage(),
                      'file'      => $this->filename
                    ];

                    if ($token instanceof PHP_Token_CLASS) {
                        $class[]        = $token->getName();
                        $classEndLine[] = $token->getEndLine();

                        $this->classes[$class[count($class) - 1]] = $tmp;
                    } else {
                        $trait                = $token->getName();
                        $traitEndLine         = $token->getEndLine();
                        $this->traits[$trait] = $tmp;
                    }
                    break;

                case 'PHP_Token_FUNCTION':
                    $name = $token->getName();
                    $tmp  = [
                      'docblock'  => $token->getDocblock(),
                      'keywords'  => $token->getKeywords(),
                      'visibility'=> $token->getVisibility(),
                      'signature' => $token->getSignature(),
                      'startLine' => $token->getLine(),
                      'endLine'   => $token->getEndLine(),
                      'ccn'       => $token->getCCN(),
                      'file'      => $this->filename
                    ];

                    if (empty($class) &&
                        $trait === false &&
                        $interface === false) {
                        $this->functions[$name] = $tmp;

                        $this->addFunctionToMap(
                            $name,
                            $tmp['startLine'],
                            $tmp['endLine']
                        );
                    } elseif (!empty($class)) {
                        $this->classes[$class[count($class) - 1]]['methods'][$name] = $tmp;

                        $this->addFunctionToMap(
                            $class[count($class) - 1] . '::' . $name,
                            $tmp['startLine'],
                            $tmp['endLine']
                        );
                    } elseif ($trait !== false) {
                        $this->traits[$trait]['methods'][$name] = $tmp;

                        $this->addFunctionToMap(
                            $trait . '::' . $name,
                            $tmp['startLine'],
                            $tmp['endLine']
                        );
                    } else {
                        $this->interfaces[$interface]['methods'][$name] = $tmp;
                    }
                    break;

                case 'PHP_Token_CLOSE_CURLY':
                    if (!empty($classEndLine) &&
                        $classEndLine[count($classEndLine) - 1] == $token->getLine()) {
                        array_pop($classEndLine);
                        array_pop($class);
                    } elseif ($traitEndLine !== false &&
                        $traitEndLine == $token->getLine()) {
                        $trait        = false;
                        $traitEndLine = false;
                    } elseif ($interfaceEndLine !== false &&
                        $interfaceEndLine == $token->getLine()) {
                        $interface        = false;
                        $interfaceEndLine = false;
                    }
                    break;
            }
        }
    }

    /**
     * @return array
     */
    public function getLinesOfCode()
    {
        return $this->linesOfCode;
    }

    /**
     */
    public function rewind()
    {
        $this->position = 0;
    }

    /**
     * @return bool
     */
    public function valid()
    {
        return isset($this->tokens[$this->position]);
    }

    /**
     * @return int
     */
    public function key()
    {
        return $this->position;
    }

    /**
     * @return PHP_Token
     */
    public function current()
    {
        return $this->tokens[$this->position];
    }

    /**
     */
    public function next()
    {
        $this->position++;
    }

    /**
     * @param int $offset
     *
     * @return bool
     */
    public function offsetExists($offset)
    {
        return isset($this->tokens[$offset]);
    }

    /**
     * @param int $offset
     *
     * @return mixed
     *
     * @throws OutOfBoundsException
     */
    public function offsetGet($offset)
    {
        if (!$this->offsetExists($offset)) {
            throw new OutOfBoundsException(
                sprintf(
                    'No token at position "%s"',
                    $offset
                )
            );
        }

        return $this->tokens[$offset];
    }

    /**
     * @param int   $offset
     * @param mixed $value
     */
    public function offsetSet($offset, $value)
    {
        $this->tokens[$offset] = $value;
    }

    /**
     * @param int $offset
     *
     * @throws OutOfBoundsException
     */
    public function offsetUnset($offset)
    {
        if (!$this->offsetExists($offset)) {
            throw new OutOfBoundsException(
                sprintf(
                    'No token at position "%s"',
                    $offset
                )
            );
        }

        unset($this->tokens[$offset]);
    }

    /**
     * Seek to an absolute position.
     *
     * @param int $position
     *
     * @throws OutOfBoundsException
     */
    public function seek($position)
    {
        $this->position = $position;

        if (!$this->valid()) {
            throw new OutOfBoundsException(
                sprintf(
                    'No token at position "%s"',
                    $this->position
                )
            );
        }
    }

    /**
     * @param string $name
     * @param int    $startLine
     * @param int    $endLine
     */
    private function addFunctionToMap($name, $startLine, $endLine)
    {
        for ($line = $startLine; $line <= $endLine; $line++) {
            $this->lineToFunctionMap[$line] = $name;
        }
    }
}
<?php
/*
 * This file is part of php-token-stream.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A PHP token.
 */
abstract class PHP_Token
{
    /**
     * @var string
     */
    protected $text;

    /**
     * @var int
     */
    protected $line;

    /**
     * @var PHP_Token_Stream
     */
    protected $tokenStream;

    /**
     * @var int
     */
    protected $id;

    /**
     * @param string           $text
     * @param int              $line
     * @param PHP_Token_Stream $tokenStream
     * @param int              $id
     */
    public function __construct($text, $line, PHP_Token_Stream $tokenStream, $id)
    {
        $this->text        = $text;
        $this->line        = $line;
        $this->tokenStream = $tokenStream;
        $this->id          = $id;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return $this->text;
    }

    /**
     * @return int
     */
    public function getLine()
    {
        return $this->line;
    }

    /**
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }
}

abstract class PHP_TokenWithScope extends PHP_Token
{
    /**
     * @var int
     */
    protected $endTokenId;

    /**
     * Get the docblock for this token
     *
     * This method will fetch the docblock belonging to the current token. The
     * docblock must be placed on the line directly above the token to be
     * recognized.
     *
     * @return string|null Returns the docblock as a string if found
     */
    public function getDocblock()
    {
        $tokens            = $this->tokenStream->tokens();
        $currentLineNumber = $tokens[$this->id]->getLine();
        $prevLineNumber    = $currentLineNumber - 1;

        for ($i = $this->id - 1; $i; $i--) {
            if (!isset($tokens[$i])) {
                return;
            }

            if ($tokens[$i] instanceof PHP_Token_FUNCTION ||
                $tokens[$i] instanceof PHP_Token_CLASS ||
                $tokens[$i] instanceof PHP_Token_TRAIT) {
                // Some other trait, class or function, no docblock can be
                // used for the current token
                break;
            }

            $line = $tokens[$i]->getLine();

            if ($line == $currentLineNumber ||
                ($line == $prevLineNumber &&
                 $tokens[$i] instanceof PHP_Token_WHITESPACE)) {
                continue;
            }

            if ($line < $currentLineNumber &&
                !$tokens[$i] instanceof PHP_Token_DOC_COMMENT) {
                break;
            }

            return (string) $tokens[$i];
        }
    }

    /**
     * @return int
     */
    public function getEndTokenId()
    {
        $block  = 0;
        $i      = $this->id;
        $tokens = $this->tokenStream->tokens();

        while ($this->endTokenId === null && isset($tokens[$i])) {
            if ($tokens[$i] instanceof PHP_Token_OPEN_CURLY ||
                $tokens[$i] instanceof PHP_Token_CURLY_OPEN) {
                $block++;
            } elseif ($tokens[$i] instanceof PHP_Token_CLOSE_CURLY) {
                $block--;

                if ($block === 0) {
                    $this->endTokenId = $i;
                }
            } elseif (($this instanceof PHP_Token_FUNCTION ||
                $this instanceof PHP_Token_NAMESPACE) &&
                $tokens[$i] instanceof PHP_Token_SEMICOLON) {
                if ($block === 0) {
                    $this->endTokenId = $i;
                }
            }

            $i++;
        }

        if ($this->endTokenId === null) {
            $this->endTokenId = $this->id;
        }

        return $this->endTokenId;
    }

    /**
     * @return int
     */
    public function getEndLine()
    {
        return $this->tokenStream[$this->getEndTokenId()]->getLine();
    }
}

abstract class PHP_TokenWithScopeAndVisibility extends PHP_TokenWithScope
{
    /**
     * @return string
     */
    public function getVisibility()
    {
        $tokens = $this->tokenStream->tokens();

        for ($i = $this->id - 2; $i > $this->id - 7; $i -= 2) {
            if (isset($tokens[$i]) &&
               ($tokens[$i] instanceof PHP_Token_PRIVATE ||
                $tokens[$i] instanceof PHP_Token_PROTECTED ||
                $tokens[$i] instanceof PHP_Token_PUBLIC)) {
                return strtolower(
                    str_replace('PHP_Token_', '', get_class($tokens[$i]))
                );
            }
            if (isset($tokens[$i]) &&
              !($tokens[$i] instanceof PHP_Token_STATIC ||
                $tokens[$i] instanceof PHP_Token_FINAL ||
                $tokens[$i] instanceof PHP_Token_ABSTRACT)) {
                // no keywords; stop visibility search
                break;
            }
        }
    }

    /**
     * @return string
     */
    public function getKeywords()
    {
        $keywords = [];
        $tokens   = $this->tokenStream->tokens();

        for ($i = $this->id - 2; $i > $this->id - 7; $i -= 2) {
            if (isset($tokens[$i]) &&
               ($tokens[$i] instanceof PHP_Token_PRIVATE ||
                $tokens[$i] instanceof PHP_Token_PROTECTED ||
                $tokens[$i] instanceof PHP_Token_PUBLIC)) {
                continue;
            }

            if (isset($tokens[$i]) &&
               ($tokens[$i] instanceof PHP_Token_STATIC ||
                $tokens[$i] instanceof PHP_Token_FINAL ||
                $tokens[$i] instanceof PHP_Token_ABSTRACT)) {
                $keywords[] = strtolower(
                    str_replace('PHP_Token_', '', get_class($tokens[$i]))
                );
            }
        }

        return implode(',', $keywords);
    }
}

abstract class PHP_Token_Includes extends PHP_Token
{
    /**
     * @var string
     */
    protected $name;

    /**
     * @var string
     */
    protected $type;

    /**
     * @return string
     */
    public function getName()
    {
        if ($this->name === null) {
            $this->process();
        }

        return $this->name;
    }

    /**
     * @return string
     */
    public function getType()
    {
        if ($this->type === null) {
            $this->process();
        }

        return $this->type;
    }

    private function process()
    {
        $tokens = $this->tokenStream->tokens();

        if ($tokens[$this->id + 2] instanceof PHP_Token_CONSTANT_ENCAPSED_STRING) {
            $this->name = trim($tokens[$this->id + 2], "'\"");
            $this->type = strtolower(
                str_replace('PHP_Token_', '', get_class($tokens[$this->id]))
            );
        }
    }
}

class PHP_Token_FUNCTION extends PHP_TokenWithScopeAndVisibility
{
    /**
     * @var array
     */
    protected $arguments;

    /**
     * @var int
     */
    protected $ccn;

    /**
     * @var string
     */
    protected $name;

    /**
     * @var string
     */
    protected $signature;

    /**
     * @var bool
     */
    private $anonymous = false;

    /**
     * @return array
     */
    public function getArguments()
    {
        if ($this->arguments !== null) {
            return $this->arguments;
        }

        $this->arguments = [];
        $tokens          = $this->tokenStream->tokens();
        $typeDeclaration = null;

        // Search for first token inside brackets
        $i = $this->id + 2;

        while (!$tokens[$i - 1] instanceof PHP_Token_OPEN_BRACKET) {
            $i++;
        }

        while (!$tokens[$i] instanceof PHP_Token_CLOSE_BRACKET) {
            if ($tokens[$i] instanceof PHP_Token_STRING) {
                $typeDeclaration = (string) $tokens[$i];
            } elseif ($tokens[$i] instanceof PHP_Token_VARIABLE) {
                $this->arguments[(string) $tokens[$i]] = $typeDeclaration;
                $typeDeclaration                       = null;
            }

            $i++;
        }

        return $this->arguments;
    }

    /**
     * @return string
     */
    public function getName()
    {
        if ($this->name !== null) {
            return $this->name;
        }

        $tokens = $this->tokenStream->tokens();

        $i = $this->id + 1;

        if ($tokens[$i] instanceof PHP_Token_WHITESPACE) {
            $i++;
        }

        if ($tokens[$i] instanceof PHP_Token_AMPERSAND) {
            $i++;
        }

        if ($tokens[$i + 1] instanceof PHP_Token_OPEN_BRACKET) {
            $this->name = (string) $tokens[$i];
        } elseif ($tokens[$i + 1] instanceof PHP_Token_WHITESPACE && $tokens[$i + 2] instanceof PHP_Token_OPEN_BRACKET) {
            $this->name = (string) $tokens[$i];
        } else {
            $this->anonymous = true;

            $this->name = sprintf(
                'anonymousFunction:%s#%s',
                $this->getLine(),
                $this->getId()
            );
        }

        if (!$this->isAnonymous()) {
            for ($i = $this->id; $i; --$i) {
                if ($tokens[$i] instanceof PHP_Token_NAMESPACE) {
                    $this->name = $tokens[$i]->getName() . '\\' . $this->name;

                    break;
                }

                if ($tokens[$i] instanceof PHP_Token_INTERFACE) {
                    break;
                }
            }
        }

        return $this->name;
    }

    /**
     * @return int
     */
    public function getCCN()
    {
        if ($this->ccn !== null) {
            return $this->ccn;
        }

        $this->ccn = 1;
        $end       = $this->getEndTokenId();
        $tokens    = $this->tokenStream->tokens();

        for ($i = $this->id; $i <= $end; $i++) {
            switch (get_class($tokens[$i])) {
                case 'PHP_Token_IF':
                case 'PHP_Token_ELSEIF':
                case 'PHP_Token_FOR':
                case 'PHP_Token_FOREACH':
                case 'PHP_Token_WHILE':
                case 'PHP_Token_CASE':
                case 'PHP_Token_CATCH':
                case 'PHP_Token_BOOLEAN_AND':
                case 'PHP_Token_LOGICAL_AND':
                case 'PHP_Token_BOOLEAN_OR':
                case 'PHP_Token_LOGICAL_OR':
                case 'PHP_Token_QUESTION_MARK':
                    $this->ccn++;
                    break;
            }
        }

        return $this->ccn;
    }

    /**
     * @return string
     */
    public function getSignature()
    {
        if ($this->signature !== null) {
            return $this->signature;
        }

        if ($this->isAnonymous()) {
            $this->signature = 'anonymousFunction';
            $i               = $this->id + 1;
        } else {
            $this->signature = '';
            $i               = $this->id + 2;
        }

        $tokens = $this->tokenStream->tokens();

        while (isset($tokens[$i]) &&
               !$tokens[$i] instanceof PHP_Token_OPEN_CURLY &&
               !$tokens[$i] instanceof PHP_Token_SEMICOLON) {
            $this->signature .= $tokens[$i++];
        }

        $this->signature = trim($this->signature);

        return $this->signature;
    }

    /**
     * @return bool
     */
    public function isAnonymous()
    {
        return $this->anonymous;
    }
}

class PHP_Token_INTERFACE extends PHP_TokenWithScopeAndVisibility
{
    /**
     * @var array
     */
    protected $interfaces;

    /**
     * @return string
     */
    public function getName()
    {
        return (string) $this->tokenStream[$this->id + 2];
    }

    /**
     * @return bool
     */
    public function hasParent()
    {
        return $this->tokenStream[$this->id + 4] instanceof PHP_Token_EXTENDS;
    }

    /**
     * @return array
     */
    public function getPackage()
    {
        $className  = $this->getName();
        $docComment = $this->getDocblock();

        $result = [
            'namespace'   => '',
            'fullPackage' => '',
            'category'    => '',
            'package'     => '',
            'subpackage'  => ''
        ];

        for ($i = $this->id; $i; --$i) {
            if ($this->tokenStream[$i] instanceof PHP_Token_NAMESPACE) {
                $result['namespace'] = $this->tokenStream[$i]->getName();
                break;
            }
        }

        if (preg_match('/@category[\s]+([\.\w]+)/', $docComment, $matches)) {
            $result['category'] = $matches[1];
        }

        if (preg_match('/@package[\s]+([\.\w]+)/', $docComment, $matches)) {
            $result['package']     = $matches[1];
            $result['fullPackage'] = $matches[1];
        }

        if (preg_match('/@subpackage[\s]+([\.\w]+)/', $docComment, $matches)) {
            $result['subpackage']   = $matches[1];
            $result['fullPackage'] .= '.' . $matches[1];
        }

        if (empty($result['fullPackage'])) {
            $result['fullPackage'] = $this->arrayToName(
                explode('_', str_replace('\\', '_', $className)),
                '.'
            );
        }

        return $result;
    }

    /**
     * @param array  $parts
     * @param string $join
     *
     * @return string
     */
    protected function arrayToName(array $parts, $join = '\\')
    {
        $result = '';

        if (count($parts) > 1) {
            array_pop($parts);

            $result = implode($join, $parts);
        }

        return $result;
    }

    /**
     * @return bool|string
     */
    public function getParent()
    {
        if (!$this->hasParent()) {
            return false;
        }

        $i         = $this->id + 6;
        $tokens    = $this->tokenStream->tokens();
        $className = (string) $tokens[$i];

        while (isset($tokens[$i + 1]) &&
               !$tokens[$i + 1] instanceof PHP_Token_WHITESPACE) {
            $className .= (string) $tokens[++$i];
        }

        return $className;
    }

    /**
     * @return bool
     */
    public function hasInterfaces()
    {
        return (isset($this->tokenStream[$this->id + 4]) &&
                $this->tokenStream[$this->id + 4] instanceof PHP_Token_IMPLEMENTS) ||
               (isset($this->tokenStream[$this->id + 8]) &&
                $this->tokenStream[$this->id + 8] instanceof PHP_Token_IMPLEMENTS);
    }

    /**
     * @return array|bool
     */
    public function getInterfaces()
    {
        if ($this->interfaces !== null) {
            return $this->interfaces;
        }

        if (!$this->hasInterfaces()) {
            return ($this->interfaces = false);
        }

        if ($this->tokenStream[$this->id + 4] instanceof PHP_Token_IMPLEMENTS) {
            $i = $this->id + 3;
        } else {
            $i = $this->id + 7;
        }

        $tokens = $this->tokenStream->tokens();

        while (!$tokens[$i + 1] instanceof PHP_Token_OPEN_CURLY) {
            $i++;

            if ($tokens[$i] instanceof PHP_Token_STRING) {
                $this->interfaces[] = (string) $tokens[$i];
            }
        }

        return $this->interfaces;
    }
}

class PHP_Token_ABSTRACT extends PHP_Token
{
}

class PHP_Token_AMPERSAND extends PHP_Token
{
}

class PHP_Token_AND_EQUAL extends PHP_Token
{
}

class PHP_Token_ARRAY extends PHP_Token
{
}

class PHP_Token_ARRAY_CAST extends PHP_Token
{
}

class PHP_Token_AS extends PHP_Token
{
}

class PHP_Token_AT extends PHP_Token
{
}

class PHP_Token_BACKTICK extends PHP_Token
{
}

class PHP_Token_BAD_CHARACTER extends PHP_Token
{
}

class PHP_Token_BOOLEAN_AND extends PHP_Token
{
}

class PHP_Token_BOOLEAN_OR extends PHP_Token
{
}

class PHP_Token_BOOL_CAST extends PHP_Token
{
}

class PHP_Token_BREAK extends PHP_Token
{
}

class PHP_Token_CARET extends PHP_Token
{
}

class PHP_Token_CASE extends PHP_Token
{
}

class PHP_Token_CATCH extends PHP_Token
{
}

class PHP_Token_CHARACTER extends PHP_Token
{
}

class PHP_Token_CLASS extends PHP_Token_INTERFACE
{
    /**
     * @var bool
     */
    private $anonymous = false;

    /**
     * @var string
     */
    private $name;

    /**
     * @return string
     */
    public function getName()
    {
        if ($this->name !== null) {
            return $this->name;
        }

        $next = $this->tokenStream[$this->id + 1];

        if ($next instanceof PHP_Token_WHITESPACE) {
            $next = $this->tokenStream[$this->id + 2];
        }

        if ($next instanceof PHP_Token_STRING) {
            $this->name =(string) $next;

            return $this->name;
        }

        if ($next instanceof PHP_Token_OPEN_CURLY ||
            $next instanceof PHP_Token_EXTENDS ||
            $next instanceof PHP_Token_IMPLEMENTS) {

            $this->name = sprintf(
                'AnonymousClass:%s#%s',
                $this->getLine(),
                $this->getId()
            );

            $this->anonymous = true;

            return $this->name;
        }
    }

    public function isAnonymous()
    {
        return $this->anonymous;
    }
}

class PHP_Token_CLASS_C extends PHP_Token
{
}

class PHP_Token_CLASS_NAME_CONSTANT extends PHP_Token
{
}

class PHP_Token_CLONE extends PHP_Token
{
}

class PHP_Token_CLOSE_BRACKET extends PHP_Token
{
}

class PHP_Token_CLOSE_CURLY extends PHP_Token
{
}

class PHP_Token_CLOSE_SQUARE extends PHP_Token
{
}

class PHP_Token_CLOSE_TAG extends PHP_Token
{
}

class PHP_Token_COLON extends PHP_Token
{
}

class PHP_Token_COMMA extends PHP_Token
{
}

class PHP_Token_COMMENT extends PHP_Token
{
}

class PHP_Token_CONCAT_EQUAL extends PHP_Token
{
}

class PHP_Token_CONST extends PHP_Token
{
}

class PHP_Token_CONSTANT_ENCAPSED_STRING extends PHP_Token
{
}

class PHP_Token_CONTINUE extends PHP_Token
{
}

class PHP_Token_CURLY_OPEN extends PHP_Token
{
}

class PHP_Token_DEC extends PHP_Token
{
}

class PHP_Token_DECLARE extends PHP_Token
{
}

class PHP_Token_DEFAULT extends PHP_Token
{
}

class PHP_Token_DIV extends PHP_Token
{
}

class PHP_Token_DIV_EQUAL extends PHP_Token
{
}

class PHP_Token_DNUMBER extends PHP_Token
{
}

class PHP_Token_DO extends PHP_Token
{
}

class PHP_Token_DOC_COMMENT extends PHP_Token
{
}

class PHP_Token_DOLLAR extends PHP_Token
{
}

class PHP_Token_DOLLAR_OPEN_CURLY_BRACES extends PHP_Token
{
}

class PHP_Token_DOT extends PHP_Token
{
}

class PHP_Token_DOUBLE_ARROW extends PHP_Token
{
}

class PHP_Token_DOUBLE_CAST extends PHP_Token
{
}

class PHP_Token_DOUBLE_COLON extends PHP_Token
{
}

class PHP_Token_DOUBLE_QUOTES extends PHP_Token
{
}

class PHP_Token_ECHO extends PHP_Token
{
}

class PHP_Token_ELSE extends PHP_Token
{
}

class PHP_Token_ELSEIF extends PHP_Token
{
}

class PHP_Token_EMPTY extends PHP_Token
{
}

class PHP_Token_ENCAPSED_AND_WHITESPACE extends PHP_Token
{
}

class PHP_Token_ENDDECLARE extends PHP_Token
{
}

class PHP_Token_ENDFOR extends PHP_Token
{
}

class PHP_Token_ENDFOREACH extends PHP_Token
{
}

class PHP_Token_ENDIF extends PHP_Token
{
}

class PHP_Token_ENDSWITCH extends PHP_Token
{
}

class PHP_Token_ENDWHILE extends PHP_Token
{
}

class PHP_Token_END_HEREDOC extends PHP_Token
{
}

class PHP_Token_EQUAL extends PHP_Token
{
}

class PHP_Token_EVAL extends PHP_Token
{
}

class PHP_Token_EXCLAMATION_MARK extends PHP_Token
{
}

class PHP_Token_EXIT extends PHP_Token
{
}

class PHP_Token_EXTENDS extends PHP_Token
{
}

class PHP_Token_FILE extends PHP_Token
{
}

class PHP_Token_FINAL extends PHP_Token
{
}

class PHP_Token_FOR extends PHP_Token
{
}

class PHP_Token_FOREACH extends PHP_Token
{
}

class PHP_Token_FUNC_C extends PHP_Token
{
}

class PHP_Token_GLOBAL extends PHP_Token
{
}

class PHP_Token_GT extends PHP_Token
{
}

class PHP_Token_IF extends PHP_Token
{
}

class PHP_Token_IMPLEMENTS extends PHP_Token
{
}

class PHP_Token_INC extends PHP_Token
{
}

class PHP_Token_INCLUDE extends PHP_Token_Includes
{
}

class PHP_Token_INCLUDE_ONCE extends PHP_Token_Includes
{
}

class PHP_Token_INLINE_HTML extends PHP_Token
{
}

class PHP_Token_INSTANCEOF extends PHP_Token
{
}

class PHP_Token_INT_CAST extends PHP_Token
{
}

class PHP_Token_ISSET extends PHP_Token
{
}

class PHP_Token_IS_EQUAL extends PHP_Token
{
}

class PHP_Token_IS_GREATER_OR_EQUAL extends PHP_Token
{
}

class PHP_Token_IS_IDENTICAL extends PHP_Token
{
}

class PHP_Token_IS_NOT_EQUAL extends PHP_Token
{
}

class PHP_Token_IS_NOT_IDENTICAL extends PHP_Token
{
}

class PHP_Token_IS_SMALLER_OR_EQUAL extends PHP_Token
{
}

class PHP_Token_LINE extends PHP_Token
{
}

class PHP_Token_LIST extends PHP_Token
{
}

class PHP_Token_LNUMBER extends PHP_Token
{
}

class PHP_Token_LOGICAL_AND extends PHP_Token
{
}

class PHP_Token_LOGICAL_OR extends PHP_Token
{
}

class PHP_Token_LOGICAL_XOR extends PHP_Token
{
}

class PHP_Token_LT extends PHP_Token
{
}

class PHP_Token_METHOD_C extends PHP_Token
{
}

class PHP_Token_MINUS extends PHP_Token
{
}

class PHP_Token_MINUS_EQUAL extends PHP_Token
{
}

class PHP_Token_MOD_EQUAL extends PHP_Token
{
}

class PHP_Token_MULT extends PHP_Token
{
}

class PHP_Token_MUL_EQUAL extends PHP_Token
{
}

class PHP_Token_NEW extends PHP_Token
{
}

class PHP_Token_NUM_STRING extends PHP_Token
{
}

class PHP_Token_OBJECT_CAST extends PHP_Token
{
}

class PHP_Token_OBJECT_OPERATOR extends PHP_Token
{
}

class PHP_Token_OPEN_BRACKET extends PHP_Token
{
}

class PHP_Token_OPEN_CURLY extends PHP_Token
{
}

class PHP_Token_OPEN_SQUARE extends PHP_Token
{
}

class PHP_Token_OPEN_TAG extends PHP_Token
{
}

class PHP_Token_OPEN_TAG_WITH_ECHO extends PHP_Token
{
}

class PHP_Token_OR_EQUAL extends PHP_Token
{
}

class PHP_Token_PAAMAYIM_NEKUDOTAYIM extends PHP_Token
{
}

class PHP_Token_PERCENT extends PHP_Token
{
}

class PHP_Token_PIPE extends PHP_Token
{
}

class PHP_Token_PLUS extends PHP_Token
{
}

class PHP_Token_PLUS_EQUAL extends PHP_Token
{
}

class PHP_Token_PRINT extends PHP_Token
{
}

class PHP_Token_PRIVATE extends PHP_Token
{
}

class PHP_Token_PROTECTED extends PHP_Token
{
}

class PHP_Token_PUBLIC extends PHP_Token
{
}

class PHP_Token_QUESTION_MARK extends PHP_Token
{
}

class PHP_Token_REQUIRE extends PHP_Token_Includes
{
}

class PHP_Token_REQUIRE_ONCE extends PHP_Token_Includes
{
}

class PHP_Token_RETURN extends PHP_Token
{
}

class PHP_Token_SEMICOLON extends PHP_Token
{
}

class PHP_Token_SL extends PHP_Token
{
}

class PHP_Token_SL_EQUAL extends PHP_Token
{
}

class PHP_Token_SR extends PHP_Token
{
}

class PHP_Token_SR_EQUAL extends PHP_Token
{
}

class PHP_Token_START_HEREDOC extends PHP_Token
{
}

class PHP_Token_STATIC extends PHP_Token
{
}

class PHP_Token_STRING extends PHP_Token
{
}

class PHP_Token_STRING_CAST extends PHP_Token
{
}

class PHP_Token_STRING_VARNAME extends PHP_Token
{
}

class PHP_Token_SWITCH extends PHP_Token
{
}

class PHP_Token_THROW extends PHP_Token
{
}

class PHP_Token_TILDE extends PHP_Token
{
}

class PHP_Token_TRY extends PHP_Token
{
}

class PHP_Token_UNSET extends PHP_Token
{
}

class PHP_Token_UNSET_CAST extends PHP_Token
{
}

class PHP_Token_USE extends PHP_Token
{
}

class PHP_Token_USE_FUNCTION extends PHP_Token
{
}

class PHP_Token_VAR extends PHP_Token
{
}

class PHP_Token_VARIABLE extends PHP_Token
{
}

class PHP_Token_WHILE extends PHP_Token
{
}

class PHP_Token_WHITESPACE extends PHP_Token
{
}

class PHP_Token_XOR_EQUAL extends PHP_Token
{
}

// Tokens introduced in PHP 5.1
class PHP_Token_HALT_COMPILER extends PHP_Token
{
}

// Tokens introduced in PHP 5.3
class PHP_Token_DIR extends PHP_Token
{
}

class PHP_Token_GOTO extends PHP_Token
{
}

class PHP_Token_NAMESPACE extends PHP_TokenWithScope
{
    /**
     * @return string
     */
    public function getName()
    {
        $tokens    = $this->tokenStream->tokens();
        $namespace = (string) $tokens[$this->id + 2];

        for ($i = $this->id + 3;; $i += 2) {
            if (isset($tokens[$i]) &&
                $tokens[$i] instanceof PHP_Token_NS_SEPARATOR) {
                $namespace .= '\\' . $tokens[$i + 1];
            } else {
                break;
            }
        }

        return $namespace;
    }
}

class PHP_Token_NS_C extends PHP_Token
{
}

class PHP_Token_NS_SEPARATOR extends PHP_Token
{
}

// Tokens introduced in PHP 5.4
class PHP_Token_CALLABLE extends PHP_Token
{
}

class PHP_Token_INSTEADOF extends PHP_Token
{
}

class PHP_Token_TRAIT extends PHP_Token_INTERFACE
{
}

class PHP_Token_TRAIT_C extends PHP_Token
{
}

// Tokens introduced in PHP 5.5
class PHP_Token_FINALLY extends PHP_Token
{
}

class PHP_Token_YIELD extends PHP_Token
{
}

// Tokens introduced in PHP 5.6
class PHP_Token_ELLIPSIS extends PHP_Token
{
}

class PHP_Token_POW extends PHP_Token
{
}

class PHP_Token_POW_EQUAL extends PHP_Token
{
}

// Tokens introduced in PHP 7.0
class PHP_Token_COALESCE extends PHP_Token
{
}

class PHP_Token_SPACESHIP extends PHP_Token
{
}

class PHP_Token_YIELD_FROM extends PHP_Token
{
}

// Tokens introduced in HackLang / HHVM
class PHP_Token_ASYNC extends PHP_Token
{
}

class PHP_Token_AWAIT extends PHP_Token
{
}

class PHP_Token_COMPILER_HALT_OFFSET extends PHP_Token
{
}

class PHP_Token_ENUM extends PHP_Token
{
}

class PHP_Token_EQUALS extends PHP_Token
{
}

class PHP_Token_IN extends PHP_Token
{
}

class PHP_Token_JOIN extends PHP_Token
{
}

class PHP_Token_LAMBDA_ARROW extends PHP_Token
{
}

class PHP_Token_LAMBDA_CP extends PHP_Token
{
}

class PHP_Token_LAMBDA_OP extends PHP_Token
{
}

class PHP_Token_ONUMBER extends PHP_Token
{
}

class PHP_Token_NULLSAFE_OBJECT_OPERATOR extends PHP_Token
{
}

class PHP_Token_SHAPE extends PHP_Token
{
}

class PHP_Token_SUPER extends PHP_Token
{
}

class PHP_Token_TYPE extends PHP_Token
{
}

class PHP_Token_TYPELIST_GT extends PHP_Token
{
}

class PHP_Token_TYPELIST_LT extends PHP_Token
{
}

class PHP_Token_WHERE extends PHP_Token
{
}

class PHP_Token_XHP_ATTRIBUTE extends PHP_Token
{
}

class PHP_Token_XHP_CATEGORY extends PHP_Token
{
}

class PHP_Token_XHP_CATEGORY_LABEL extends PHP_Token
{
}

class PHP_Token_XHP_CHILDREN extends PHP_Token
{
}

class PHP_Token_XHP_LABEL extends PHP_Token
{
}

class PHP_Token_XHP_REQUIRED extends PHP_Token
{
}

class PHP_Token_XHP_TAG_GT extends PHP_Token
{
}

class PHP_Token_XHP_TAG_LT extends PHP_Token
{
}

class PHP_Token_XHP_TEXT extends PHP_Token
{
}
{
    "name": "phpunit/phpunit-mock-objects",
    "description": "Mock Object library for PHPUnit",
    "type": "library",
    "keywords": [
        "xunit",
        "mock"
    ],
    "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
    "license": "BSD-3-Clause",
    "authors": [
        {
            "name": "Sebastian Bergmann",
            "email": "sb@sebastian-bergmann.de",
            "role": "lead"
        }
    ],
    "support": {
        "issues": "https://github.com/sebastianbergmann/phpunit-mock-objects/issues",
        "irc": "irc://irc.freenode.net/phpunit"
    },
    "require": {
        "php": "^5.6 || ^7.0",
        "phpunit/php-text-template": "^1.2",
        "doctrine/instantiator": "^1.0.2",
        "sebastian/exporter": "^1.2 || ^2.0"
    },
    "require-dev": {
        "phpunit/phpunit": "^5.4"
    },
    "conflict": {
        "phpunit/phpunit": "<5.4.0"
    },
    "suggest": {
        "ext-soap": "*"
    },
    "autoload": {
        "classmap": [
            "src/"
        ]
    },
    "autoload-dev": {
        "classmap": [
            "tests/_fixture/"
        ]
    },
    "extra": {
        "branch-alias": {
            "dev-master": "3.2.x-dev"
        }
    }
}

<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use SebastianBergmann\Exporter\Exporter;

/**
 * Represents a static invocation.
 *
 * @since Class available since Release 1.0.0
 */
class PHPUnit_Framework_MockObject_Invocation_Static implements PHPUnit_Framework_MockObject_Invocation, PHPUnit_Framework_SelfDescribing
{
    /**
     * @var array
     */
    protected static $uncloneableExtensions = [
        'mysqli'    => true,
        'SQLite'    => true,
        'sqlite3'   => true,
        'tidy'      => true,
        'xmlwriter' => true,
        'xsl'       => true
    ];

    /**
     * @var array
     */
    protected static $uncloneableClasses = [
        'Closure',
        'COMPersistHelper',
        'IteratorIterator',
        'RecursiveIteratorIterator',
        'SplFileObject',
        'PDORow',
        'ZipArchive'
    ];

    /**
     * @var string
     */
    public $className;

    /**
     * @var string
     */
    public $methodName;

    /**
     * @var array
     */
    public $parameters;

    /**
     * @var string
     */
    public $returnType;

    /**
     * @var bool
     */
    public $returnTypeNullable = false;

    /**
     * @param string $className
     * @param string $methodName
     * @param array  $parameters
     * @param string $returnType
     * @param bool   $cloneObjects
     */
    public function __construct($className, $methodName, array $parameters, $returnType, $cloneObjects = false)
    {
        $this->className  = $className;
        $this->methodName = $methodName;
        $this->parameters = $parameters;

        if (strpos($returnType, '?') === 0) {
            $returnType               = substr($returnType, 1);
            $this->returnTypeNullable = true;
        }

        $this->returnType = $returnType;

        if (!$cloneObjects) {
            return;
        }

        foreach ($this->parameters as $key => $value) {
            if (is_object($value)) {
                $this->parameters[$key] = $this->cloneObject($value);
            }
        }
    }

    /**
     * @return string
     */
    public function toString()
    {
        $exporter = new Exporter;

        return sprintf(
            '%s::%s(%s)%s',
            $this->className,
            $this->methodName,
            implode(
                ', ',
                array_map(
                    [$exporter, 'shortenedExport'],
                    $this->parameters
                )
            ),
            $this->returnType ? sprintf(': %s', $this->returnType) : ''
        );
    }

    /**
     * @return mixed Mocked return value.
     */
    public function generateReturnValue()
    {
        switch ($this->returnType) {
            case '':       return;
            case 'string': return $this->returnTypeNullable ? null : '';
            case 'float':  return $this->returnTypeNullable ? null : 0.0;
            case 'int':    return $this->returnTypeNullable ? null : 0;
            case 'bool':   return $this->returnTypeNullable ? null : false;
            case 'array':  return $this->returnTypeNullable ? null : [];
            case 'void':   return;

            case 'callable':
            case 'Closure':
                return function () {};

            case 'Traversable':
            case 'Generator':
                $generator = function () { yield; };

                return $generator();

            default:
                if ($this->returnTypeNullable) {
                    return null;
                }

                $generator = new PHPUnit_Framework_MockObject_Generator;

                return $generator->getMock($this->returnType, [], [], '', false);
        }
    }

    /**
     * @param object $original
     *
     * @return object
     */
    protected function cloneObject($original)
    {
        $cloneable = null;
        $object    = new ReflectionObject($original);

        // Check the blacklist before asking PHP reflection to work around
        // https://bugs.php.net/bug.php?id=53967
        if ($object->isInternal() &&
            isset(self::$uncloneableExtensions[$object->getExtensionName()])) {
            $cloneable = false;
        }

        if ($cloneable === null) {
            foreach (self::$uncloneableClasses as $class) {
                if ($original instanceof $class) {
                    $cloneable = false;
                    break;
                }
            }
        }

        if ($cloneable === null && method_exists($object, 'isCloneable')) {
            $cloneable = $object->isCloneable();
        }

        if ($cloneable === null && $object->hasMethod('__clone')) {
            $method    = $object->getMethod('__clone');
            $cloneable = $method->isPublic();
        }

        if ($cloneable === null) {
            $cloneable = true;
        }

        if ($cloneable) {
            try {
                return clone $original;
            } catch (Exception $e) {
                return $original;
            }
        } else {
            return $original;
        }
    }
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Represents a non-static invocation.
 *
 * @since Class available since Release 1.0.0
 */
class PHPUnit_Framework_MockObject_Invocation_Object extends PHPUnit_Framework_MockObject_Invocation_Static
{
    /**
     * @var object
     */
    public $object;

    /**
     * @param string $className
     * @param string $methodName
     * @param array  $parameters
     * @param string $returnType
     * @param object $object
     * @param bool   $cloneObjects
     */
    public function __construct($className, $methodName, array $parameters, $returnType, $object, $cloneObjects = false)
    {
        parent::__construct($className, $methodName, $parameters, $returnType, $cloneObjects);

        $this->object = $object;
    }
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Interface for all mock objects which are generated by
 * PHPUnit_Framework_MockObject_MockBuilder.
 *
 * @method PHPUnit_Framework_MockObject_Builder_InvocationMocker method($constraint)
 *
 * @since Interface available since Release 1.0.0
 */
interface PHPUnit_Framework_MockObject_MockObject /*extends PHPUnit_Framework_MockObject_Verifiable*/
{
    /**
     * Registers a new expectation in the mock object and returns the match
     * object which can be infused with further details.
     *
     * @param PHPUnit_Framework_MockObject_Matcher_Invocation $matcher
     *
     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
     */
    public function expects(PHPUnit_Framework_MockObject_Matcher_Invocation $matcher);

    /**
     * @return PHPUnit_Framework_MockObject_InvocationMocker
     *
     * @since  Method available since Release 2.0.0
     */
    public function __phpunit_setOriginalObject($originalObject);

    /**
     * @return PHPUnit_Framework_MockObject_InvocationMocker
     */
    public function __phpunit_getInvocationMocker();

    /**
     * Verifies that the current expectation is valid. If everything is OK the
     * code should just return, if not it must throw an exception.
     *
     * @throws PHPUnit_Framework_ExpectationFailedException
     */
    public function __phpunit_verify();

    /**
     * @return bool
     */
    public function __phpunit_hasMatchers();
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Mocker for invocations which are sent from
 * PHPUnit_Framework_MockObject_MockObject objects.
 *
 * Keeps track of all expectations and stubs as well as registering
 * identifications for builders.
 *
 * @since Class available since Release 1.0.0
 */
class PHPUnit_Framework_MockObject_InvocationMocker implements PHPUnit_Framework_MockObject_Stub_MatcherCollection, PHPUnit_Framework_MockObject_Invokable, PHPUnit_Framework_MockObject_Builder_Namespace
{
    /**
     * @var PHPUnit_Framework_MockObject_Matcher_Invocation[]
     */
    protected $matchers = [];

    /**
     * @var PHPUnit_Framework_MockObject_Builder_Match[]
     */
    protected $builderMap = [];

    /**
     * @var string[]
     */
    private $configurableMethods = [];

    /**
     * @param array $configurableMethods
     */
    public function __construct(array $configurableMethods)
    {
        $this->configurableMethods = $configurableMethods;
    }

    /**
     * @param PHPUnit_Framework_MockObject_Matcher_Invocation $matcher
     */
    public function addMatcher(PHPUnit_Framework_MockObject_Matcher_Invocation $matcher)
    {
        $this->matchers[] = $matcher;
    }

    /**
     * @since Method available since Release 1.1.0
     */
    public function hasMatchers()
    {
        foreach ($this->matchers as $matcher) {
            if ($matcher->hasMatchers()) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param mixed $id
     *
     * @return bool|null
     */
    public function lookupId($id)
    {
        if (isset($this->builderMap[$id])) {
            return $this->builderMap[$id];
        }

        return;
    }

    /**
     * @param mixed                                      $id
     * @param PHPUnit_Framework_MockObject_Builder_Match $builder
     *
     * @throws PHPUnit_Framework_MockObject_RuntimeException
     */
    public function registerId($id, PHPUnit_Framework_MockObject_Builder_Match $builder)
    {
        if (isset($this->builderMap[$id])) {
            throw new PHPUnit_Framework_MockObject_RuntimeException(
                'Match builder with id <' . $id . '> is already registered.'
            );
        }

        $this->builderMap[$id] = $builder;
    }

    /**
     * @param PHPUnit_Framework_MockObject_Matcher_Invocation $matcher
     *
     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
     */
    public function expects(PHPUnit_Framework_MockObject_Matcher_Invocation $matcher)
    {
        return new PHPUnit_Framework_MockObject_Builder_InvocationMocker(
            $this,
            $matcher,
            $this->configurableMethods
        );
    }

    /**
     * @param PHPUnit_Framework_MockObject_Invocation $invocation
     *
     * @return mixed
     *
     * @throws Exception
     */
    public function invoke(PHPUnit_Framework_MockObject_Invocation $invocation)
    {
        $exception      = null;
        $hasReturnValue = false;
        $returnValue    = null;

        foreach ($this->matchers as $match) {
            try {
                if ($match->matches($invocation)) {
                    $value = $match->invoked($invocation);

                    if (!$hasReturnValue) {
                        $returnValue    = $value;
                        $hasReturnValue = true;
                    }
                }
            } catch (Exception $e) {
                $exception = $e;
            }
        }

        if ($exception !== null) {
            throw $exception;
        }

        if ($hasReturnValue) {
            return $returnValue;
        } elseif (strtolower($invocation->methodName) == '__tostring') {
            return '';
        }

        return $invocation->generateReturnValue();
    }

    /**
     * @param PHPUnit_Framework_MockObject_Invocation $invocation
     *
     * @return bool
     */
    public function matches(PHPUnit_Framework_MockObject_Invocation $invocation)
    {
        foreach ($this->matchers as $matcher) {
            if (!$matcher->matches($invocation)) {
                return false;
            }
        }

        return true;
    }

    /**
     * @return bool
     */
    public function verify()
    {
        foreach ($this->matchers as $matcher) {
            $matcher->verify();
        }
    }
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Interface for classes which can be invoked.
 *
 * The invocation will be taken from a mock object and passed to an object
 * of this class.
 *
 * @since Interface available since Release 1.0.0
 */
interface PHPUnit_Framework_MockObject_Invokable extends PHPUnit_Framework_MockObject_Verifiable
{
    /**
     * Invokes the invocation object $invocation so that it can be checked for
     * expectations or matched against stubs.
     *
     * @param PHPUnit_Framework_MockObject_Invocation $invocation The invocation object passed from mock object
     *
     * @return object
     */
    public function invoke(PHPUnit_Framework_MockObject_Invocation $invocation);

    /**
     * Checks if the invocation matches.
     *
     * @param PHPUnit_Framework_MockObject_Invocation $invocation The invocation object passed from mock object
     *
     * @return bool
     */
    public function matches(PHPUnit_Framework_MockObject_Invocation $invocation);
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Interface for invocations.
 *
 * @since Interface available since Release 1.0.0
 */
interface PHPUnit_Framework_MockObject_Invocation
{
    /**
     * @return mixed Mocked return value.
     */
    public function generateReturnValue();
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Implementation of the Builder pattern for Mock objects.
 *
 * @since File available since Release 1.0.0
 */
class PHPUnit_Framework_MockObject_MockBuilder
{
    /**
     * @var PHPUnit_Framework_TestCase
     */
    private $testCase;

    /**
     * @var string
     */
    private $type;

    /**
     * @var array
     */
    private $methods = [];

    /**
     * @var array
     */
    private $methodsExcept = [];

    /**
     * @var string
     */
    private $mockClassName = '';

    /**
     * @var array
     */
    private $constructorArgs = [];

    /**
     * @var bool
     */
    private $originalConstructor = true;

    /**
     * @var bool
     */
    private $originalClone = true;

    /**
     * @var bool
     */
    private $autoload = true;

    /**
     * @var bool
     */
    private $cloneArguments = false;

    /**
     * @var bool
     */
    private $callOriginalMethods = false;

    /**
     * @var object
     */
    private $proxyTarget = null;

    /**
     * @var bool
     */
    private $allowMockingUnknownTypes = true;

    /**
     * @var PHPUnit_Framework_MockObject_Generator
     */
    private $generator;

    /**
     * @param PHPUnit_Framework_TestCase $testCase
     * @param array|string               $type
     */
    public function __construct(PHPUnit_Framework_TestCase $testCase, $type)
    {
        $this->testCase  = $testCase;
        $this->type      = $type;
        $this->generator = new PHPUnit_Framework_MockObject_Generator;
    }

    /**
     * Creates a mock object using a fluent interface.
     *
     * @return PHPUnit_Framework_MockObject_MockObject
     */
    public function getMock()
    {
        $object = $this->generator->getMock(
            $this->type,
            $this->methods,
            $this->constructorArgs,
            $this->mockClassName,
            $this->originalConstructor,
            $this->originalClone,
            $this->autoload,
            $this->cloneArguments,
            $this->callOriginalMethods,
            $this->proxyTarget,
            $this->allowMockingUnknownTypes
        );

        $this->testCase->registerMockObject($object);

        return $object;
    }

    /**
     * Creates a mock object for an abstract class using a fluent interface.
     *
     * @return PHPUnit_Framework_MockObject_MockObject
     */
    public function getMockForAbstractClass()
    {
        $object = $this->generator->getMockForAbstractClass(
            $this->type,
            $this->constructorArgs,
            $this->mockClassName,
            $this->originalConstructor,
            $this->originalClone,
            $this->autoload,
            $this->methods,
            $this->cloneArguments
        );

        $this->testCase->registerMockObject($object);

        return $object;
    }

    /**
     * Creates a mock object for a trait using a fluent interface.
     *
     * @return PHPUnit_Framework_MockObject_MockObject
     */
    public function getMockForTrait()
    {
        $object = $this->generator->getMockForTrait(
            $this->type,
            $this->constructorArgs,
            $this->mockClassName,
            $this->originalConstructor,
            $this->originalClone,
            $this->autoload,
            $this->methods,
            $this->cloneArguments
        );

        $this->testCase->registerMockObject($object);

        return $object;
    }

    /**
     * Specifies the subset of methods to mock. Default is to mock all of them.
     *
     * @param array|null $methods
     *
     * @return PHPUnit_Framework_MockObject_MockBuilder
     */
    public function setMethods(array $methods = null)
    {
        $this->methods = $methods;

        return $this;
    }

    /**
     * Specifies the subset of methods to not mock. Default is to mock all of them.
     *
     * @param array $methods
     *
     * @return PHPUnit_Framework_MockObject_MockBuilder
     */
    public function setMethodsExcept(array $methods = [])
    {
        $this->methodsExcept = $methods;

        $this->setMethods(
            array_diff(
                $this->generator->getClassMethods($this->type),
                $this->methodsExcept
            )
        );

        return $this;
    }

    /**
     * Specifies the arguments for the constructor.
     *
     * @param array $args
     *
     * @return PHPUnit_Framework_MockObject_MockBuilder
     */
    public function setConstructorArgs(array $args)
    {
        $this->constructorArgs = $args;

        return $this;
    }

    /**
     * Specifies the name for the mock class.
     *
     * @param string $name
     *
     * @return PHPUnit_Framework_MockObject_MockBuilder
     */
    public function setMockClassName($name)
    {
        $this->mockClassName = $name;

        return $this;
    }

    /**
     * Disables the invocation of the original constructor.
     *
     * @return PHPUnit_Framework_MockObject_MockBuilder
     */
    public function disableOriginalConstructor()
    {
        $this->originalConstructor = false;

        return $this;
    }

    /**
     * Enables the invocation of the original constructor.
     *
     * @return PHPUnit_Framework_MockObject_MockBuilder
     *
     * @since  Method available since Release 1.2.0
     */
    public function enableOriginalConstructor()
    {
        $this->originalConstructor = true;

        return $this;
    }

    /**
     * Disables the invocation of the original clone constructor.
     *
     * @return PHPUnit_Framework_MockObject_MockBuilder
     */
    public function disableOriginalClone()
    {
        $this->originalClone = false;

        return $this;
    }

    /**
     * Enables the invocation of the original clone constructor.
     *
     * @return PHPUnit_Framework_MockObject_MockBuilder
     *
     * @since  Method available since Release 1.2.0
     */
    public function enableOriginalClone()
    {
        $this->originalClone = true;

        return $this;
    }

    /**
     * Disables the use of class autoloading while creating the mock object.
     *
     * @return PHPUnit_Framework_MockObject_MockBuilder
     */
    public function disableAutoload()
    {
        $this->autoload = false;

        return $this;
    }

    /**
     * Enables the use of class autoloading while creating the mock object.
     *
     * @return PHPUnit_Framework_MockObject_MockBuilder
     *
     * @since  Method available since Release 1.2.0
     */
    public function enableAutoload()
    {
        $this->autoload = true;

        return $this;
    }

    /**
     * Disables the cloning of arguments passed to mocked methods.
     *
     * @return PHPUnit_Framework_MockObject_MockBuilder
     *
     * @since  Method available since Release 1.2.0
     */
    public function disableArgumentCloning()
    {
        $this->cloneArguments = false;

        return $this;
    }

    /**
     * Enables the cloning of arguments passed to mocked methods.
     *
     * @return PHPUnit_Framework_MockObject_MockBuilder
     *
     * @since  Method available since Release 1.2.0
     */
    public function enableArgumentCloning()
    {
        $this->cloneArguments = true;

        return $this;
    }

    /**
     * Enables the invocation of the original methods.
     *
     * @return PHPUnit_Framework_MockObject_MockBuilder
     *
     * @since  Method available since Release 2.0.0
     */
    public function enableProxyingToOriginalMethods()
    {
        $this->callOriginalMethods = true;

        return $this;
    }

    /**
     * Disables the invocation of the original methods.
     *
     * @return PHPUnit_Framework_MockObject_MockBuilder
     *
     * @since  Method available since Release 2.0.0
     */
    public function disableProxyingToOriginalMethods()
    {
        $this->callOriginalMethods = false;
        $this->proxyTarget         = null;

        return $this;
    }

    /**
     * Sets the proxy target.
     *
     * @param object $object
     *
     * @return PHPUnit_Framework_MockObject_MockBuilder
     *
     * @since  Method available since Release 2.0.0
     */
    public function setProxyTarget($object)
    {
        $this->proxyTarget = $object;

        return $this;
    }

    /**
     * @return PHPUnit_Framework_MockObject_MockBuilder
     *
     * @since  Method available since Release 3.2.0
     */
    public function allowMockingUnknownTypes()
    {
        $this->allowMockingUnknownTypes = true;

        return $this;
    }

    /**
     * @return PHPUnit_Framework_MockObject_MockBuilder
     *
     * @since  Method available since Release 3.2.0
     */
    public function disallowMockingUnknownTypes()
    {
        $this->allowMockingUnknownTypes = false;

        return $this;
    }
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Invocation matcher which looks for a specific method name in the invocations.
 *
 * Checks the method name all incoming invocations, the name is checked against
 * the defined constraint $constraint. If the constraint is met it will return
 * true in matches().
 *
 * @since Class available since Release 1.0.0
 */
class PHPUnit_Framework_MockObject_Matcher_MethodName extends PHPUnit_Framework_MockObject_Matcher_StatelessInvocation
{
    /**
     * @var PHPUnit_Framework_Constraint
     */
    protected $constraint;

    /**
     * @param  PHPUnit_Framework_Constraint|string
     *
     * @throws PHPUnit_Framework_Constraint
     */
    public function __construct($constraint)
    {
        if (!$constraint instanceof PHPUnit_Framework_Constraint) {
            if (!is_string($constraint)) {
                throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
            }

            $constraint = new PHPUnit_Framework_Constraint_IsEqual(
                $constraint,
                0,
                10,
                false,
                true
            );
        }

        $this->constraint = $constraint;
    }

    /**
     * @return string
     */
    public function toString()
    {
        return 'method name ' . $this->constraint->toString();
    }

    /**
     * @param PHPUnit_Framework_MockObject_Invocation $invocation
     *
     * @return bool
     */
    public function matches(PHPUnit_Framework_MockObject_Invocation $invocation)
    {
        return $this->constraint->evaluate($invocation->methodName, '', true);
    }
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Invocation matcher which looks for sets of specific parameters in the invocations.
 *
 * Checks the parameters of the incoming invocations, the parameter list is
 * checked against the defined constraints in $parameters. If the constraint
 * is met it will return true in matches().
 *
 * It takes a list of match groups and and increases a call index after each invocation.
 * So the first invocation uses the first group of constraints, the second the next and so on.
 */
class PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters extends PHPUnit_Framework_MockObject_Matcher_StatelessInvocation
{
    /**
     * @var array
     */
    private $parameterGroups = [];

    /**
     * @var array
     */
    private $invocations = [];

    /**
     * @param array $parameterGroups
     */
    public function __construct(array $parameterGroups)
    {
        foreach ($parameterGroups as $index => $parameters) {
            foreach ($parameters as $parameter) {
                if (!$parameter instanceof PHPUnit_Framework_Constraint) {
                    $parameter = new PHPUnit_Framework_Constraint_IsEqual($parameter);
                }

                $this->parameterGroups[$index][] = $parameter;
            }
        }
    }

    /**
     * @return string
     */
    public function toString()
    {
        $text = 'with consecutive parameters';

        return $text;
    }

    /**
     * @param PHPUnit_Framework_MockObject_Invocation $invocation
     *
     * @return bool
     */
    public function matches(PHPUnit_Framework_MockObject_Invocation $invocation)
    {
        $this->invocations[] = $invocation;
        $callIndex           = count($this->invocations) - 1;

        $this->verifyInvocation($invocation, $callIndex);

        return false;
    }

    public function verify()
    {
        foreach ($this->invocations as $callIndex => $invocation) {
            $this->verifyInvocation($invocation, $callIndex);
        }
    }

    /**
     * Verify a single invocation
     *
     * @param PHPUnit_Framework_MockObject_Invocation $invocation
     * @param int                                     $callIndex
     *
     * @throws PHPUnit_Framework_ExpectationFailedException
     */
    private function verifyInvocation(PHPUnit_Framework_MockObject_Invocation $invocation, $callIndex)
    {
        if (isset($this->parameterGroups[$callIndex])) {
            $parameters = $this->parameterGroups[$callIndex];
        } else {
            // no parameter assertion for this call index
            return;
        }

        if ($invocation === null) {
            throw new PHPUnit_Framework_ExpectationFailedException(
                'Mocked method does not exist.'
            );
        }

        if (count($invocation->parameters) < count($parameters)) {
            throw new PHPUnit_Framework_ExpectationFailedException(
                sprintf(
                    'Parameter count for invocation %s is too low.',
                    $invocation->toString()
                )
            );
        }

        foreach ($parameters as $i => $parameter) {
            $parameter->evaluate(
                $invocation->parameters[$i],
                sprintf(
                    'Parameter %s for invocation #%d %s does not match expected ' .
                    'value.',
                    $i,
                    $callIndex,
                    $invocation->toString()
                )
            );
        }
    }
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Records invocations and provides convenience methods for checking them later
 * on.
 * This abstract class can be implemented by matchers which needs to check the
 * number of times an invocation has occurred.
 *
 * @since Class available since Release 1.0.0
 * @abstract
 */
abstract class PHPUnit_Framework_MockObject_Matcher_InvokedRecorder implements PHPUnit_Framework_MockObject_Matcher_Invocation
{
    /**
     * @var PHPUnit_Framework_MockObject_Invocation[]
     */
    protected $invocations = [];

    /**
     * @return int
     */
    public function getInvocationCount()
    {
        return count($this->invocations);
    }

    /**
     * @return PHPUnit_Framework_MockObject_Invocation[]
     */
    public function getInvocations()
    {
        return $this->invocations;
    }

    /**
     * @return bool
     */
    public function hasBeenInvoked()
    {
        return count($this->invocations) > 0;
    }

    /**
     * @param PHPUnit_Framework_MockObject_Invocation $invocation
     */
    public function invoked(PHPUnit_Framework_MockObject_Invocation $invocation)
    {
        $this->invocations[] = $invocation;
    }

    /**
     * @param PHPUnit_Framework_MockObject_Invocation $invocation
     *
     * @return bool
     */
    public function matches(PHPUnit_Framework_MockObject_Invocation $invocation)
    {
        return true;
    }
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Interface for classes which matches an invocation based on its
 * method name, argument, order or call count.
 *
 * @since Interface available since Release 1.0.0
 */
interface PHPUnit_Framework_MockObject_Matcher_Invocation extends PHPUnit_Framework_SelfDescribing, PHPUnit_Framework_MockObject_Verifiable
{
    /**
     * Registers the invocation $invocation in the object as being invoked.
     * This will only occur after matches() returns true which means the
     * current invocation is the correct one.
     *
     * The matcher can store information from the invocation which can later
     * be checked in verify(), or it can check the values directly and throw
     * and exception if an expectation is not met.
     *
     * If the matcher is a stub it will also have a return value.
     *
     * @param PHPUnit_Framework_MockObject_Invocation $invocation Object containing information on a mocked or stubbed method which was invoked
     *
     * @return mixed
     */
    public function invoked(PHPUnit_Framework_MockObject_Invocation $invocation);

    /**
     * Checks if the invocation $invocation matches the current rules. If it does
     * the matcher will get the invoked() method called which should check if an
     * expectation is met.
     *
     * @param PHPUnit_Framework_MockObject_Invocation $invocation Object containing information on a mocked or stubbed method which was invoked
     *
     * @return bool
     */
    public function matches(PHPUnit_Framework_MockObject_Invocation $invocation);
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Invocation matcher which checks if a method has been invoked zero or more
 * times. This matcher will always match.
 *
 * @since Class available since Release 1.0.0
 */
class PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount extends PHPUnit_Framework_MockObject_Matcher_InvokedRecorder
{
    /**
     * @return string
     */
    public function toString()
    {
        return 'invoked zero or more times';
    }

    /**
     */
    public function verify()
    {
    }
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Invocation matcher which checks if a method has been invoked at least one
 * time.
 *
 * If the number of invocations is 0 it will throw an exception in verify.
 *
 * @since Class available since Release 1.0.0
 */
class PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastOnce extends PHPUnit_Framework_MockObject_Matcher_InvokedRecorder
{
    /**
     * @return string
     */
    public function toString()
    {
        return 'invoked at least once';
    }

    /**
     * Verifies that the current expectation is valid. If everything is OK the
     * code should just return, if not it must throw an exception.
     *
     * @throws PHPUnit_Framework_ExpectationFailedException
     */
    public function verify()
    {
        $count = $this->getInvocationCount();

        if ($count < 1) {
            throw new PHPUnit_Framework_ExpectationFailedException(
                'Expected invocation at least once but it never occurred.'
            );
        }
    }
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Invocation matcher which does not care about previous state from earlier
 * invocations.
 *
 * This abstract class can be implemented by matchers which does not care about
 * state but only the current run-time value of the invocation itself.
 *
 * @since Class available since Release 1.0.0
 * @abstract
 */
abstract class PHPUnit_Framework_MockObject_Matcher_StatelessInvocation implements PHPUnit_Framework_MockObject_Matcher_Invocation
{
    /**
     * Registers the invocation $invocation in the object as being invoked.
     * This will only occur after matches() returns true which means the
     * current invocation is the correct one.
     *
     * The matcher can store information from the invocation which can later
     * be checked in verify(), or it can check the values directly and throw
     * and exception if an expectation is not met.
     *
     * If the matcher is a stub it will also have a return value.
     *
     * @param PHPUnit_Framework_MockObject_Invocation $invocation Object containing information on a mocked or stubbed method which was invoked
     *
     * @return mixed
     */
    public function invoked(PHPUnit_Framework_MockObject_Invocation $invocation)
    {
    }

    /**
     * Checks if the invocation $invocation matches the current rules. If it does
     * the matcher will get the invoked() method called which should check if an
     * expectation is met.
     *
     * @param PHPUnit_Framework_MockObject_Invocation $invocation Object containing information on a mocked or stubbed method which was invoked
     *
     * @return bool
     */
    public function verify()
    {
    }
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Invocation matcher which allows any parameters to a method.
 *
 * @since Class available since Release 1.0.0
 */
class PHPUnit_Framework_MockObject_Matcher_AnyParameters extends PHPUnit_Framework_MockObject_Matcher_StatelessInvocation
{
    /**
     * @return string
     */
    public function toString()
    {
        return 'with any parameters';
    }

    /**
     * @param PHPUnit_Framework_MockObject_Invocation $invocation
     *
     * @return bool
     */
    public function matches(PHPUnit_Framework_MockObject_Invocation $invocation)
    {
        return true;
    }
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Invocation matcher which checks if a method has been invoked at least
 * N times.
 *
 * @since Class available since Release 2.2.0
 */
class PHPUnit_Framework_MockObject_Matcher_InvokedAtMostCount extends PHPUnit_Framework_MockObject_Matcher_InvokedRecorder
{
    /**
     * @var int
     */
    private $allowedInvocations;

    /**
     * @param int $allowedInvocations
     */
    public function __construct($allowedInvocations)
    {
        $this->allowedInvocations = $allowedInvocations;
    }

    /**
     * @return string
     */
    public function toString()
    {
        return 'invoked at most ' . $this->allowedInvocations . ' times';
    }

    /**
     * Verifies that the current expectation is valid. If everything is OK the
     * code should just return, if not it must throw an exception.
     *
     * @throws PHPUnit_Framework_ExpectationFailedException
     */
    public function verify()
    {
        $count = $this->getInvocationCount();

        if ($count > $this->allowedInvocations) {
            throw new PHPUnit_Framework_ExpectationFailedException(
                'Expected invocation at most ' . $this->allowedInvocations .
                ' times but it occurred ' . $count . ' time(s).'
            );
        }
    }
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Invocation matcher which checks if a method was invoked at a certain index.
 *
 * If the expected index number does not match the current invocation index it
 * will not match which means it skips all method and parameter matching. Only
 * once the index is reached will the method and parameter start matching and
 * verifying.
 *
 * If the index is never reached it will throw an exception in index.
 *
 * @since Class available since Release 1.0.0
 */
class PHPUnit_Framework_MockObject_Matcher_InvokedAtIndex implements PHPUnit_Framework_MockObject_Matcher_Invocation
{
    /**
     * @var int
     */
    protected $sequenceIndex;

    /**
     * @var int
     */
    protected $currentIndex = -1;

    /**
     * @param int $sequenceIndex
     */
    public function __construct($sequenceIndex)
    {
        $this->sequenceIndex = $sequenceIndex;
    }

    /**
     * @return string
     */
    public function toString()
    {
        return 'invoked at sequence index ' . $this->sequenceIndex;
    }

    /**
     * @param PHPUnit_Framework_MockObject_Invocation $invocation
     *
     * @return bool
     */
    public function matches(PHPUnit_Framework_MockObject_Invocation $invocation)
    {
        $this->currentIndex++;

        return $this->currentIndex == $this->sequenceIndex;
    }

    /**
     * @param PHPUnit_Framework_MockObject_Invocation $invocation
     */
    public function invoked(PHPUnit_Framework_MockObject_Invocation $invocation)
    {
    }

    /**
     * Verifies that the current expectation is valid. If everything is OK the
     * code should just return, if not it must throw an exception.
     *
     * @throws PHPUnit_Framework_ExpectationFailedException
     */
    public function verify()
    {
        if ($this->currentIndex < $this->sequenceIndex) {
            throw new PHPUnit_Framework_ExpectationFailedException(
                sprintf(
                    'The expected invocation at index %s was never reached.',
                    $this->sequenceIndex
                )
            );
        }
    }
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Invocation matcher which checks if a method has been invoked at least
 * N times.
 *
 * @since Class available since Release 2.2.0
 */
class PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastCount extends PHPUnit_Framework_MockObject_Matcher_InvokedRecorder
{
    /**
     * @var int
     */
    private $requiredInvocations;

    /**
     * @param int $requiredInvocations
     */
    public function __construct($requiredInvocations)
    {
        $this->requiredInvocations = $requiredInvocations;
    }

    /**
     * @return string
     */
    public function toString()
    {
        return 'invoked at least ' . $this->requiredInvocations . ' times';
    }

    /**
     * Verifies that the current expectation is valid. If everything is OK the
     * code should just return, if not it must throw an exception.
     *
     * @throws PHPUnit_Framework_ExpectationFailedException
     */
    public function verify()
    {
        $count = $this->getInvocationCount();

        if ($count < $this->requiredInvocations) {
            throw new PHPUnit_Framework_ExpectationFailedException(
                'Expected invocation at least ' . $this->requiredInvocations .
                ' times but it occurred ' . $count . ' time(s).'
            );
        }
    }
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Invocation matcher which looks for specific parameters in the invocations.
 *
 * Checks the parameters of all incoming invocations, the parameter list is
 * checked against the defined constraints in $parameters. If the constraint
 * is met it will return true in matches().
 *
 * @since Class available since Release 1.0.0
 */
class PHPUnit_Framework_MockObject_Matcher_Parameters extends PHPUnit_Framework_MockObject_Matcher_StatelessInvocation
{
    /**
     * @var PHPUnit_Framework_Constraint[]
     */
    protected $parameters = [];

    /**
     * @var PHPUnit_Framework_MockObject_Invocation
     */
    protected $invocation;

    /**
     * @var PHPUnit_Framework_ExpectationFailedException
     */
    private $parameterVerificationResult;

    /**
     * @param array $parameters
     */
    public function __construct(array $parameters)
    {
        foreach ($parameters as $parameter) {
            if (!($parameter instanceof PHPUnit_Framework_Constraint)) {
                $parameter = new PHPUnit_Framework_Constraint_IsEqual(
                    $parameter
                );
            }

            $this->parameters[] = $parameter;
        }
    }

    /**
     * @return string
     */
    public function toString()
    {
        $text = 'with parameter';

        foreach ($this->parameters as $index => $parameter) {
            if ($index > 0) {
                $text .= ' and';
            }

            $text .= ' ' . $index . ' ' . $parameter->toString();
        }

        return $text;
    }

    /**
     * @param PHPUnit_Framework_MockObject_Invocation $invocation
     *
     * @return bool
     */
    public function matches(PHPUnit_Framework_MockObject_Invocation $invocation)
    {
        $this->invocation                  = $invocation;
        $this->parameterVerificationResult = null;

        try {
            $this->parameterVerificationResult = $this->verify();

            return $this->parameterVerificationResult;
        } catch (PHPUnit_Framework_ExpectationFailedException $e) {
            $this->parameterVerificationResult = $e;

            throw $this->parameterVerificationResult;
        }
    }

    /**
     * Checks if the invocation $invocation matches the current rules. If it
     * does the matcher will get the invoked() method called which should check
     * if an expectation is met.
     *
     * @return bool
     *
     * @throws PHPUnit_Framework_ExpectationFailedException
     */
    public function verify()
    {
        if (isset($this->parameterVerificationResult)) {
            return $this->guardAgainstDuplicateEvaluationOfParameterConstraints();
        }

        if ($this->invocation === null) {
            throw new PHPUnit_Framework_ExpectationFailedException(
                'Mocked method does not exist.'
            );
        }

        if (count($this->invocation->parameters) < count($this->parameters)) {
            $message = 'Parameter count for invocation %s is too low.';

            // The user called `->with($this->anything())`, but may have meant
            // `->withAnyParameters()`.
            //
            // @see https://github.com/sebastianbergmann/phpunit-mock-objects/issues/199
            if (count($this->parameters) === 1 &&
                get_class($this->parameters[0]) === 'PHPUnit_Framework_Constraint_IsAnything') {
                $message .= "\nTo allow 0 or more parameters with any value, omit ->with() or use ->withAnyParameters() instead.";
            }

            throw new PHPUnit_Framework_ExpectationFailedException(
                sprintf($message, $this->invocation->toString())
            );
        }

        foreach ($this->parameters as $i => $parameter) {
            $parameter->evaluate(
                $this->invocation->parameters[$i],
                sprintf(
                    'Parameter %s for invocation %s does not match expected ' .
                    'value.',
                    $i,
                    $this->invocation->toString()
                )
            );
        }

        return true;
    }

    /**
     * @return bool
     *
     * @throws PHPUnit_Framework_ExpectationFailedException
     */
    private function guardAgainstDuplicateEvaluationOfParameterConstraints()
    {
        if ($this->parameterVerificationResult instanceof Exception) {
            throw $this->parameterVerificationResult;
        }

        return (bool) $this->parameterVerificationResult;
    }
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Invocation matcher which checks if a method has been invoked a certain amount
 * of times.
 * If the number of invocations exceeds the value it will immediately throw an
 * exception,
 * If the number is less it will later be checked in verify() and also throw an
 * exception.
 *
 * @since Class available since Release 1.0.0
 */
class PHPUnit_Framework_MockObject_Matcher_InvokedCount extends PHPUnit_Framework_MockObject_Matcher_InvokedRecorder
{
    /**
     * @var int
     */
    protected $expectedCount;

    /**
     * @param int $expectedCount
     */
    public function __construct($expectedCount)
    {
        $this->expectedCount = $expectedCount;
    }

    /**
     * @return bool
     */
    public function isNever()
    {
        return $this->expectedCount == 0;
    }

    /**
     * @return string
     */
    public function toString()
    {
        return 'invoked ' . $this->expectedCount . ' time(s)';
    }

    /**
     * @param PHPUnit_Framework_MockObject_Invocation $invocation
     *
     * @throws PHPUnit_Framework_ExpectationFailedException
     */
    public function invoked(PHPUnit_Framework_MockObject_Invocation $invocation)
    {
        parent::invoked($invocation);

        $count = $this->getInvocationCount();

        if ($count > $this->expectedCount) {
            $message = $invocation->toString() . ' ';

            switch ($this->expectedCount) {
                case 0: {
                    $message .= 'was not expected to be called.';
                }
                break;

                case 1: {
                    $message .= 'was not expected to be called more than once.';
                }
                break;

                default: {
                    $message .= sprintf(
                        'was not expected to be called more than %d times.',
                        $this->expectedCount
                    );
                    }
            }

            throw new PHPUnit_Framework_ExpectationFailedException($message);
        }
    }

    /**
     * Verifies that the current expectation is valid. If everything is OK the
     * code should just return, if not it must throw an exception.
     *
     * @throws PHPUnit_Framework_ExpectationFailedException
     */
    public function verify()
    {
        $count = $this->getInvocationCount();

        if ($count !== $this->expectedCount) {
            throw new PHPUnit_Framework_ExpectationFailedException(
                sprintf(
                    'Method was expected to be called %d times, ' .
                    'actually called %d times.',
                    $this->expectedCount,
                    $count
                )
            );
        }
    }
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Main matcher which defines a full expectation using method, parameter and
 * invocation matchers.
 * This matcher encapsulates all the other matchers and allows the builder to
 * set the specific matchers when the appropriate methods are called (once(),
 * where() etc.).
 *
 * All properties are public so that they can easily be accessed by the builder.
 *
 * @since Class available since Release 1.0.0
 */
class PHPUnit_Framework_MockObject_Matcher implements PHPUnit_Framework_MockObject_Matcher_Invocation
{
    /**
     * @var PHPUnit_Framework_MockObject_Matcher_Invocation
     */
    public $invocationMatcher;

    /**
     * @var mixed
     */
    public $afterMatchBuilderId = null;

    /**
     * @var bool
     */
    public $afterMatchBuilderIsInvoked = false;

    /**
     * @var PHPUnit_Framework_MockObject_Matcher_MethodName
     */
    public $methodNameMatcher = null;

    /**
     * @var PHPUnit_Framework_MockObject_Matcher_Parameters
     */
    public $parametersMatcher = null;

    /**
     * @var PHPUnit_Framework_MockObject_Stub
     */
    public $stub = null;

    /**
     * @param PHPUnit_Framework_MockObject_Matcher_Invocation $invocationMatcher
     */
    public function __construct(PHPUnit_Framework_MockObject_Matcher_Invocation $invocationMatcher)
    {
        $this->invocationMatcher = $invocationMatcher;
    }

    /**
     * @return string
     */
    public function toString()
    {
        $list = [];

        if ($this->invocationMatcher !== null) {
            $list[] = $this->invocationMatcher->toString();
        }

        if ($this->methodNameMatcher !== null) {
            $list[] = 'where ' . $this->methodNameMatcher->toString();
        }

        if ($this->parametersMatcher !== null) {
            $list[] = 'and ' . $this->parametersMatcher->toString();
        }

        if ($this->afterMatchBuilderId !== null) {
            $list[] = 'after ' . $this->afterMatchBuilderId;
        }

        if ($this->stub !== null) {
            $list[] = 'will ' . $this->stub->toString();
        }

        return implode(' ', $list);
    }

    /**
     * @param PHPUnit_Framework_MockObject_Invocation $invocation
     *
     * @return mixed
     */
    public function invoked(PHPUnit_Framework_MockObject_Invocation $invocation)
    {
        if ($this->invocationMatcher === null) {
            throw new PHPUnit_Framework_MockObject_RuntimeException(
                'No invocation matcher is set'
            );
        }

        if ($this->methodNameMatcher === null) {
            throw new PHPUnit_Framework_MockObject_RuntimeException('No method matcher is set');
        }

        if ($this->afterMatchBuilderId !== null) {
            $builder = $invocation->object
                                  ->__phpunit_getInvocationMocker()
                                  ->lookupId($this->afterMatchBuilderId);

            if (!$builder) {
                throw new PHPUnit_Framework_MockObject_RuntimeException(
                    sprintf(
                        'No builder found for match builder identification <%s>',
                        $this->afterMatchBuilderId
                    )
                );
            }

            $matcher = $builder->getMatcher();

            if ($matcher && $matcher->invocationMatcher->hasBeenInvoked()) {
                $this->afterMatchBuilderIsInvoked = true;
            }
        }

        $this->invocationMatcher->invoked($invocation);

        try {
            if ($this->parametersMatcher !== null &&
                !$this->parametersMatcher->matches($invocation)) {
                $this->parametersMatcher->verify();
            }
        } catch (PHPUnit_Framework_ExpectationFailedException $e) {
            throw new PHPUnit_Framework_ExpectationFailedException(
                sprintf(
                    "Expectation failed for %s when %s\n%s",
                    $this->methodNameMatcher->toString(),
                    $this->invocationMatcher->toString(),
                    $e->getMessage()
                ),
                $e->getComparisonFailure()
            );
        }

        if ($this->stub) {
            return $this->stub->invoke($invocation);
        }

        return $invocation->generateReturnValue();
    }

    /**
     * @param PHPUnit_Framework_MockObject_Invocation $invocation
     *
     * @return bool
     */
    public function matches(PHPUnit_Framework_MockObject_Invocation $invocation)
    {
        if ($this->afterMatchBuilderId !== null) {
            $builder = $invocation->object
                                  ->__phpunit_getInvocationMocker()
                                  ->lookupId($this->afterMatchBuilderId);

            if (!$builder) {
                throw new PHPUnit_Framework_MockObject_RuntimeException(
                    sprintf(
                        'No builder found for match builder identification <%s>',
                        $this->afterMatchBuilderId
                    )
                );
            }

            $matcher = $builder->getMatcher();

            if (!$matcher) {
                return false;
            }

            if (!$matcher->invocationMatcher->hasBeenInvoked()) {
                return false;
            }
        }

        if ($this->invocationMatcher === null) {
            throw new PHPUnit_Framework_MockObject_RuntimeException(
                'No invocation matcher is set'
            );
        }

        if ($this->methodNameMatcher === null) {
            throw new PHPUnit_Framework_MockObject_RuntimeException('No method matcher is set');
        }

        if (!$this->invocationMatcher->matches($invocation)) {
            return false;
        }

        try {
            if (!$this->methodNameMatcher->matches($invocation)) {
                return false;
            }
        } catch (PHPUnit_Framework_ExpectationFailedException $e) {
            throw new PHPUnit_Framework_ExpectationFailedException(
                sprintf(
                    "Expectation failed for %s when %s\n%s",
                    $this->methodNameMatcher->toString(),
                    $this->invocationMatcher->toString(),
                    $e->getMessage()
                ),
                $e->getComparisonFailure()
            );
        }

        return true;
    }

    /**
     * @throws PHPUnit_Framework_MockObject_RuntimeException
     * @throws PHPUnit_Framework_ExpectationFailedException
     */
    public function verify()
    {
        if ($this->invocationMatcher === null) {
            throw new PHPUnit_Framework_MockObject_RuntimeException(
                'No invocation matcher is set'
            );
        }

        if ($this->methodNameMatcher === null) {
            throw new PHPUnit_Framework_MockObject_RuntimeException('No method matcher is set');
        }

        try {
            $this->invocationMatcher->verify();

            if ($this->parametersMatcher === null) {
                $this->parametersMatcher = new PHPUnit_Framework_MockObject_Matcher_AnyParameters;
            }

            $invocationIsAny   = $this->invocationMatcher instanceof PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount;
            $invocationIsNever = $this->invocationMatcher instanceof PHPUnit_Framework_MockObject_Matcher_InvokedCount && $this->invocationMatcher->isNever();

            if (!$invocationIsAny && !$invocationIsNever) {
                $this->parametersMatcher->verify();
            }
        } catch (PHPUnit_Framework_ExpectationFailedException $e) {
            throw new PHPUnit_Framework_ExpectationFailedException(
                sprintf(
                    "Expectation failed for %s when %s.\n%s",
                    $this->methodNameMatcher->toString(),
                    $this->invocationMatcher->toString(),
                    PHPUnit_Framework_TestFailure::exceptionToString($e)
                )
            );
        }
    }

    /**
     * @since Method available since Release 1.2.4
     */
    public function hasMatchers()
    {
        if ($this->invocationMatcher !== null &&
            !$this->invocationMatcher instanceof PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount) {
            return true;
        }

        return false;
    }
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use SebastianBergmann\Exporter\Exporter;

/**
 * Stubs a method by returning a user-defined value.
 *
 * @since Class available since Release 1.0.0
 */
class PHPUnit_Framework_MockObject_Stub_Return implements PHPUnit_Framework_MockObject_Stub
{
    protected $value;

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

    public function invoke(PHPUnit_Framework_MockObject_Invocation $invocation)
    {
        return $this->value;
    }

    public function toString()
    {
        $exporter = new Exporter;

        return sprintf(
            'return user-specified value %s',
            $exporter->export($this->value)
        );
    }
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use SebastianBergmann\Exporter\Exporter;

/**
 * Stubs a method by returning a user-defined stack of values.
 *
 * @since Class available since Release 1.0.0
 */
class PHPUnit_Framework_MockObject_Stub_ConsecutiveCalls implements PHPUnit_Framework_MockObject_Stub
{
    protected $stack;
    protected $value;

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

    public function invoke(PHPUnit_Framework_MockObject_Invocation $invocation)
    {
        $this->value = array_shift($this->stack);

        if ($this->value instanceof PHPUnit_Framework_MockObject_Stub) {
            $this->value = $this->value->invoke($invocation);
        }

        return $this->value;
    }

    public function toString()
    {
        $exporter = new Exporter;

        return sprintf(
            'return user-specified value %s',
            $exporter->export($this->value)
        );
    }
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Stubs a method by returning an argument that was passed to the mocked method.
 *
 * @since Class available since Release 1.0.0
 */
class PHPUnit_Framework_MockObject_Stub_ReturnArgument extends PHPUnit_Framework_MockObject_Stub_Return
{
    protected $argumentIndex;

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

    public function invoke(PHPUnit_Framework_MockObject_Invocation $invocation)
    {
        if (isset($invocation->parameters[$this->argumentIndex])) {
            return $invocation->parameters[$this->argumentIndex];
        } else {
            return;
        }
    }

    public function toString()
    {
        return sprintf('return argument #%d', $this->argumentIndex);
    }
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Stubs a method by returning a user-defined reference to a value.
 *
 * @since Class available since Release 3.0.7
 */
class PHPUnit_Framework_MockObject_Stub_ReturnReference extends PHPUnit_Framework_MockObject_Stub_Return
{
    public function __construct(&$value)
    {
        $this->value = &$value;
    }
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Stubs a method by returning a user-defined value.
 *
 * @since Interface available since Release 1.0.0
 */
interface PHPUnit_Framework_MockObject_Stub_MatcherCollection
{
    /**
     * Adds a new matcher to the collection which can be used as an expectation
     * or a stub.
     *
     * @param PHPUnit_Framework_MockObject_Matcher_Invocation $matcher Matcher for invocations to mock objects
     */
    public function addMatcher(PHPUnit_Framework_MockObject_Matcher_Invocation $matcher);
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Stubs a method by returning a value from a map.
 *
 * @since Class available since Release 1.1.0
 */
class PHPUnit_Framework_MockObject_Stub_ReturnValueMap implements PHPUnit_Framework_MockObject_Stub
{
    protected $valueMap;

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

    public function invoke(PHPUnit_Framework_MockObject_Invocation $invocation)
    {
        $parameterCount = count($invocation->parameters);

        foreach ($this->valueMap as $map) {
            if (!is_array($map) || $parameterCount != count($map) - 1) {
                continue;
            }

            $return = array_pop($map);
            if ($invocation->parameters === $map) {
                return $return;
            }
        }

        return;
    }

    public function toString()
    {
        return 'return value from a map';
    }
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * @since Class available since Release 1.0.0
 */
class PHPUnit_Framework_MockObject_Stub_ReturnCallback implements PHPUnit_Framework_MockObject_Stub
{
    protected $callback;

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

    public function invoke(PHPUnit_Framework_MockObject_Invocation $invocation)
    {
        return call_user_func_array($this->callback, $invocation->parameters);
    }

    public function toString()
    {
        if (is_array($this->callback)) {
            if (is_object($this->callback[0])) {
                $class = get_class($this->callback[0]);
                $type  = '->';
            } else {
                $class = $this->callback[0];
                $type  = '::';
            }

            return sprintf(
                'return result of user defined callback %s%s%s() with the ' .
                'passed arguments',
                $class,
                $type,
                $this->callback[1]
            );
        } else {
            return 'return result of user defined callback ' . $this->callback .
                   ' with the passed arguments';
        }
    }
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Stubs a method by returning the current object.
 *
 * @since Class available since Release 1.1.0
 */
class PHPUnit_Framework_MockObject_Stub_ReturnSelf implements PHPUnit_Framework_MockObject_Stub
{
    public function invoke(PHPUnit_Framework_MockObject_Invocation $invocation)
    {
        if (!$invocation instanceof PHPUnit_Framework_MockObject_Invocation_Object) {
            throw new PHPUnit_Framework_MockObject_RuntimeException(
                'The current object can only be returned when mocking an ' .
                'object, not a static class.'
            );
        }

        return $invocation->object;
    }

    public function toString()
    {
        return 'return the current object';
    }
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use SebastianBergmann\Exporter\Exporter;

/**
 * Stubs a method by raising a user-defined exception.
 *
 * @since Class available since Release 1.0.0
 */
class PHPUnit_Framework_MockObject_Stub_Exception implements PHPUnit_Framework_MockObject_Stub
{
    protected $exception;

    public function __construct($exception)
    {
        // TODO Replace check with type declaration when support for PHP 5 is dropped
        if (!$exception instanceof Throwable && !$exception instanceof Exception) {
            throw new PHPUnit_Framework_MockObject_RuntimeException(
                'Exception must be an instance of Throwable (PHP 7) or Exception (PHP 5)'
            );
        }

        $this->exception = $exception;
    }

    public function invoke(PHPUnit_Framework_MockObject_Invocation $invocation)
    {
        throw $this->exception;
    }

    public function toString()
    {
        $exporter = new Exporter;

        return sprintf(
            'raise user-specified exception %s',
            $exporter->export($this->exception)
        );
    }
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * An object that stubs the process of a normal method for a mock object.
 *
 * The stub object will replace the code for the stubbed method and return a
 * specific value instead of the original value.
 *
 * @since Interface available since Release 1.0.0
 */
interface PHPUnit_Framework_MockObject_Stub extends PHPUnit_Framework_SelfDescribing
{
    /**
     * Fakes the processing of the invocation $invocation by returning a
     * specific value.
     *
     * @param PHPUnit_Framework_MockObject_Invocation $invocation The invocation which was mocked and matched by the current method and argument matchers
     *
     * @return mixed
     */
    public function invoke(PHPUnit_Framework_MockObject_Invocation $invocation);
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Doctrine\Instantiator\Instantiator;
use Doctrine\Instantiator\Exception\InvalidArgumentException as InstantiatorInvalidArgumentException;
use Doctrine\Instantiator\Exception\UnexpectedValueException as InstantiatorUnexpectedValueException;

/**
 * Mock Object Code Generator
 *
 * @since Class available since Release 1.0.0
 */
class PHPUnit_Framework_MockObject_Generator
{
    /**
     * @var array
     */
    private static $cache = [];

    /**
     * @var Text_Template[]
     */
    private static $templates = [];

    /**
     * @var array
     */
    private $legacyBlacklistedMethodNames = [
        '__CLASS__'       => true,
        '__DIR__'         => true,
        '__FILE__'        => true,
        '__FUNCTION__'    => true,
        '__LINE__'        => true,
        '__METHOD__'      => true,
        '__NAMESPACE__'   => true,
        '__TRAIT__'       => true,
        '__clone'         => true,
        '__halt_compiler' => true,
        'abstract'        => true,
        'and'             => true,
        'array'           => true,
        'as'              => true,
        'break'           => true,
        'callable'        => true,
        'case'            => true,
        'catch'           => true,
        'class'           => true,
        'clone'           => true,
        'const'           => true,
        'continue'        => true,
        'declare'         => true,
        'default'         => true,
        'die'             => true,
        'do'              => true,
        'echo'            => true,
        'else'            => true,
        'elseif'          => true,
        'empty'           => true,
        'enddeclare'      => true,
        'endfor'          => true,
        'endforeach'      => true,
        'endif'           => true,
        'endswitch'       => true,
        'endwhile'        => true,
        'eval'            => true,
        'exit'            => true,
        'expects'         => true,
        'extends'         => true,
        'final'           => true,
        'for'             => true,
        'foreach'         => true,
        'function'        => true,
        'global'          => true,
        'goto'            => true,
        'if'              => true,
        'implements'      => true,
        'include'         => true,
        'include_once'    => true,
        'instanceof'      => true,
        'insteadof'       => true,
        'interface'       => true,
        'isset'           => true,
        'list'            => true,
        'namespace'       => true,
        'new'             => true,
        'or'              => true,
        'print'           => true,
        'private'         => true,
        'protected'       => true,
        'public'          => true,
        'require'         => true,
        'require_once'    => true,
        'return'          => true,
        'static'          => true,
        'switch'          => true,
        'throw'           => true,
        'trait'           => true,
        'try'             => true,
        'unset'           => true,
        'use'             => true,
        'var'             => true,
        'while'           => true,
        'xor'             => true
    ];

    /**
     * @var array
     */
    private $blacklistedMethodNames = [
        '__CLASS__'       => true,
        '__DIR__'         => true,
        '__FILE__'        => true,
        '__FUNCTION__'    => true,
        '__LINE__'        => true,
        '__METHOD__'      => true,
        '__NAMESPACE__'   => true,
        '__TRAIT__'       => true,
        '__clone'         => true,
        '__halt_compiler' => true,
    ];

    /**
     * Returns a mock object for the specified class.
     *
     * @param array|string $type
     * @param array        $methods
     * @param array        $arguments
     * @param string       $mockClassName
     * @param bool         $callOriginalConstructor
     * @param bool         $callOriginalClone
     * @param bool         $callAutoload
     * @param bool         $cloneArguments
     * @param bool         $callOriginalMethods
     * @param object       $proxyTarget
     * @param bool         $allowMockingUnknownTypes
     *
     * @return PHPUnit_Framework_MockObject_MockObject
     *
     * @throws InvalidArgumentException
     * @throws PHPUnit_Framework_Exception
     * @throws PHPUnit_Framework_MockObject_RuntimeException
     *
     * @since  Method available since Release 1.0.0
     */
    public function getMock($type, $methods = [], array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $cloneArguments = true, $callOriginalMethods = false, $proxyTarget = null, $allowMockingUnknownTypes = true)
    {
        if (!is_array($type) && !is_string($type)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'array or string');
        }

        if (!is_string($mockClassName)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(4, 'string');
        }

        if (!is_array($methods) && !is_null($methods)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'array', $methods);
        }

        if ($type === 'Traversable' || $type === '\\Traversable') {
            $type = 'Iterator';
        }

        if (is_array($type)) {
            $type = array_unique(
                array_map(
                    function ($type) {
                        if ($type === 'Traversable' ||
                            $type === '\\Traversable' ||
                            $type === '\\Iterator') {
                            return 'Iterator';
                        }

                        return $type;
                    },
                    $type
                )
            );
        }

        if (!$allowMockingUnknownTypes) {
            if (is_array($type)) {
                foreach ($type as $_type) {
                    if (!class_exists($_type, $callAutoload) &&
                        !interface_exists($_type, $callAutoload)) {
                        throw new PHPUnit_Framework_MockObject_RuntimeException(
                            sprintf(
                                'Cannot stub or mock class or interface "%s" which does not exist',
                                $_type
                            )
                        );
                    }
                }
            } else {
                if (!class_exists($type, $callAutoload) &&
                    !interface_exists($type, $callAutoload)
                ) {
                    throw new PHPUnit_Framework_MockObject_RuntimeException(
                        sprintf(
                            'Cannot stub or mock class or interface "%s" which does not exist',
                            $type
                        )
                    );
                }
            }
        }

        if (null !== $methods) {
            foreach ($methods as $method) {
                if (!preg_match('~[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*~', $method)) {
                    throw new PHPUnit_Framework_MockObject_RuntimeException(
                        sprintf(
                            'Cannot stub or mock method with invalid name "%s"',
                            $method
                        )
                    );
                }
            }

            if ($methods != array_unique($methods)) {
                throw new PHPUnit_Framework_MockObject_RuntimeException(
                    sprintf(
                        'Cannot stub or mock using a method list that contains duplicates: "%s" (duplicate: "%s")',
                        implode(', ', $methods),
                        implode(', ', array_unique(array_diff_assoc($methods, array_unique($methods))))
                    )
                );
            }
        }

        if ($mockClassName != '' && class_exists($mockClassName, false)) {
            $reflect = new ReflectionClass($mockClassName);

            if (!$reflect->implementsInterface('PHPUnit_Framework_MockObject_MockObject')) {
                throw new PHPUnit_Framework_MockObject_RuntimeException(
                    sprintf(
                        'Class "%s" already exists.',
                        $mockClassName
                    )
                );
            }
        }

        if ($callOriginalConstructor === false && $callOriginalMethods === true) {
            throw new PHPUnit_Framework_MockObject_RuntimeException(
                'Proxying to original methods requires invoking the original constructor'
            );
        }

        $mock = $this->generate(
            $type,
            $methods,
            $mockClassName,
            $callOriginalClone,
            $callAutoload,
            $cloneArguments,
            $callOriginalMethods
        );

        return $this->getObject(
            $mock['code'],
            $mock['mockClassName'],
            $type,
            $callOriginalConstructor,
            $callAutoload,
            $arguments,
            $callOriginalMethods,
            $proxyTarget
        );
    }

    /**
     * @param string       $code
     * @param string       $className
     * @param array|string $type
     * @param bool         $callOriginalConstructor
     * @param bool         $callAutoload
     * @param array        $arguments
     * @param bool         $callOriginalMethods
     * @param object       $proxyTarget
     *
     * @return object
     */
    private function getObject($code, $className, $type = '', $callOriginalConstructor = false, $callAutoload = false, array $arguments = [], $callOriginalMethods = false, $proxyTarget = null)
    {
        $this->evalClass($code, $className);

        if ($callOriginalConstructor &&
            is_string($type) &&
            !interface_exists($type, $callAutoload)) {
            if (count($arguments) == 0) {
                $object = new $className;
            } else {
                $class  = new ReflectionClass($className);
                $object = $class->newInstanceArgs($arguments);
            }
        } else {
            try {
                $instantiator = new Instantiator;
                $object       = $instantiator->instantiate($className);
            } catch (InstantiatorUnexpectedValueException $exception) {
                if ($exception->getPrevious()) {
                    $exception = $exception->getPrevious();
                }

                throw new PHPUnit_Framework_MockObject_RuntimeException(
                    $exception->getMessage()
                );
            } catch (InstantiatorInvalidArgumentException $exception) {
                throw new PHPUnit_Framework_MockObject_RuntimeException(
                    $exception->getMessage()
                );
            }
        }

        if ($callOriginalMethods) {
            if (!is_object($proxyTarget)) {
                if (count($arguments) == 0) {
                    $proxyTarget = new $type;
                } else {
                    $class       = new ReflectionClass($type);
                    $proxyTarget = $class->newInstanceArgs($arguments);
                }
            }

            $object->__phpunit_setOriginalObject($proxyTarget);
        }

        return $object;
    }

    /**
     * @param string $code
     * @param string $className
     */
    private function evalClass($code, $className)
    {
        if (!class_exists($className, false)) {
            eval($code);
        }
    }

    /**
     * Returns a mock object for the specified abstract class with all abstract
     * methods of the class mocked. Concrete methods to mock can be specified with
     * the last parameter
     *
     * @param string $originalClassName
     * @param array  $arguments
     * @param string $mockClassName
     * @param bool   $callOriginalConstructor
     * @param bool   $callOriginalClone
     * @param bool   $callAutoload
     * @param array  $mockedMethods
     * @param bool   $cloneArguments
     *
     * @return PHPUnit_Framework_MockObject_MockObject
     *
     * @throws PHPUnit_Framework_MockObject_RuntimeException
     * @throws PHPUnit_Framework_Exception
     *
     * @since  Method available since Release 1.0.0
     */
    public function getMockForAbstractClass($originalClassName, array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = [], $cloneArguments = true)
    {
        if (!is_string($originalClassName)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        if (!is_string($mockClassName)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(3, 'string');
        }

        if (class_exists($originalClassName, $callAutoload) ||
            interface_exists($originalClassName, $callAutoload)) {
            $reflector = new ReflectionClass($originalClassName);
            $methods   = $mockedMethods;

            foreach ($reflector->getMethods() as $method) {
                if ($method->isAbstract() && !in_array($method->getName(), $methods)) {
                    $methods[] = $method->getName();
                }
            }

            if (empty($methods)) {
                $methods = null;
            }

            return $this->getMock(
                $originalClassName,
                $methods,
                $arguments,
                $mockClassName,
                $callOriginalConstructor,
                $callOriginalClone,
                $callAutoload,
                $cloneArguments
            );
        } else {
            throw new PHPUnit_Framework_MockObject_RuntimeException(
                sprintf('Class "%s" does not exist.', $originalClassName)
            );
        }
    }

    /**
     * Returns a mock object for the specified trait with all abstract methods
     * of the trait mocked. Concrete methods to mock can be specified with the
     * `$mockedMethods` parameter.
     *
     * @param string $traitName
     * @param array  $arguments
     * @param string $mockClassName
     * @param bool   $callOriginalConstructor
     * @param bool   $callOriginalClone
     * @param bool   $callAutoload
     * @param array  $mockedMethods
     * @param bool   $cloneArguments
     *
     * @return PHPUnit_Framework_MockObject_MockObject
     *
     * @throws PHPUnit_Framework_MockObject_RuntimeException
     * @throws PHPUnit_Framework_Exception
     *
     * @since  Method available since Release 1.2.3
     */
    public function getMockForTrait($traitName, array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = [], $cloneArguments = true)
    {
        if (!is_string($traitName)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        if (!is_string($mockClassName)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(3, 'string');
        }

        if (!trait_exists($traitName, $callAutoload)) {
            throw new PHPUnit_Framework_MockObject_RuntimeException(
                sprintf(
                    'Trait "%s" does not exist.',
                    $traitName
                )
            );
        }

        $className = $this->generateClassName(
            $traitName,
            '',
            'Trait_'
        );

        $templateDir   = __DIR__ . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR;
        $classTemplate = $this->getTemplate($templateDir . 'trait_class.tpl');

        $classTemplate->setVar(
            [
                'prologue'   => 'abstract ',
                'class_name' => $className['className'],
                'trait_name' => $traitName
            ]
        );

        $this->evalClass(
            $classTemplate->render(),
            $className['className']
        );

        return $this->getMockForAbstractClass($className['className'], $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $mockedMethods, $cloneArguments);
    }

    /**
     * Returns an object for the specified trait.
     *
     * @param string $traitName
     * @param array  $arguments
     * @param string $traitClassName
     * @param bool   $callOriginalConstructor
     * @param bool   $callOriginalClone
     * @param bool   $callAutoload
     *
     * @return object
     *
     * @throws PHPUnit_Framework_MockObject_RuntimeException
     * @throws PHPUnit_Framework_Exception
     *
     * @since  Method available since Release 1.1.0
     */
    public function getObjectForTrait($traitName, array $arguments = [], $traitClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true)
    {
        if (!is_string($traitName)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        if (!is_string($traitClassName)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(3, 'string');
        }

        if (!trait_exists($traitName, $callAutoload)) {
            throw new PHPUnit_Framework_MockObject_RuntimeException(
                sprintf(
                    'Trait "%s" does not exist.',
                    $traitName
                )
            );
        }

        $className = $this->generateClassName(
            $traitName,
            $traitClassName,
            'Trait_'
        );

        $templateDir   = __DIR__ . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR;
        $classTemplate = $this->getTemplate($templateDir . 'trait_class.tpl');

        $classTemplate->setVar(
            [
                'prologue'   => '',
                'class_name' => $className['className'],
                'trait_name' => $traitName
            ]
        );

        return $this->getObject(
            $classTemplate->render(),
            $className['className']
        );
    }

    /**
     * @param array|string $type
     * @param array        $methods
     * @param string       $mockClassName
     * @param bool         $callOriginalClone
     * @param bool         $callAutoload
     * @param bool         $cloneArguments
     * @param bool         $callOriginalMethods
     *
     * @return array
     */
    public function generate($type, array $methods = null, $mockClassName = '', $callOriginalClone = true, $callAutoload = true, $cloneArguments = true, $callOriginalMethods = false)
    {
        if (is_array($type)) {
            sort($type);
        }

        if ($mockClassName == '') {
            $key = md5(
                is_array($type) ? implode('_', $type) : $type .
                serialize($methods) .
                serialize($callOriginalClone) .
                serialize($cloneArguments) .
                serialize($callOriginalMethods)
            );

            if (isset(self::$cache[$key])) {
                return self::$cache[$key];
            }
        }

        $mock = $this->generateMock(
            $type,
            $methods,
            $mockClassName,
            $callOriginalClone,
            $callAutoload,
            $cloneArguments,
            $callOriginalMethods
        );

        if (isset($key)) {
            self::$cache[$key] = $mock;
        }

        return $mock;
    }

    /**
     * @param string $wsdlFile
     * @param string $className
     * @param array  $methods
     * @param array  $options
     *
     * @return string
     *
     * @throws PHPUnit_Framework_MockObject_RuntimeException
     */
    public function generateClassFromWsdl($wsdlFile, $className, array $methods = [], array $options = [])
    {
        if (!extension_loaded('soap')) {
            throw new PHPUnit_Framework_MockObject_RuntimeException(
                'The SOAP extension is required to generate a mock object from WSDL.'
            );
        }

        $options  = array_merge($options, ['cache_wsdl' => WSDL_CACHE_NONE]);
        $client   = new SoapClient($wsdlFile, $options);
        $_methods = array_unique($client->__getFunctions());
        unset($client);

        sort($_methods);

        $templateDir    = __DIR__ . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR;
        $methodTemplate = $this->getTemplate($templateDir . 'wsdl_method.tpl');
        $methodsBuffer  = '';

        foreach ($_methods as $method) {
            $nameStart = strpos($method, ' ') + 1;
            $nameEnd   = strpos($method, '(');
            $name      = substr($method, $nameStart, $nameEnd - $nameStart);

            if (empty($methods) || in_array($name, $methods)) {
                $args    = explode(
                    ',',
                    substr(
                        $method,
                        $nameEnd + 1,
                        strpos($method, ')') - $nameEnd - 1
                    )
                );
                $numArgs = count($args);

                for ($i = 0; $i < $numArgs; $i++) {
                    $args[$i] = substr($args[$i], strpos($args[$i], '$'));
                }

                $methodTemplate->setVar(
                    [
                        'method_name' => $name,
                        'arguments'   => implode(', ', $args)
                    ]
                );

                $methodsBuffer .= $methodTemplate->render();
            }
        }

        $optionsBuffer = 'array(';

        foreach ($options as $key => $value) {
            $optionsBuffer .= $key . ' => ' . $value;
        }

        $optionsBuffer .= ')';

        $classTemplate = $this->getTemplate($templateDir . 'wsdl_class.tpl');
        $namespace     = '';

        if (strpos($className, '\\') !== false) {
            $parts     = explode('\\', $className);
            $className = array_pop($parts);
            $namespace = 'namespace ' . implode('\\', $parts) . ';' . "\n\n";
        }

        $classTemplate->setVar(
            [
                'namespace'  => $namespace,
                'class_name' => $className,
                'wsdl'       => $wsdlFile,
                'options'    => $optionsBuffer,
                'methods'    => $methodsBuffer
            ]
        );

        return $classTemplate->render();
    }

    /**
     * @param array|string $type
     * @param array|null   $methods
     * @param string       $mockClassName
     * @param bool         $callOriginalClone
     * @param bool         $callAutoload
     * @param bool         $cloneArguments
     * @param bool         $callOriginalMethods
     *
     * @return array
     *
     * @throws PHPUnit_Framework_MockObject_RuntimeException
     */
    private function generateMock($type, $methods, $mockClassName, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods)
    {
        $methodReflections   = [];
        $templateDir         = __DIR__ . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR;
        $classTemplate       = $this->getTemplate($templateDir . 'mocked_class.tpl');

        $additionalInterfaces = [];
        $cloneTemplate        = '';
        $isClass              = false;
        $isInterface          = false;
        $isMultipleInterfaces = false;

        if (is_array($type)) {
            foreach ($type as $_type) {
                if (!interface_exists($_type, $callAutoload)) {
                    throw new PHPUnit_Framework_MockObject_RuntimeException(
                        sprintf(
                            'Interface "%s" does not exist.',
                            $_type
                        )
                    );
                }

                $isMultipleInterfaces = true;

                $additionalInterfaces[] = $_type;
                $typeClass              = new ReflectionClass($this->generateClassName(
                    $_type,
                    $mockClassName,
                    'Mock_'
                    )['fullClassName']
                );

                foreach ($this->getClassMethods($_type) as $method) {
                    if (in_array($method, $methods)) {
                        throw new PHPUnit_Framework_MockObject_RuntimeException(
                            sprintf(
                                'Duplicate method "%s" not allowed.',
                                $method
                            )
                        );
                    }

                    $methodReflections[$method] = $typeClass->getMethod($method);
                    $methods[]                  = $method;
                }
            }
        }

        $mockClassName = $this->generateClassName(
            $type,
            $mockClassName,
            'Mock_'
        );

        if (class_exists($mockClassName['fullClassName'], $callAutoload)) {
            $isClass = true;
        } elseif (interface_exists($mockClassName['fullClassName'], $callAutoload)) {
            $isInterface = true;
        }

        if (!$isClass && !$isInterface) {
            $prologue = 'class ' . $mockClassName['originalClassName'] . "\n{\n}\n\n";

            if (!empty($mockClassName['namespaceName'])) {
                $prologue = 'namespace ' . $mockClassName['namespaceName'] .
                            " {\n\n" . $prologue . "}\n\n" .
                            "namespace {\n\n";

                $epilogue = "\n\n}";
            }

            $cloneTemplate = $this->getTemplate($templateDir . 'mocked_clone.tpl');
        } else {
            $class = new ReflectionClass($mockClassName['fullClassName']);

            if ($class->isFinal()) {
                throw new PHPUnit_Framework_MockObject_RuntimeException(
                    sprintf(
                        'Class "%s" is declared "final" and cannot be mocked.',
                        $mockClassName['fullClassName']
                    )
                );
            }

            if ($class->hasMethod('__clone')) {
                $cloneMethod = $class->getMethod('__clone');

                if (!$cloneMethod->isFinal()) {
                    if ($callOriginalClone && !$isInterface) {
                        $cloneTemplate = $this->getTemplate($templateDir . 'unmocked_clone.tpl');
                    } else {
                        $cloneTemplate = $this->getTemplate($templateDir . 'mocked_clone.tpl');
                    }
                }
            } else {
                $cloneTemplate = $this->getTemplate($templateDir . 'mocked_clone.tpl');
            }
        }

        if (is_object($cloneTemplate)) {
            $cloneTemplate = $cloneTemplate->render();
        }

        if (is_array($methods) && empty($methods) &&
            ($isClass || $isInterface)) {
            $methods = $this->getClassMethods($mockClassName['fullClassName']);
        }

        if (!is_array($methods)) {
            $methods = [];
        }

        $mockedMethods = '';
        $configurable  = [];

        foreach ($methods as $methodName) {
            if ($methodName != '__construct' && $methodName != '__clone') {
                $configurable[] = strtolower($methodName);
            }
        }

        if (isset($class)) {
            // https://github.com/sebastianbergmann/phpunit-mock-objects/issues/103
            if ($isInterface && $class->implementsInterface('Traversable') &&
                !$class->implementsInterface('Iterator') &&
                !$class->implementsInterface('IteratorAggregate')) {
                $additionalInterfaces[] = 'Iterator';
                $methods                = array_merge($methods, $this->getClassMethods('Iterator'));
            }

            foreach ($methods as $methodName) {
                try {
                    $method = $class->getMethod($methodName);

                    if ($this->canMockMethod($method)) {
                        $mockedMethods .= $this->generateMockedMethodDefinitionFromExisting(
                            $templateDir,
                            $method,
                            $cloneArguments,
                            $callOriginalMethods
                        );
                    }
                } catch (ReflectionException $e) {
                    $mockedMethods .= $this->generateMockedMethodDefinition(
                        $templateDir,
                        $mockClassName['fullClassName'],
                        $methodName,
                        $cloneArguments
                    );
                }
            }
        } elseif ($isMultipleInterfaces) {
            foreach ($methods as $methodName) {
                if ($this->canMockMethod($methodReflections[$methodName])) {
                    $mockedMethods .= $this->generateMockedMethodDefinitionFromExisting(
                        $templateDir,
                        $methodReflections[$methodName],
                        $cloneArguments,
                        $callOriginalMethods
                    );
                }
            }
        } else {
            foreach ($methods as $methodName) {
                $mockedMethods .= $this->generateMockedMethodDefinition(
                    $templateDir,
                    $mockClassName['fullClassName'],
                    $methodName,
                    $cloneArguments
                );
            }
        }

        $method = '';

        if (!in_array('method', $methods) && (!isset($class) || !$class->hasMethod('method'))) {
            $methodTemplate = $this->getTemplate($templateDir . 'mocked_class_method.tpl');

            $method = $methodTemplate->render();
        }

        $classTemplate->setVar(
            [
                'prologue'          => isset($prologue) ? $prologue : '',
                'epilogue'          => isset($epilogue) ? $epilogue : '',
                'class_declaration' => $this->generateMockClassDeclaration(
                    $mockClassName,
                    $isInterface,
                    $additionalInterfaces
                ),
                'clone'             => $cloneTemplate,
                'mock_class_name'   => $mockClassName['className'],
                'mocked_methods'    => $mockedMethods,
                'method'            => $method,
                'configurable'      => '[' . implode(', ', array_map(function ($m) { return '\'' . $m . '\'';}, $configurable)) . ']'
            ]
        );

        return [
          'code'          => $classTemplate->render(),
          'mockClassName' => $mockClassName['className']
        ];
    }

    /**
     * @param array|string $type
     * @param string       $className
     * @param string       $prefix
     *
     * @return array
     */
    private function generateClassName($type, $className, $prefix)
    {
        if (is_array($type)) {
            $type = implode('_', $type);
        }

        if ($type[0] == '\\') {
            $type = substr($type, 1);
        }

        $classNameParts = explode('\\', $type);

        if (count($classNameParts) > 1) {
            $type          = array_pop($classNameParts);
            $namespaceName = implode('\\', $classNameParts);
            $fullClassName = $namespaceName . '\\' . $type;
        } else {
            $namespaceName = '';
            $fullClassName = $type;
        }

        if ($className == '') {
            do {
                $className = $prefix . $type . '_' .
                             substr(md5(mt_rand()), 0, 8);
            } while (class_exists($className, false));
        }

        return [
          'className'         => $className,
          'originalClassName' => $type,
          'fullClassName'     => $fullClassName,
          'namespaceName'     => $namespaceName
        ];
    }

    /**
     * @param array $mockClassName
     * @param bool  $isInterface
     * @param array $additionalInterfaces
     *
     * @return array
     */
    private function generateMockClassDeclaration(array $mockClassName, $isInterface, array $additionalInterfaces = [])
    {
        $buffer = 'class ';

        $additionalInterfaces[] = 'PHPUnit_Framework_MockObject_MockObject';
        $interfaces             = implode(', ', $additionalInterfaces);

        if ($isInterface) {
            $buffer .= sprintf(
                '%s implements %s',
                $mockClassName['className'],
                $interfaces
            );

            if (!in_array($mockClassName['originalClassName'], $additionalInterfaces)) {
                $buffer .= ', ';

                if (!empty($mockClassName['namespaceName'])) {
                    $buffer .= $mockClassName['namespaceName'] . '\\';
                }

                $buffer .= $mockClassName['originalClassName'];
            }
        } else {
            $buffer .= sprintf(
                '%s extends %s%s implements %s',
                $mockClassName['className'],
                !empty($mockClassName['namespaceName']) ? $mockClassName['namespaceName'] . '\\' : '',
                $mockClassName['originalClassName'],
                $interfaces
            );
        }

        return $buffer;
    }

    /**
     * @param string           $templateDir
     * @param ReflectionMethod $method
     * @param bool             $cloneArguments
     * @param bool             $callOriginalMethods
     *
     * @return string
     */
    private function generateMockedMethodDefinitionFromExisting($templateDir, ReflectionMethod $method, $cloneArguments, $callOriginalMethods)
    {
        if ($method->isPrivate()) {
            $modifier = 'private';
        } elseif ($method->isProtected()) {
            $modifier = 'protected';
        } else {
            $modifier = 'public';
        }

        if ($method->isStatic()) {
            $modifier .= ' static';
        }

        if ($method->returnsReference()) {
            $reference = '&';
        } else {
            $reference = '';
        }

        if ($this->hasReturnType($method)) {
            $returnType = (string) $method->getReturnType();
        } else {
            $returnType = '';
        }

        if (preg_match('#\*[ \t]*+@deprecated[ \t]*+(.*?)\r?+\n[ \t]*+\*(?:[ \t]*+@|/$)#s', $method->getDocComment(), $deprecation)) {
            $deprecation = trim(preg_replace('#[ \t]*\r?\n[ \t]*+\*[ \t]*+#', ' ', $deprecation[1]));
        } else {
            $deprecation = false;
        }

        return $this->generateMockedMethodDefinition(
            $templateDir,
            $method->getDeclaringClass()->getName(),
            $method->getName(),
            $cloneArguments,
            $modifier,
            $this->getMethodParameters($method),
            $this->getMethodParameters($method, true),
            $returnType,
            $reference,
            $callOriginalMethods,
            $method->isStatic(),
            $deprecation,
            $this->allowsReturnNull($method)
        );
    }

    /**
     * @param string       $templateDir
     * @param string       $className
     * @param string       $methodName
     * @param bool         $cloneArguments
     * @param string       $modifier
     * @param string       $arguments_decl
     * @param string       $arguments_call
     * @param string       $return_type
     * @param string       $reference
     * @param bool         $callOriginalMethods
     * @param bool         $static
     * @param string|false $deprecation
     * @param bool         $allowsReturnNull
     *
     * @return string
     */
    private function generateMockedMethodDefinition($templateDir, $className, $methodName, $cloneArguments = true, $modifier = 'public', $arguments_decl = '', $arguments_call = '', $return_type = '', $reference = '', $callOriginalMethods = false, $static = false, $deprecation = false, $allowsReturnNull = false)
    {
        if ($static) {
            $templateFile = 'mocked_static_method.tpl';
        } else {
            if ($return_type === 'void') {
                $templateFile = sprintf(
                    '%s_method_void.tpl',
                    $callOriginalMethods ? 'proxied' : 'mocked'
                );
            } else {
                $templateFile = sprintf(
                    '%s_method.tpl',
                    $callOriginalMethods ? 'proxied' : 'mocked'
                );
            }
        }

        // Mocked interfaces returning 'self' must explicitly declare the
        // interface name as the return type. See
        // https://bugs.php.net/bug.php?id=70722
        if ($return_type === 'self') {
            $return_type = $className;
        }

        if (false !== $deprecation) {
            $deprecation         = "The $className::$methodName method is deprecated ($deprecation).";
            $deprecationTemplate = $this->getTemplate($templateDir . 'deprecation.tpl');

            $deprecationTemplate->setVar(
                [
                    'deprecation' => var_export($deprecation, true),
                ]
            );

            $deprecation = $deprecationTemplate->render();
        }

        $template = $this->getTemplate($templateDir . $templateFile);

        $template->setVar(
            [
                'arguments_decl'  => $arguments_decl,
                'arguments_call'  => $arguments_call,
                'return_delim'    => $return_type ? ': ' : '',
                'return_type'     => $allowsReturnNull ? '?' . $return_type : $return_type,
                'arguments_count' => !empty($arguments_call) ? count(explode(',', $arguments_call)) : 0,
                'class_name'      => $className,
                'method_name'     => $methodName,
                'modifier'        => $modifier,
                'reference'       => $reference,
                'clone_arguments' => $cloneArguments ? 'true' : 'false',
                'deprecation'     => $deprecation
            ]
        );

        return $template->render();
    }

    /**
     * @param ReflectionMethod $method
     *
     * @return bool
     */
    private function canMockMethod(ReflectionMethod $method)
    {
        if ($method->isConstructor() ||
            $method->isFinal() ||
            $method->isPrivate() ||
            $this->isMethodNameBlacklisted($method->getName())) {
            return false;
        }

        return true;
    }

    /**
     * Returns whether i method name is blacklisted
     *
     * Since PHP 7 the only names that are still reserved for method names are the ones that start with an underscore
     *
     * @param string $name
     *
     * @return bool
     */
    private function isMethodNameBlacklisted($name)
    {
        if (PHP_MAJOR_VERSION < 7 && isset($this->legacyBlacklistedMethodNames[$name])) {
            return true;
        }

        if (PHP_MAJOR_VERSION >= 7 && isset($this->blacklistedMethodNames[$name])) {
            return true;
        }

        return false;
    }

    /**
     * Returns the parameters of a function or method.
     *
     * @param ReflectionMethod $method
     * @param bool             $forCall
     *
     * @return string
     *
     * @throws PHPUnit_Framework_MockObject_RuntimeException
     *
     * @since  Method available since Release 2.0.0
     */
    private function getMethodParameters(ReflectionMethod $method, $forCall = false)
    {
        $parameters = [];

        foreach ($method->getParameters() as $i => $parameter) {
            $name = '$' . $parameter->getName();

            /* Note: PHP extensions may use empty names for reference arguments
             * or "..." for methods taking a variable number of arguments.
             */
            if ($name === '$' || $name === '$...') {
                $name = '$arg' . $i;
            }

            if ($this->isVariadic($parameter)) {
                if ($forCall) {
                    continue;
                } else {
                    $name = '...' . $name;
                }
            }

            $nullable        = '';
            $default         = '';
            $reference       = '';
            $typeDeclaration = '';

            if (!$forCall) {
                if ($this->hasType($parameter) && (string) $parameter->getType() !== 'self') {
                    if (version_compare(PHP_VERSION, '7.1', '>=') && $parameter->allowsNull() && !$parameter->isVariadic()) {
                        $nullable = '?';
                    }

                    $typeDeclaration = (string) $parameter->getType() . ' ';
                } elseif ($parameter->isArray()) {
                    $typeDeclaration = 'array ';
                } elseif ($parameter->isCallable()) {
                    $typeDeclaration = 'callable ';
                } else {
                    try {
                        $class = $parameter->getClass();
                    } catch (ReflectionException $e) {
                        throw new PHPUnit_Framework_MockObject_RuntimeException(
                            sprintf(
                                'Cannot mock %s::%s() because a class or ' .
                                'interface used in the signature is not loaded',
                                $method->getDeclaringClass()->getName(),
                                $method->getName()
                            ),
                            0,
                            $e
                        );
                    }

                    if ($class !== null) {
                        $typeDeclaration = $class->getName() . ' ';
                    }
                }

                if (!$this->isVariadic($parameter)) {
                    if ($parameter->isDefaultValueAvailable()) {
                        $value   = $parameter->getDefaultValue();
                        $default = ' = ' . var_export($value, true);
                    } elseif ($parameter->isOptional()) {
                        $default = ' = null';
                    }
                }
            }

            if ($parameter->isPassedByReference()) {
                $reference = '&';
            }

            $parameters[] = $nullable . $typeDeclaration . $reference . $name . $default;
        }

        return implode(', ', $parameters);
    }

    /**
     * @param ReflectionParameter $parameter
     *
     * @return bool
     *
     * @since  Method available since Release 2.2.1
     */
    private function isVariadic(ReflectionParameter $parameter)
    {
        return method_exists(ReflectionParameter::class, 'isVariadic') && $parameter->isVariadic();
    }

    /**
     * @param ReflectionParameter $parameter
     *
     * @return bool
     *
     * @since  Method available since Release 2.3.4
     */
    private function hasType(ReflectionParameter $parameter)
    {
        return method_exists(ReflectionParameter::class, 'hasType') && $parameter->hasType();
    }

    /**
     * @param ReflectionMethod $method
     *
     * @return bool
     */
    private function hasReturnType(ReflectionMethod $method)
    {
        return method_exists(ReflectionMethod::class, 'hasReturnType') && $method->hasReturnType();
    }

    /**
     * @param ReflectionMethod $method
     *
     * @return bool
     */
    private function allowsReturnNull(ReflectionMethod $method)
    {
        return method_exists(ReflectionMethod::class, 'getReturnType')
            && method_exists(ReflectionType::class, 'allowsNull')
            && $method->hasReturnType()
            && $method->getReturnType()->allowsNull();
    }

    /**
     * @param string $className
     *
     * @return array
     *
     * @since  Method available since Release 2.3.2
     */
    public function getClassMethods($className)
    {
        $class   = new ReflectionClass($className);
        $methods = [];

        foreach ($class->getMethods() as $method) {
            if ($method->isPublic() || $method->isAbstract()) {
                $methods[] = $method->getName();
            }
        }

        return $methods;
    }

    /**
     * @param string $filename
     *
     * @return Text_Template
     *
     * @since  Method available since Release 3.2.4
     */
    private function getTemplate($filename)
    {
        if (!isset(self::$templates[$filename])) {
            self::$templates[$filename] = new Text_Template($filename);
        }

        return self::$templates[$filename];
    }
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Builder for mocked or stubbed invocations.
 *
 * Provides methods for building expectations without having to resort to
 * instantiating the various matchers manually. These methods also form a
 * more natural way of reading the expectation. This class should be together
 * with the test case PHPUnit_Framework_MockObject_TestCase.
 *
 * @since Class available since Release 1.0.0
 */
class PHPUnit_Framework_MockObject_Builder_InvocationMocker implements PHPUnit_Framework_MockObject_Builder_MethodNameMatch
{
    /**
     * @var PHPUnit_Framework_MockObject_Stub_MatcherCollection
     */
    protected $collection;

    /**
     * @var PHPUnit_Framework_MockObject_Matcher
     */
    protected $matcher;

    /**
     * @var string[]
     */
    private $configurableMethods = [];

    /**
     * @param PHPUnit_Framework_MockObject_Stub_MatcherCollection $collection
     * @param PHPUnit_Framework_MockObject_Matcher_Invocation     $invocationMatcher
     * @param array                                               $configurableMethods
     */
    public function __construct(PHPUnit_Framework_MockObject_Stub_MatcherCollection $collection, PHPUnit_Framework_MockObject_Matcher_Invocation $invocationMatcher, array $configurableMethods)
    {
        $this->collection = $collection;
        $this->matcher    = new PHPUnit_Framework_MockObject_Matcher(
            $invocationMatcher
        );

        $this->collection->addMatcher($this->matcher);

        $this->configurableMethods = $configurableMethods;
    }

    /**
     * @return PHPUnit_Framework_MockObject_Matcher
     */
    public function getMatcher()
    {
        return $this->matcher;
    }

    /**
     * @param mixed $id
     *
     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
     */
    public function id($id)
    {
        $this->collection->registerId($id, $this);

        return $this;
    }

    /**
     * @param PHPUnit_Framework_MockObject_Stub $stub
     *
     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
     */
    public function will(PHPUnit_Framework_MockObject_Stub $stub)
    {
        $this->matcher->stub = $stub;

        return $this;
    }

    /**
     * @param mixed $value
     * @param mixed $nextValues, ...
     *
     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
     */
    public function willReturn($value, ...$nextValues)
    {
        $stub = count($nextValues) === 0 ?
            new PHPUnit_Framework_MockObject_Stub_Return($value) :
            new PHPUnit_Framework_MockObject_Stub_ConsecutiveCalls(
               array_merge([$value], $nextValues)
            );

        return $this->will($stub);
    }

    /**
     * @param mixed $reference
     *
     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
     */
    public function willReturnReference(&$reference)
    {
        $stub = new PHPUnit_Framework_MockObject_Stub_ReturnReference($reference);

        return $this->will($stub);
    }

    /**
     * @param array $valueMap
     *
     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
     */
    public function willReturnMap(array $valueMap)
    {
        $stub = new PHPUnit_Framework_MockObject_Stub_ReturnValueMap(
            $valueMap
        );

        return $this->will($stub);
    }

    /**
     * @param mixed $argumentIndex
     *
     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
     */
    public function willReturnArgument($argumentIndex)
    {
        $stub = new PHPUnit_Framework_MockObject_Stub_ReturnArgument(
            $argumentIndex
        );

        return $this->will($stub);
    }

    /**
     * @param callable $callback
     *
     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
     */
    public function willReturnCallback($callback)
    {
        $stub = new PHPUnit_Framework_MockObject_Stub_ReturnCallback(
            $callback
        );

        return $this->will($stub);
    }

    /**
     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
     */
    public function willReturnSelf()
    {
        $stub = new PHPUnit_Framework_MockObject_Stub_ReturnSelf;

        return $this->will($stub);
    }

    /**
     * @param mixed $values, ...
     *
     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
     */
    public function willReturnOnConsecutiveCalls(...$values)
    {
        $stub = new PHPUnit_Framework_MockObject_Stub_ConsecutiveCalls($values);

        return $this->will($stub);
    }

    /**
     * @param Exception $exception
     *
     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
     */
    public function willThrowException(Exception $exception)
    {
        $stub = new PHPUnit_Framework_MockObject_Stub_Exception($exception);

        return $this->will($stub);
    }

    /**
     * @param mixed $id
     *
     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
     */
    public function after($id)
    {
        $this->matcher->afterMatchBuilderId = $id;

        return $this;
    }

    /**
     * Validate that a parameters matcher can be defined, throw exceptions otherwise.
     *
     * @throws PHPUnit_Framework_MockObject_RuntimeException
     */
    private function canDefineParameters()
    {
        if ($this->matcher->methodNameMatcher === null) {
            throw new PHPUnit_Framework_MockObject_RuntimeException(
                'Method name matcher is not defined, cannot define parameter ' .
                'matcher without one'
            );
        }

        if ($this->matcher->parametersMatcher !== null) {
            throw new PHPUnit_Framework_MockObject_RuntimeException(
                'Parameter matcher is already defined, cannot redefine'
            );
        }
    }

    /**
     * @param  array ...$arguments
     *
     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
     */
    public function with(...$arguments)
    {
        $this->canDefineParameters();

        $this->matcher->parametersMatcher = new PHPUnit_Framework_MockObject_Matcher_Parameters($arguments);

        return $this;
    }

    /**
     * @param  array ...$arguments
     *
     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
     */
    public function withConsecutive(...$arguments)
    {
        $this->canDefineParameters();

        $this->matcher->parametersMatcher = new PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters($arguments);

        return $this;
    }

    /**
     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
     */
    public function withAnyParameters()
    {
        $this->canDefineParameters();

        $this->matcher->parametersMatcher = new PHPUnit_Framework_MockObject_Matcher_AnyParameters;

        return $this;
    }

    /**
     * @param PHPUnit_Framework_Constraint|string $constraint
     *
     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
     */
    public function method($constraint)
    {
        if ($this->matcher->methodNameMatcher !== null) {
            throw new PHPUnit_Framework_MockObject_RuntimeException(
                'Method name matcher is already defined, cannot redefine'
            );
        }

        if (is_string($constraint) && !in_array(strtolower($constraint), $this->configurableMethods)) {
            throw new PHPUnit_Framework_MockObject_RuntimeException(
                sprintf(
                    'Trying to configure method "%s" which cannot be configured because it does not exist, has not been specified, is final, or is static',
                    $constraint
                )
            );
        }

        $this->matcher->methodNameMatcher = new PHPUnit_Framework_MockObject_Matcher_MethodName($constraint);

        return $this;
    }
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Builder interface for parameter matchers.
 *
 * @since Interface available since Release 1.0.0
 */
interface PHPUnit_Framework_MockObject_Builder_ParametersMatch extends PHPUnit_Framework_MockObject_Builder_Match
{
    /**
     * Sets the parameters to match for, each parameter to this funtion will
     * be part of match. To perform specific matches or constraints create a
     * new PHPUnit_Framework_Constraint and use it for the parameter.
     * If the parameter value is not a constraint it will use the
     * PHPUnit_Framework_Constraint_IsEqual for the value.
     *
     * Some examples:
     * <code>
     * // match first parameter with value 2
     * $b->with(2);
     * // match first parameter with value 'smock' and second identical to 42
     * $b->with('smock', new PHPUnit_Framework_Constraint_IsEqual(42));
     * </code>
     *
     * @return PHPUnit_Framework_MockObject_Builder_ParametersMatch
     */
    public function with(...$arguments);

    /**
     * Sets a matcher which allows any kind of parameters.
     *
     * Some examples:
     * <code>
     * // match any number of parameters
     * $b->withAnyParameters();
     * </code>
     *
     * @return PHPUnit_Framework_MockObject_Matcher_AnyParameters
     */
    public function withAnyParameters();
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Interface for builders which can register builders with a given identification.
 *
 * This interface relates to PHPUnit_Framework_MockObject_Builder_Identity.
 *
 * @since Interface available since Release 1.0.0
 */
interface PHPUnit_Framework_MockObject_Builder_Namespace
{
    /**
     * Looks up the match builder with identification $id and returns it.
     *
     * @param string $id The identification of the match builder
     *
     * @return PHPUnit_Framework_MockObject_Builder_Match
     */
    public function lookupId($id);

    /**
     * Registers the match builder $builder with the identification $id. The
     * builder can later be looked up using lookupId() to figure out if it
     * has been invoked.
     *
     * @param string                                     $id      The identification of the match builder
     * @param PHPUnit_Framework_MockObject_Builder_Match $builder The builder which is being registered
     */
    public function registerId($id, PHPUnit_Framework_MockObject_Builder_Match $builder);
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Builder interface for unique identifiers.
 *
 * Defines the interface for recording unique identifiers. The identifiers
 * can be used to define the invocation order of expectations. The expectation
 * is recorded using id() and then defined in order using
 * PHPUnit_Framework_MockObject_Builder_Match::after().
 *
 * @since Interface available since Release 1.0.0
 */
interface PHPUnit_Framework_MockObject_Builder_Identity
{
    /**
     * Sets the identification of the expectation to $id.
     *
     * @note The identifier is unique per mock object.
     *
     * @param string $id Unique identification of expectation.
     */
    public function id($id);
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Builder interface for invocation order matches.
 *
 * @since Interface available since Release 1.0.0
 */
interface PHPUnit_Framework_MockObject_Builder_Match extends PHPUnit_Framework_MockObject_Builder_Stub
{
    /**
     * Defines the expectation which must occur before the current is valid.
     *
     * @param string $id The identification of the expectation that should
     *                   occur before this one.
     *
     * @return PHPUnit_Framework_MockObject_Builder_Stub
     */
    public function after($id);
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Builder interface for stubs which are actions replacing an invocation.
 *
 * @since Interface available since Release 1.0.0
 */
interface PHPUnit_Framework_MockObject_Builder_Stub extends PHPUnit_Framework_MockObject_Builder_Identity
{
    /**
     * Stubs the matching method with the stub object $stub. Any invocations of
     * the matched method will now be handled by the stub instead.
     *
     * @param PHPUnit_Framework_MockObject_Stub $stub
     *
     * @return PHPUnit_Framework_MockObject_Builder_Identity
     */
    public function will(PHPUnit_Framework_MockObject_Stub $stub);
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Builder interface for matcher of method names.
 *
 * @since Interface available since Release 1.0.0
 */
interface PHPUnit_Framework_MockObject_Builder_MethodNameMatch extends PHPUnit_Framework_MockObject_Builder_ParametersMatch
{
    /**
     * Adds a new method name match and returns the parameter match object for
     * further matching possibilities.
     *
     * @param PHPUnit_Framework_Constraint $name Constraint for matching method, if a string is passed it will use the PHPUnit_Framework_Constraint_IsEqual
     *
     * @return PHPUnit_Framework_MockObject_Builder_ParametersMatch
     */
    public function method($name);
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * @since Class available since Release 2.0.6
 */
class PHPUnit_Framework_MockObject_RuntimeException extends RuntimeException implements PHPUnit_Framework_MockObject_Exception
{
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * @since Class available since Release 2.0.6
 */
class PHPUnit_Framework_MockObject_BadMethodCallException extends BadMethodCallException implements PHPUnit_Framework_MockObject_Exception
{
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Interface for exceptions used by PHPUnit_MockObject.
 *
 * @since Interface available since Release 2.0.6
 */
interface PHPUnit_Framework_MockObject_Exception
{
}
<?php
/*
 * This file is part of the PHPUnit_MockObject package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Interface for classes which must verify a given expectation.
 *
 * @since Interface available since Release 1.0.0
 */
interface PHPUnit_Framework_MockObject_Verifiable
{
    /**
     * Verifies that the current expectation is valid. If everything is OK the
     * code should just return, if not it must throw an exception.
     *
     * @throws PHPUnit_Framework_ExpectationFailedException
     */
    public function verify();
}
{
    "name": "phpunit/phpunit",
    "description": "The PHP Unit Testing framework.",
    "type": "library",
    "keywords": [
        "phpunit",
        "xunit",
        "testing"
    ],
    "homepage": "https://phpunit.de/",
    "license": "BSD-3-Clause",
    "authors": [
        {
            "name": "Sebastian Bergmann",
            "email": "sebastian@phpunit.de",
            "role": "lead"
        }
    ],
    "support": {
        "issues": "https://github.com/sebastianbergmann/phpunit/issues"
    },
    "prefer-stable": true,
    "require": {
        "php": "^5.6 || ^7.0",
        "phpunit/php-file-iterator": "~1.4",
        "phpunit/php-text-template": "~1.2",
        "phpunit/php-code-coverage": "^4.0.4",
        "phpunit/php-timer": "^1.0.6",
        "phpunit/phpunit-mock-objects": "^3.2",
        "phpspec/prophecy": "^1.6.2",
        "symfony/yaml": "~2.1|~3.0|~4.0",
        "sebastian/comparator": "^1.2.4",
        "sebastian/diff": "^1.4.3",
        "sebastian/environment": "^1.3.4 || ^2.0",
        "sebastian/exporter": "~2.0",
        "sebastian/global-state": "^1.1",
        "sebastian/object-enumerator": "~2.0",
        "sebastian/resource-operations": "~1.0",
        "sebastian/version": "^1.0.6|^2.0.1",
        "myclabs/deep-copy": "~1.3",
        "ext-dom": "*",
        "ext-json": "*",
        "ext-mbstring": "*",
        "ext-xml": "*",
        "ext-libxml": "*"
    },
    "require-dev": {
        "ext-PDO": "*"
    },
    "conflict": {
        "phpdocumentor/reflection-docblock": "3.0.2"
    },
    "config": {
        "platform": {
            "php": "5.6.0"
        },
        "optimize-autoloader": true,
        "sort-packages": true
    },
    "suggest": {
        "phpunit/php-invoker": "~1.1",
        "ext-xdebug": "*"
    },
    "bin": [
        "phpunit"
    ],
    "autoload": {
        "classmap": [
            "src/"
        ]
    },
    "autoload-dev": {
        "classmap": [
            "tests/"
        ],
        "files": [
            "src/Framework/Assert/Functions.php",
            "tests/_files/CoveredFunction.php"
        ]
    },
    "extra": {
        "branch-alias": {
            "dev-master": "5.7.x-dev"
        }
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use SebastianBergmann\Environment\Console;

/**
 * Prints the result of a TextUI TestRunner run.
 */
class PHPUnit_TextUI_ResultPrinter extends PHPUnit_Util_Printer implements PHPUnit_Framework_TestListener
{
    const EVENT_TEST_START      = 0;
    const EVENT_TEST_END        = 1;
    const EVENT_TESTSUITE_START = 2;
    const EVENT_TESTSUITE_END   = 3;

    const COLOR_NEVER   = 'never';
    const COLOR_AUTO    = 'auto';
    const COLOR_ALWAYS  = 'always';
    const COLOR_DEFAULT = self::COLOR_NEVER;

    /**
     * @var array
     */
    private static $ansiCodes = [
      'bold'       => 1,
      'fg-black'   => 30,
      'fg-red'     => 31,
      'fg-green'   => 32,
      'fg-yellow'  => 33,
      'fg-blue'    => 34,
      'fg-magenta' => 35,
      'fg-cyan'    => 36,
      'fg-white'   => 37,
      'bg-black'   => 40,
      'bg-red'     => 41,
      'bg-green'   => 42,
      'bg-yellow'  => 43,
      'bg-blue'    => 44,
      'bg-magenta' => 45,
      'bg-cyan'    => 46,
      'bg-white'   => 47
    ];

    /**
     * @var int
     */
    protected $column = 0;

    /**
     * @var int
     */
    protected $maxColumn;

    /**
     * @var bool
     */
    protected $lastTestFailed = false;

    /**
     * @var int
     */
    protected $numAssertions = 0;

    /**
     * @var int
     */
    protected $numTests = -1;

    /**
     * @var int
     */
    protected $numTestsRun = 0;

    /**
     * @var int
     */
    protected $numTestsWidth;

    /**
     * @var bool
     */
    protected $colors = false;

    /**
     * @var bool
     */
    protected $debug = false;

    /**
     * @var bool
     */
    protected $verbose = false;

    /**
     * @var int
     */
    private $numberOfColumns;

    /**
     * @var bool
     */
    private $reverse = false;

    /**
     * @var bool
     */
    private $defectListPrinted = false;

    /**
     * Constructor.
     *
     * @param mixed      $out
     * @param bool       $verbose
     * @param string     $colors
     * @param bool       $debug
     * @param int|string $numberOfColumns
     * @param bool       $reverse
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function __construct($out = null, $verbose = false, $colors = self::COLOR_DEFAULT, $debug = false, $numberOfColumns = 80, $reverse = false)
    {
        parent::__construct($out);

        if (!is_bool($verbose)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'boolean');
        }

        $availableColors = [self::COLOR_NEVER, self::COLOR_AUTO, self::COLOR_ALWAYS];

        if (!in_array($colors, $availableColors)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(
                3,
                vsprintf('value from "%s", "%s" or "%s"', $availableColors)
            );
        }

        if (!is_bool($debug)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(4, 'boolean');
        }

        if (!is_int($numberOfColumns) && $numberOfColumns != 'max') {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(5, 'integer or "max"');
        }

        if (!is_bool($reverse)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(6, 'boolean');
        }

        $console            = new Console;
        $maxNumberOfColumns = $console->getNumberOfColumns();

        if ($numberOfColumns == 'max' || $numberOfColumns > $maxNumberOfColumns) {
            $numberOfColumns = $maxNumberOfColumns;
        }

        $this->numberOfColumns = $numberOfColumns;
        $this->verbose         = $verbose;
        $this->debug           = $debug;
        $this->reverse         = $reverse;

        if ($colors === self::COLOR_AUTO && $console->hasColorSupport()) {
            $this->colors = true;
        } else {
            $this->colors = (self::COLOR_ALWAYS === $colors);
        }
    }

    /**
     * @param PHPUnit_Framework_TestResult $result
     */
    public function printResult(PHPUnit_Framework_TestResult $result)
    {
        $this->printHeader();
        $this->printErrors($result);
        $this->printWarnings($result);
        $this->printFailures($result);

        if ($this->verbose) {
            $this->printRisky($result);
            $this->printIncompletes($result);
            $this->printSkipped($result);
        }

        $this->printFooter($result);
    }

    /**
     * @param array  $defects
     * @param string $type
     */
    protected function printDefects(array $defects, $type)
    {
        $count = count($defects);

        if ($count == 0) {
            return;
        }

        if ($this->defectListPrinted) {
            $this->write("\n--\n\n");
        }

        $this->write(
            sprintf(
                "There %s %d %s%s:\n",
                ($count == 1) ? 'was' : 'were',
                $count,
                $type,
                ($count == 1) ? '' : 's'
            )
        );

        $i = 1;

        if ($this->reverse) {
            $defects = array_reverse($defects);
        }

        foreach ($defects as $defect) {
            $this->printDefect($defect, $i++);
        }

        $this->defectListPrinted = true;
    }

    /**
     * @param PHPUnit_Framework_TestFailure $defect
     * @param int                           $count
     */
    protected function printDefect(PHPUnit_Framework_TestFailure $defect, $count)
    {
        $this->printDefectHeader($defect, $count);
        $this->printDefectTrace($defect);
    }

    /**
     * @param PHPUnit_Framework_TestFailure $defect
     * @param int                           $count
     */
    protected function printDefectHeader(PHPUnit_Framework_TestFailure $defect, $count)
    {
        $this->write(
            sprintf(
                "\n%d) %s\n",
                $count,
                $defect->getTestName()
            )
        );
    }

    /**
     * @param PHPUnit_Framework_TestFailure $defect
     */
    protected function printDefectTrace(PHPUnit_Framework_TestFailure $defect)
    {
        $e = $defect->thrownException();
        $this->write((string) $e);

        while ($e = $e->getPrevious()) {
            $this->write("\nCaused by\n" . $e);
        }
    }

    /**
     * @param PHPUnit_Framework_TestResult $result
     */
    protected function printErrors(PHPUnit_Framework_TestResult $result)
    {
        $this->printDefects($result->errors(), 'error');
    }

    /**
     * @param PHPUnit_Framework_TestResult $result
     */
    protected function printFailures(PHPUnit_Framework_TestResult $result)
    {
        $this->printDefects($result->failures(), 'failure');
    }

    /**
     * @param PHPUnit_Framework_TestResult $result
     */
    protected function printWarnings(PHPUnit_Framework_TestResult $result)
    {
        $this->printDefects($result->warnings(), 'warning');
    }

    /**
     * @param PHPUnit_Framework_TestResult $result
     */
    protected function printIncompletes(PHPUnit_Framework_TestResult $result)
    {
        $this->printDefects($result->notImplemented(), 'incomplete test');
    }

    /**
     * @param PHPUnit_Framework_TestResult $result
     */
    protected function printRisky(PHPUnit_Framework_TestResult $result)
    {
        $this->printDefects($result->risky(), 'risky test');
    }

    /**
     * @param PHPUnit_Framework_TestResult $result
     */
    protected function printSkipped(PHPUnit_Framework_TestResult $result)
    {
        $this->printDefects($result->skipped(), 'skipped test');
    }

    protected function printHeader()
    {
        $this->write("\n\n" . PHP_Timer::resourceUsage() . "\n\n");
    }

    /**
     * @param PHPUnit_Framework_TestResult $result
     */
    protected function printFooter(PHPUnit_Framework_TestResult $result)
    {
        if (count($result) === 0) {
            $this->writeWithColor(
                'fg-black, bg-yellow',
                'No tests executed!'
            );

            return;
        }

        if ($result->wasSuccessful() &&
            $result->allHarmless() &&
            $result->allCompletelyImplemented() &&
            $result->noneSkipped()) {
            $this->writeWithColor(
                'fg-black, bg-green',
                sprintf(
                    'OK (%d test%s, %d assertion%s)',
                    count($result),
                    (count($result) == 1) ? '' : 's',
                    $this->numAssertions,
                    ($this->numAssertions == 1) ? '' : 's'
                )
            );
        } else {
            if ($result->wasSuccessful()) {
                $color = 'fg-black, bg-yellow';

                if ($this->verbose) {
                    $this->write("\n");
                }

                $this->writeWithColor(
                    $color,
                    'OK, but incomplete, skipped, or risky tests!'
                );
            } else {
                $this->write("\n");

                if ($result->errorCount()) {
                    $color = 'fg-white, bg-red';

                    $this->writeWithColor(
                        $color,
                        'ERRORS!'
                    );
                } elseif ($result->failureCount()) {
                    $color = 'fg-white, bg-red';

                    $this->writeWithColor(
                        $color,
                        'FAILURES!'
                    );
                } elseif ($result->warningCount()) {
                    $color = 'fg-black, bg-yellow';

                    $this->writeWithColor(
                        $color,
                        'WARNINGS!'
                    );
                }
            }

            $this->writeCountString(count($result), 'Tests', $color, true);
            $this->writeCountString($this->numAssertions, 'Assertions', $color, true);
            $this->writeCountString($result->errorCount(), 'Errors', $color);
            $this->writeCountString($result->failureCount(), 'Failures', $color);
            $this->writeCountString($result->warningCount(), 'Warnings', $color);
            $this->writeCountString($result->skippedCount(), 'Skipped', $color);
            $this->writeCountString($result->notImplementedCount(), 'Incomplete', $color);
            $this->writeCountString($result->riskyCount(), 'Risky', $color);
            $this->writeWithColor($color, '.', true);
        }
    }

    public function printWaitPrompt()
    {
        $this->write("\n<RETURN> to continue\n");
    }

    /**
     * An error occurred.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addError(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        $this->writeProgressWithColor('fg-red, bold', 'E');
        $this->lastTestFailed = true;
    }

    /**
     * A failure occurred.
     *
     * @param PHPUnit_Framework_Test                 $test
     * @param PHPUnit_Framework_AssertionFailedError $e
     * @param float                                  $time
     */
    public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time)
    {
        $this->writeProgressWithColor('bg-red, fg-white', 'F');
        $this->lastTestFailed = true;
    }

    /**
     * A warning occurred.
     *
     * @param PHPUnit_Framework_Test    $test
     * @param PHPUnit_Framework_Warning $e
     * @param float                     $time
     */
    public function addWarning(PHPUnit_Framework_Test $test, PHPUnit_Framework_Warning $e, $time)
    {
        $this->writeProgressWithColor('fg-yellow, bold', 'W');
        $this->lastTestFailed = true;
    }

    /**
     * Incomplete test.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        $this->writeProgressWithColor('fg-yellow, bold', 'I');
        $this->lastTestFailed = true;
    }

    /**
     * Risky test.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        $this->writeProgressWithColor('fg-yellow, bold', 'R');
        $this->lastTestFailed = true;
    }

    /**
     * Skipped test.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        $this->writeProgressWithColor('fg-cyan, bold', 'S');
        $this->lastTestFailed = true;
    }

    /**
     * A testsuite started.
     *
     * @param PHPUnit_Framework_TestSuite $suite
     */
    public function startTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
        if ($this->numTests == -1) {
            $this->numTests      = count($suite);
            $this->numTestsWidth = strlen((string) $this->numTests);
            $this->maxColumn     = $this->numberOfColumns - strlen('  /  (XXX%)') - (2 * $this->numTestsWidth);
        }
    }

    /**
     * A testsuite ended.
     *
     * @param PHPUnit_Framework_TestSuite $suite
     */
    public function endTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
    }

    /**
     * A test started.
     *
     * @param PHPUnit_Framework_Test $test
     */
    public function startTest(PHPUnit_Framework_Test $test)
    {
        if ($this->debug) {
            $this->write(
                sprintf(
                    "\nStarting test '%s'.\n",
                    PHPUnit_Util_Test::describe($test)
                )
            );
        }
    }

    /**
     * A test ended.
     *
     * @param PHPUnit_Framework_Test $test
     * @param float                  $time
     */
    public function endTest(PHPUnit_Framework_Test $test, $time)
    {
        if (!$this->lastTestFailed) {
            $this->writeProgress('.');
        }

        if ($test instanceof PHPUnit_Framework_TestCase) {
            $this->numAssertions += $test->getNumAssertions();
        } elseif ($test instanceof PHPUnit_Extensions_PhptTestCase) {
            $this->numAssertions++;
        }

        $this->lastTestFailed = false;

        if ($test instanceof PHPUnit_Framework_TestCase) {
            if (!$test->hasExpectationOnOutput()) {
                $this->write($test->getActualOutput());
            }
        }
    }

    /**
     * @param string $progress
     */
    protected function writeProgress($progress)
    {
        $this->write($progress);
        $this->column++;
        $this->numTestsRun++;

        if ($this->column == $this->maxColumn
            || $this->numTestsRun == $this->numTests
        ) {
            if ($this->numTestsRun == $this->numTests) {
                $this->write(str_repeat(' ', $this->maxColumn - $this->column));
            }

            $this->write(
                sprintf(
                    ' %' . $this->numTestsWidth . 'd / %' .
                    $this->numTestsWidth . 'd (%3s%%)',
                    $this->numTestsRun,
                    $this->numTests,
                    floor(($this->numTestsRun / $this->numTests) * 100)
                )
            );

            if ($this->column == $this->maxColumn) {
                $this->writeNewLine();
            }
        }
    }

    protected function writeNewLine()
    {
        $this->column = 0;
        $this->write("\n");
    }

    /**
     * Formats a buffer with a specified ANSI color sequence if colors are
     * enabled.
     *
     * @param string $color
     * @param string $buffer
     *
     * @return string
     */
    protected function formatWithColor($color, $buffer)
    {
        if (!$this->colors) {
            return $buffer;
        }

        $codes   = array_map('trim', explode(',', $color));
        $lines   = explode("\n", $buffer);
        $padding = max(array_map('strlen', $lines));
        $styles  = [];

        foreach ($codes as $code) {
            $styles[] = self::$ansiCodes[$code];
        }

        $style = sprintf("\x1b[%sm", implode(';', $styles));

        $styledLines = [];

        foreach ($lines as $line) {
            $styledLines[] = $style . str_pad($line, $padding) . "\x1b[0m";
        }

        return implode("\n", $styledLines);
    }

    /**
     * Writes a buffer out with a color sequence if colors are enabled.
     *
     * @param string $color
     * @param string $buffer
     * @param bool   $lf
     */
    protected function writeWithColor($color, $buffer, $lf = true)
    {
        $this->write($this->formatWithColor($color, $buffer));

        if ($lf) {
            $this->write("\n");
        }
    }

    /**
     * Writes progress with a color sequence if colors are enabled.
     *
     * @param string $color
     * @param string $buffer
     */
    protected function writeProgressWithColor($color, $buffer)
    {
        $buffer = $this->formatWithColor($color, $buffer);
        $this->writeProgress($buffer);
    }

    /**
     * @param int    $count
     * @param string $name
     * @param string $color
     * @param bool   $always
     */
    private function writeCountString($count, $name, $color, $always = false)
    {
        static $first = true;

        if ($always || $count > 0) {
            $this->writeWithColor(
                $color,
                sprintf(
                    '%s%s: %d',
                    !$first ? ', ' : '',
                    $name,
                    $count
                ),
                false
            );

            $first = false;
        }
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\Exception as CodeCoverageException;
use SebastianBergmann\CodeCoverage\Filter as CodeCoverageFilter;
use SebastianBergmann\CodeCoverage\Report\Clover as CloverReport;
use SebastianBergmann\CodeCoverage\Report\Crap4j as Crap4jReport;
use SebastianBergmann\CodeCoverage\Report\Html\Facade as HtmlReport;
use SebastianBergmann\CodeCoverage\Report\PHP as PhpReport;
use SebastianBergmann\CodeCoverage\Report\Text as TextReport;
use SebastianBergmann\CodeCoverage\Report\Xml\Facade as XmlReport;
use SebastianBergmann\Environment\Runtime;

/**
 * A TestRunner for the Command Line Interface (CLI)
 * PHP SAPI Module.
 */
class PHPUnit_TextUI_TestRunner extends PHPUnit_Runner_BaseTestRunner
{
    const SUCCESS_EXIT   = 0;
    const FAILURE_EXIT   = 1;
    const EXCEPTION_EXIT = 2;

    /**
     * @var CodeCoverageFilter
     */
    protected $codeCoverageFilter;

    /**
     * @var PHPUnit_Runner_TestSuiteLoader
     */
    protected $loader = null;

    /**
     * @var PHPUnit_TextUI_ResultPrinter
     */
    protected $printer = null;

    /**
     * @var bool
     */
    protected static $versionStringPrinted = false;

    /**
     * @var Runtime
     */
    private $runtime;

    /**
     * @var bool
     */
    private $messagePrinted = false;

    /**
     * @param PHPUnit_Runner_TestSuiteLoader $loader
     * @param CodeCoverageFilter             $filter
     */
    public function __construct(PHPUnit_Runner_TestSuiteLoader $loader = null, CodeCoverageFilter $filter = null)
    {
        if ($filter === null) {
            $filter = new CodeCoverageFilter;
        }

        $this->codeCoverageFilter = $filter;
        $this->loader             = $loader;
        $this->runtime            = new Runtime;
    }

    /**
     * @param PHPUnit_Framework_Test|ReflectionClass $test
     * @param array                                  $arguments
     *
     * @return PHPUnit_Framework_TestResult
     *
     * @throws PHPUnit_Framework_Exception
     */
    public static function run($test, array $arguments = [])
    {
        if ($test instanceof ReflectionClass) {
            $test = new PHPUnit_Framework_TestSuite($test);
        }

        if ($test instanceof PHPUnit_Framework_Test) {
            $aTestRunner = new self;

            return $aTestRunner->doRun(
                $test,
                $arguments
            );
        } else {
            throw new PHPUnit_Framework_Exception(
                'No test case or test suite found.'
            );
        }
    }

    /**
     * @return PHPUnit_Framework_TestResult
     */
    protected function createTestResult()
    {
        return new PHPUnit_Framework_TestResult;
    }

    /**
     * @param PHPUnit_Framework_TestSuite $suite
     * @param array                       $arguments
     */
    private function processSuiteFilters(PHPUnit_Framework_TestSuite $suite, array $arguments)
    {
        if (!$arguments['filter'] &&
            empty($arguments['groups']) &&
            empty($arguments['excludeGroups'])) {
            return;
        }

        $filterFactory = new PHPUnit_Runner_Filter_Factory();

        if (!empty($arguments['excludeGroups'])) {
            $filterFactory->addFilter(
                new ReflectionClass('PHPUnit_Runner_Filter_Group_Exclude'),
                $arguments['excludeGroups']
            );
        }

        if (!empty($arguments['groups'])) {
            $filterFactory->addFilter(
                new ReflectionClass('PHPUnit_Runner_Filter_Group_Include'),
                $arguments['groups']
            );
        }

        if ($arguments['filter']) {
            $filterFactory->addFilter(
                new ReflectionClass('PHPUnit_Runner_Filter_Test'),
                $arguments['filter']
            );
        }
        $suite->injectFilter($filterFactory);
    }

    /**
     * @param PHPUnit_Framework_Test $suite
     * @param array                  $arguments
     * @param bool                   $exit
     *
     * @return PHPUnit_Framework_TestResult
     */
    public function doRun(PHPUnit_Framework_Test $suite, array $arguments = [], $exit = true)
    {
        if (isset($arguments['configuration'])) {
            $GLOBALS['__PHPUNIT_CONFIGURATION_FILE'] = $arguments['configuration'];
        }

        $this->handleConfiguration($arguments);

        $this->processSuiteFilters($suite, $arguments);

        if (isset($arguments['bootstrap'])) {
            $GLOBALS['__PHPUNIT_BOOTSTRAP'] = $arguments['bootstrap'];
        }

        if ($arguments['backupGlobals'] === false) {
            $suite->setBackupGlobals(false);
        }

        if ($arguments['backupStaticAttributes'] === true) {
            $suite->setBackupStaticAttributes(true);
        }

        if ($arguments['beStrictAboutChangesToGlobalState'] === true) {
            $suite->setBeStrictAboutChangesToGlobalState(true);
        }

        if (is_int($arguments['repeat'])) {
            $test = new PHPUnit_Extensions_RepeatedTest(
                $suite,
                $arguments['repeat'],
                $arguments['processIsolation']
            );

            $suite = new PHPUnit_Framework_TestSuite();
            $suite->addTest($test);
        }

        $result = $this->createTestResult();

        if (!$arguments['convertErrorsToExceptions']) {
            $result->convertErrorsToExceptions(false);
        }

        if (!$arguments['convertNoticesToExceptions']) {
            PHPUnit_Framework_Error_Notice::$enabled = false;
        }

        if (!$arguments['convertWarningsToExceptions']) {
            PHPUnit_Framework_Error_Warning::$enabled = false;
        }

        if ($arguments['stopOnError']) {
            $result->stopOnError(true);
        }

        if ($arguments['stopOnFailure']) {
            $result->stopOnFailure(true);
        }

        if ($arguments['stopOnWarning']) {
            $result->stopOnWarning(true);
        }

        if ($arguments['stopOnIncomplete']) {
            $result->stopOnIncomplete(true);
        }

        if ($arguments['stopOnRisky']) {
            $result->stopOnRisky(true);
        }

        if ($arguments['stopOnSkipped']) {
            $result->stopOnSkipped(true);
        }

        if ($arguments['registerMockObjectsFromTestArgumentsRecursively']) {
            $result->setRegisterMockObjectsFromTestArgumentsRecursively(true);
        }

        if ($this->printer === null) {
            if (isset($arguments['printer']) &&
                $arguments['printer'] instanceof PHPUnit_Util_Printer) {
                $this->printer = $arguments['printer'];
            } else {
                $printerClass = 'PHPUnit_TextUI_ResultPrinter';

                if (isset($arguments['printer']) &&
                    is_string($arguments['printer']) &&
                    class_exists($arguments['printer'], false)) {
                    $class = new ReflectionClass($arguments['printer']);

                    if ($class->isSubclassOf('PHPUnit_TextUI_ResultPrinter')) {
                        $printerClass = $arguments['printer'];
                    }
                }

                $this->printer = new $printerClass(
                    (isset($arguments['stderr']) && $arguments['stderr'] === true) ? 'php://stderr' : null,
                    $arguments['verbose'],
                    $arguments['colors'],
                    $arguments['debug'],
                    $arguments['columns'],
                    $arguments['reverseList']
                );
            }
        }

        if (!$this->printer instanceof PHPUnit_Util_Log_TAP) {
            $this->printer->write(
                PHPUnit_Runner_Version::getVersionString() . "\n"
            );

            self::$versionStringPrinted = true;

            if ($arguments['verbose']) {
                $runtime = $this->runtime->getNameWithVersion();

                if ($this->runtime->hasXdebug()) {
                    $runtime .= sprintf(
                        ' with Xdebug %s',
                        phpversion('xdebug')
                    );
                }

                $this->writeMessage('Runtime', $runtime);

                if (isset($arguments['configuration'])) {
                    $this->writeMessage(
                        'Configuration',
                        $arguments['configuration']->getFilename()
                    );
                }

                foreach ($arguments['loadedExtensions'] as $extension) {
                    $this->writeMessage(
                        'Extension',
                        $extension
                    );
                }

                foreach ($arguments['notLoadedExtensions'] as $extension) {
                    $this->writeMessage(
                        'Extension',
                        $extension
                    );
                }
            }

            if (isset($arguments['deprecatedCheckForUnintentionallyCoveredCodeSettingUsed'])) {
                $this->writeMessage('Warning', 'Deprecated configuration setting "checkForUnintentionallyCoveredCode" used');
            }

            if (isset($arguments['tapLogfile'])) {
                $this->writeMessage('Warning', 'Deprecated TAP test listener used');
            }

            if (isset($arguments['jsonLogfile'])) {
                $this->writeMessage('Warning', 'Deprecated JSON test listener used');
            }
        }

        foreach ($arguments['listeners'] as $listener) {
            $result->addListener($listener);
        }

        $result->addListener($this->printer);

        if (isset($arguments['testdoxHTMLFile'])) {
            $result->addListener(
                new PHPUnit_Util_TestDox_ResultPrinter_HTML(
                    $arguments['testdoxHTMLFile'],
                    $arguments['testdoxGroups'],
                    $arguments['testdoxExcludeGroups']
                )
            );
        }

        if (isset($arguments['testdoxTextFile'])) {
            $result->addListener(
                new PHPUnit_Util_TestDox_ResultPrinter_Text(
                    $arguments['testdoxTextFile'],
                    $arguments['testdoxGroups'],
                    $arguments['testdoxExcludeGroups']
                )
            );
        }

        if (isset($arguments['testdoxXMLFile'])) {
            $result->addListener(
                new PHPUnit_Util_TestDox_ResultPrinter_XML(
                    $arguments['testdoxXMLFile']
                )
            );
        }

        $codeCoverageReports = 0;

        if (isset($arguments['coverageClover'])) {
            $codeCoverageReports++;
        }

        if (isset($arguments['coverageCrap4J'])) {
            $codeCoverageReports++;
        }

        if (isset($arguments['coverageHtml'])) {
            $codeCoverageReports++;
        }

        if (isset($arguments['coveragePHP'])) {
            $codeCoverageReports++;
        }

        if (isset($arguments['coverageText'])) {
            $codeCoverageReports++;
        }

        if (isset($arguments['coverageXml'])) {
            $codeCoverageReports++;
        }

        if (isset($arguments['noCoverage'])) {
            $codeCoverageReports = 0;
        }

        if ($codeCoverageReports > 0 && !$this->runtime->canCollectCodeCoverage()) {
            $this->writeMessage('Error', 'No code coverage driver is available');

            $codeCoverageReports = 0;
        }

        if (!$this->printer instanceof PHPUnit_Util_Log_TAP) {
            $this->printer->write("\n");
        }

        if ($codeCoverageReports > 0) {
            $codeCoverage = new CodeCoverage(
                null,
                $this->codeCoverageFilter
            );

            $codeCoverage->setUnintentionallyCoveredSubclassesWhitelist(
                [SebastianBergmann\Comparator\Comparator::class]
            );

            $codeCoverage->setCheckForUnintentionallyCoveredCode(
                $arguments['strictCoverage']
            );

            $codeCoverage->setCheckForMissingCoversAnnotation(
                $arguments['strictCoverage']
            );

            if (isset($arguments['forceCoversAnnotation'])) {
                $codeCoverage->setForceCoversAnnotation(
                    $arguments['forceCoversAnnotation']
                );
            }

            if (isset($arguments['disableCodeCoverageIgnore'])) {
                $codeCoverage->setDisableIgnoredLines(true);
            }

            if (isset($arguments['whitelist'])) {
                $this->codeCoverageFilter->addDirectoryToWhitelist($arguments['whitelist']);
            }

            if (isset($arguments['configuration'])) {
                $filterConfiguration = $arguments['configuration']->getFilterConfiguration();

                $codeCoverage->setAddUncoveredFilesFromWhitelist(
                    $filterConfiguration['whitelist']['addUncoveredFilesFromWhitelist']
                );

                $codeCoverage->setProcessUncoveredFilesFromWhitelist(
                    $filterConfiguration['whitelist']['processUncoveredFilesFromWhitelist']
                );

                foreach ($filterConfiguration['whitelist']['include']['directory'] as $dir) {
                    $this->codeCoverageFilter->addDirectoryToWhitelist(
                        $dir['path'],
                        $dir['suffix'],
                        $dir['prefix']
                    );
                }

                foreach ($filterConfiguration['whitelist']['include']['file'] as $file) {
                    $this->codeCoverageFilter->addFileToWhitelist($file);
                }

                foreach ($filterConfiguration['whitelist']['exclude']['directory'] as $dir) {
                    $this->codeCoverageFilter->removeDirectoryFromWhitelist(
                        $dir['path'],
                        $dir['suffix'],
                        $dir['prefix']
                    );
                }

                foreach ($filterConfiguration['whitelist']['exclude']['file'] as $file) {
                    $this->codeCoverageFilter->removeFileFromWhitelist($file);
                }
            }

            if (!$this->codeCoverageFilter->hasWhitelist()) {
                $this->writeMessage('Error', 'No whitelist configured, no code coverage will be generated');

                $codeCoverageReports = 0;

                unset($codeCoverage);
            }
        }

        if (isset($codeCoverage)) {
            $result->setCodeCoverage($codeCoverage);

            if ($codeCoverageReports > 1 && isset($arguments['cacheTokens'])) {
                $codeCoverage->setCacheTokens($arguments['cacheTokens']);
            }
        }

        if (isset($arguments['jsonLogfile'])) {
            $result->addListener(
                new PHPUnit_Util_Log_JSON($arguments['jsonLogfile'])
            );
        }

        if (isset($arguments['tapLogfile'])) {
            $result->addListener(
                new PHPUnit_Util_Log_TAP($arguments['tapLogfile'])
            );
        }

        if (isset($arguments['teamcityLogfile'])) {
            $result->addListener(
                new PHPUnit_Util_Log_TeamCity($arguments['teamcityLogfile'])
            );
        }

        if (isset($arguments['junitLogfile'])) {
            $result->addListener(
                new PHPUnit_Util_Log_JUnit(
                    $arguments['junitLogfile'],
                    $arguments['logIncompleteSkipped']
                )
            );
        }

        $result->beStrictAboutTestsThatDoNotTestAnything($arguments['reportUselessTests']);
        $result->beStrictAboutOutputDuringTests($arguments['disallowTestOutput']);
        $result->beStrictAboutTodoAnnotatedTests($arguments['disallowTodoAnnotatedTests']);
        $result->beStrictAboutResourceUsageDuringSmallTests($arguments['beStrictAboutResourceUsageDuringSmallTests']);
        $result->enforceTimeLimit($arguments['enforceTimeLimit']);
        $result->setTimeoutForSmallTests($arguments['timeoutForSmallTests']);
        $result->setTimeoutForMediumTests($arguments['timeoutForMediumTests']);
        $result->setTimeoutForLargeTests($arguments['timeoutForLargeTests']);

        if ($suite instanceof PHPUnit_Framework_TestSuite) {
            $suite->setRunTestInSeparateProcess($arguments['processIsolation']);
        }

        $suite->run($result);

        unset($suite);
        $result->flushListeners();

        if ($this->printer instanceof PHPUnit_TextUI_ResultPrinter) {
            $this->printer->printResult($result);
        }

        if (isset($codeCoverage)) {
            if (isset($arguments['coverageClover'])) {
                $this->printer->write(
                    "\nGenerating code coverage report in Clover XML format ..."
                );

                try {
                    $writer = new CloverReport();
                    $writer->process($codeCoverage, $arguments['coverageClover']);

                    $this->printer->write(" done\n");
                    unset($writer);
                } catch (CodeCoverageException $e) {
                    $this->printer->write(
                        " failed\n" . $e->getMessage() . "\n"
                    );
                }
            }

            if (isset($arguments['coverageCrap4J'])) {
                $this->printer->write(
                    "\nGenerating Crap4J report XML file ..."
                );

                try {
                    $writer = new Crap4jReport($arguments['crap4jThreshold']);
                    $writer->process($codeCoverage, $arguments['coverageCrap4J']);

                    $this->printer->write(" done\n");
                    unset($writer);
                } catch (CodeCoverageException $e) {
                    $this->printer->write(
                        " failed\n" . $e->getMessage() . "\n"
                    );
                }
            }

            if (isset($arguments['coverageHtml'])) {
                $this->printer->write(
                    "\nGenerating code coverage report in HTML format ..."
                );

                try {
                    $writer = new HtmlReport(
                        $arguments['reportLowUpperBound'],
                        $arguments['reportHighLowerBound'],
                        sprintf(
                            ' and <a href="https://phpunit.de/">PHPUnit %s</a>',
                            PHPUnit_Runner_Version::id()
                        )
                    );

                    $writer->process($codeCoverage, $arguments['coverageHtml']);

                    $this->printer->write(" done\n");
                    unset($writer);
                } catch (CodeCoverageException $e) {
                    $this->printer->write(
                        " failed\n" . $e->getMessage() . "\n"
                    );
                }
            }

            if (isset($arguments['coveragePHP'])) {
                $this->printer->write(
                    "\nGenerating code coverage report in PHP format ..."
                );

                try {
                    $writer = new PhpReport();
                    $writer->process($codeCoverage, $arguments['coveragePHP']);

                    $this->printer->write(" done\n");
                    unset($writer);
                } catch (CodeCoverageException $e) {
                    $this->printer->write(
                        " failed\n" . $e->getMessage() . "\n"
                    );
                }
            }

            if (isset($arguments['coverageText'])) {
                if ($arguments['coverageText'] == 'php://stdout') {
                    $outputStream = $this->printer;
                    $colors       = $arguments['colors'] && $arguments['colors'] != PHPUnit_TextUI_ResultPrinter::COLOR_NEVER;
                } else {
                    $outputStream = new PHPUnit_Util_Printer($arguments['coverageText']);
                    $colors       = false;
                }

                $processor = new TextReport(
                    $arguments['reportLowUpperBound'],
                    $arguments['reportHighLowerBound'],
                    $arguments['coverageTextShowUncoveredFiles'],
                    $arguments['coverageTextShowOnlySummary']
                );

                $outputStream->write(
                    $processor->process($codeCoverage, $colors)
                );
            }

            if (isset($arguments['coverageXml'])) {
                $this->printer->write(
                    "\nGenerating code coverage report in PHPUnit XML format ..."
                );

                try {
                    $writer = new XmlReport;
                    $writer->process($codeCoverage, $arguments['coverageXml']);

                    $this->printer->write(" done\n");
                    unset($writer);
                } catch (CodeCoverageException $e) {
                    $this->printer->write(
                        " failed\n" . $e->getMessage() . "\n"
                    );
                }
            }
        }

        if ($exit) {
            if ($result->wasSuccessful(false)) {
                if ($arguments['failOnRisky'] && !$result->allHarmless()) {
                    exit(self::FAILURE_EXIT);
                }

                if ($arguments['failOnWarning'] && $result->warningCount() > 0) {
                    exit(self::FAILURE_EXIT);
                }

                exit(self::SUCCESS_EXIT);
            }

            if ($result->errorCount() > 0) {
                exit(self::EXCEPTION_EXIT);
            }

            if ($result->failureCount() > 0) {
                exit(self::FAILURE_EXIT);
            }
        }

        return $result;
    }

    /**
     * @param PHPUnit_TextUI_ResultPrinter $resultPrinter
     */
    public function setPrinter(PHPUnit_TextUI_ResultPrinter $resultPrinter)
    {
        $this->printer = $resultPrinter;
    }

    /**
     * Override to define how to handle a failed loading of
     * a test suite.
     *
     * @param string $message
     */
    protected function runFailed($message)
    {
        $this->write($message . PHP_EOL);
        exit(self::FAILURE_EXIT);
    }

    /**
     * @param string $buffer
     */
    protected function write($buffer)
    {
        if (PHP_SAPI != 'cli' && PHP_SAPI != 'phpdbg') {
            $buffer = htmlspecialchars($buffer);
        }

        if ($this->printer !== null) {
            $this->printer->write($buffer);
        } else {
            print $buffer;
        }
    }

    /**
     * Returns the loader to be used.
     *
     * @return PHPUnit_Runner_TestSuiteLoader
     */
    public function getLoader()
    {
        if ($this->loader === null) {
            $this->loader = new PHPUnit_Runner_StandardTestSuiteLoader;
        }

        return $this->loader;
    }

    /**
     * @param array $arguments
     */
    protected function handleConfiguration(array &$arguments)
    {
        if (isset($arguments['configuration']) &&
            !$arguments['configuration'] instanceof PHPUnit_Util_Configuration) {
            $arguments['configuration'] = PHPUnit_Util_Configuration::getInstance(
                $arguments['configuration']
            );
        }

        $arguments['debug']     = isset($arguments['debug'])     ? $arguments['debug']     : false;
        $arguments['filter']    = isset($arguments['filter'])    ? $arguments['filter']    : false;
        $arguments['listeners'] = isset($arguments['listeners']) ? $arguments['listeners'] : [];

        if (isset($arguments['configuration'])) {
            $arguments['configuration']->handlePHPConfiguration();

            $phpunitConfiguration = $arguments['configuration']->getPHPUnitConfiguration();

            if (isset($phpunitConfiguration['deprecatedCheckForUnintentionallyCoveredCodeSettingUsed'])) {
                $arguments['deprecatedCheckForUnintentionallyCoveredCodeSettingUsed'] = true;
            }

            if (isset($phpunitConfiguration['backupGlobals']) &&
                !isset($arguments['backupGlobals'])) {
                $arguments['backupGlobals'] = $phpunitConfiguration['backupGlobals'];
            }

            if (isset($phpunitConfiguration['backupStaticAttributes']) &&
                !isset($arguments['backupStaticAttributes'])) {
                $arguments['backupStaticAttributes'] = $phpunitConfiguration['backupStaticAttributes'];
            }

            if (isset($phpunitConfiguration['beStrictAboutChangesToGlobalState']) &&
                !isset($arguments['beStrictAboutChangesToGlobalState'])) {
                $arguments['beStrictAboutChangesToGlobalState'] = $phpunitConfiguration['beStrictAboutChangesToGlobalState'];
            }

            if (isset($phpunitConfiguration['bootstrap']) &&
                !isset($arguments['bootstrap'])) {
                $arguments['bootstrap'] = $phpunitConfiguration['bootstrap'];
            }

            if (isset($phpunitConfiguration['cacheTokens']) &&
                !isset($arguments['cacheTokens'])) {
                $arguments['cacheTokens'] = $phpunitConfiguration['cacheTokens'];
            }

            if (isset($phpunitConfiguration['colors']) &&
                !isset($arguments['colors'])) {
                $arguments['colors'] = $phpunitConfiguration['colors'];
            }

            if (isset($phpunitConfiguration['convertErrorsToExceptions']) &&
                !isset($arguments['convertErrorsToExceptions'])) {
                $arguments['convertErrorsToExceptions'] = $phpunitConfiguration['convertErrorsToExceptions'];
            }

            if (isset($phpunitConfiguration['convertNoticesToExceptions']) &&
                !isset($arguments['convertNoticesToExceptions'])) {
                $arguments['convertNoticesToExceptions'] = $phpunitConfiguration['convertNoticesToExceptions'];
            }

            if (isset($phpunitConfiguration['convertWarningsToExceptions']) &&
                !isset($arguments['convertWarningsToExceptions'])) {
                $arguments['convertWarningsToExceptions'] = $phpunitConfiguration['convertWarningsToExceptions'];
            }

            if (isset($phpunitConfiguration['processIsolation']) &&
                !isset($arguments['processIsolation'])) {
                $arguments['processIsolation'] = $phpunitConfiguration['processIsolation'];
            }

            if (isset($phpunitConfiguration['stopOnError']) &&
                !isset($arguments['stopOnError'])) {
                $arguments['stopOnError'] = $phpunitConfiguration['stopOnError'];
            }

            if (isset($phpunitConfiguration['stopOnFailure']) &&
                !isset($arguments['stopOnFailure'])) {
                $arguments['stopOnFailure'] = $phpunitConfiguration['stopOnFailure'];
            }

            if (isset($phpunitConfiguration['stopOnWarning']) &&
                !isset($arguments['stopOnWarning'])) {
                $arguments['stopOnWarning'] = $phpunitConfiguration['stopOnWarning'];
            }

            if (isset($phpunitConfiguration['stopOnIncomplete']) &&
                !isset($arguments['stopOnIncomplete'])) {
                $arguments['stopOnIncomplete'] = $phpunitConfiguration['stopOnIncomplete'];
            }

            if (isset($phpunitConfiguration['stopOnRisky']) &&
                !isset($arguments['stopOnRisky'])) {
                $arguments['stopOnRisky'] = $phpunitConfiguration['stopOnRisky'];
            }

            if (isset($phpunitConfiguration['stopOnSkipped']) &&
                !isset($arguments['stopOnSkipped'])) {
                $arguments['stopOnSkipped'] = $phpunitConfiguration['stopOnSkipped'];
            }

            if (isset($phpunitConfiguration['failOnWarning']) &&
                !isset($arguments['failOnWarning'])) {
                $arguments['failOnWarning'] = $phpunitConfiguration['failOnWarning'];
            }

            if (isset($phpunitConfiguration['failOnRisky']) &&
                !isset($arguments['failOnRisky'])) {
                $arguments['failOnRisky'] = $phpunitConfiguration['failOnRisky'];
            }

            if (isset($phpunitConfiguration['timeoutForSmallTests']) &&
                !isset($arguments['timeoutForSmallTests'])) {
                $arguments['timeoutForSmallTests'] = $phpunitConfiguration['timeoutForSmallTests'];
            }

            if (isset($phpunitConfiguration['timeoutForMediumTests']) &&
                !isset($arguments['timeoutForMediumTests'])) {
                $arguments['timeoutForMediumTests'] = $phpunitConfiguration['timeoutForMediumTests'];
            }

            if (isset($phpunitConfiguration['timeoutForLargeTests']) &&
                !isset($arguments['timeoutForLargeTests'])) {
                $arguments['timeoutForLargeTests'] = $phpunitConfiguration['timeoutForLargeTests'];
            }

            if (isset($phpunitConfiguration['reportUselessTests']) &&
                !isset($arguments['reportUselessTests'])) {
                $arguments['reportUselessTests'] = $phpunitConfiguration['reportUselessTests'];
            }

            if (isset($phpunitConfiguration['strictCoverage']) &&
                !isset($arguments['strictCoverage'])) {
                $arguments['strictCoverage'] = $phpunitConfiguration['strictCoverage'];
            }

            if (isset($phpunitConfiguration['disallowTestOutput']) &&
                !isset($arguments['disallowTestOutput'])) {
                $arguments['disallowTestOutput'] = $phpunitConfiguration['disallowTestOutput'];
            }

            if (isset($phpunitConfiguration['enforceTimeLimit']) &&
                !isset($arguments['enforceTimeLimit'])) {
                $arguments['enforceTimeLimit'] = $phpunitConfiguration['enforceTimeLimit'];
            }

            if (isset($phpunitConfiguration['disallowTodoAnnotatedTests']) &&
                !isset($arguments['disallowTodoAnnotatedTests'])) {
                $arguments['disallowTodoAnnotatedTests'] = $phpunitConfiguration['disallowTodoAnnotatedTests'];
            }

            if (isset($phpunitConfiguration['beStrictAboutResourceUsageDuringSmallTests']) &&
                !isset($arguments['beStrictAboutResourceUsageDuringSmallTests'])) {
                $arguments['beStrictAboutResourceUsageDuringSmallTests'] = $phpunitConfiguration['beStrictAboutResourceUsageDuringSmallTests'];
            }

            if (isset($phpunitConfiguration['verbose']) &&
                !isset($arguments['verbose'])) {
                $arguments['verbose'] = $phpunitConfiguration['verbose'];
            }

            if (isset($phpunitConfiguration['reverseDefectList']) &&
                !isset($arguments['reverseList'])) {
                $arguments['reverseList'] = $phpunitConfiguration['reverseDefectList'];
            }

            if (isset($phpunitConfiguration['forceCoversAnnotation']) &&
                !isset($arguments['forceCoversAnnotation'])) {
                $arguments['forceCoversAnnotation'] = $phpunitConfiguration['forceCoversAnnotation'];
            }

            if (isset($phpunitConfiguration['disableCodeCoverageIgnore']) &&
                !isset($arguments['disableCodeCoverageIgnore'])) {
                $arguments['disableCodeCoverageIgnore'] = $phpunitConfiguration['disableCodeCoverageIgnore'];
            }

            if (isset($phpunitConfiguration['registerMockObjectsFromTestArgumentsRecursively']) &&
                !isset($arguments['registerMockObjectsFromTestArgumentsRecursively'])) {
                $arguments['registerMockObjectsFromTestArgumentsRecursively'] = $phpunitConfiguration['registerMockObjectsFromTestArgumentsRecursively'];
            }

            $groupCliArgs = [];

            if (!empty($arguments['groups'])) {
                $groupCliArgs = $arguments['groups'];
            }

            $groupConfiguration = $arguments['configuration']->getGroupConfiguration();

            if (!empty($groupConfiguration['include']) &&
                !isset($arguments['groups'])) {
                $arguments['groups'] = $groupConfiguration['include'];
            }

            if (!empty($groupConfiguration['exclude']) &&
                !isset($arguments['excludeGroups'])) {
                $arguments['excludeGroups'] = array_diff($groupConfiguration['exclude'], $groupCliArgs);
            }

            foreach ($arguments['configuration']->getListenerConfiguration() as $listener) {
                if (!class_exists($listener['class'], false) &&
                    $listener['file'] !== '') {
                    require_once $listener['file'];
                }

                if (!class_exists($listener['class'])) {
                    throw new PHPUnit_Framework_Exception(
                        sprintf(
                            'Class "%s" does not exist',
                            $listener['class']
                        )
                    );
                }

                $listenerClass = new ReflectionClass($listener['class']);

                if (!$listenerClass->implementsInterface(PHPUnit_Framework_TestListener::class)) {
                    throw new PHPUnit_Framework_Exception(
                        sprintf(
                            'Class "%s" does not implement the PHPUnit_Framework_TestListener interface',
                            $listener['class']
                        )
                    );
                }

                if (count($listener['arguments']) == 0) {
                    $listener = new $listener['class'];
                } else {
                    $listener = $listenerClass->newInstanceArgs(
                        $listener['arguments']
                    );
                }

                $arguments['listeners'][] = $listener;
            }

            $loggingConfiguration = $arguments['configuration']->getLoggingConfiguration();

            if (isset($loggingConfiguration['coverage-clover']) &&
                !isset($arguments['coverageClover'])) {
                $arguments['coverageClover'] = $loggingConfiguration['coverage-clover'];
            }

            if (isset($loggingConfiguration['coverage-crap4j']) &&
                !isset($arguments['coverageCrap4J'])) {
                $arguments['coverageCrap4J'] = $loggingConfiguration['coverage-crap4j'];

                if (isset($loggingConfiguration['crap4jThreshold']) &&
                    !isset($arguments['crap4jThreshold'])) {
                    $arguments['crap4jThreshold'] = $loggingConfiguration['crap4jThreshold'];
                }
            }

            if (isset($loggingConfiguration['coverage-html']) &&
                !isset($arguments['coverageHtml'])) {
                if (isset($loggingConfiguration['lowUpperBound']) &&
                    !isset($arguments['reportLowUpperBound'])) {
                    $arguments['reportLowUpperBound'] = $loggingConfiguration['lowUpperBound'];
                }

                if (isset($loggingConfiguration['highLowerBound']) &&
                    !isset($arguments['reportHighLowerBound'])) {
                    $arguments['reportHighLowerBound'] = $loggingConfiguration['highLowerBound'];
                }

                $arguments['coverageHtml'] = $loggingConfiguration['coverage-html'];
            }

            if (isset($loggingConfiguration['coverage-php']) &&
                !isset($arguments['coveragePHP'])) {
                $arguments['coveragePHP'] = $loggingConfiguration['coverage-php'];
            }

            if (isset($loggingConfiguration['coverage-text']) &&
                !isset($arguments['coverageText'])) {
                $arguments['coverageText'] = $loggingConfiguration['coverage-text'];
                if (isset($loggingConfiguration['coverageTextShowUncoveredFiles'])) {
                    $arguments['coverageTextShowUncoveredFiles'] = $loggingConfiguration['coverageTextShowUncoveredFiles'];
                } else {
                    $arguments['coverageTextShowUncoveredFiles'] = false;
                }
                if (isset($loggingConfiguration['coverageTextShowOnlySummary'])) {
                    $arguments['coverageTextShowOnlySummary'] = $loggingConfiguration['coverageTextShowOnlySummary'];
                } else {
                    $arguments['coverageTextShowOnlySummary'] = false;
                }
            }

            if (isset($loggingConfiguration['coverage-xml']) &&
                !isset($arguments['coverageXml'])) {
                $arguments['coverageXml'] = $loggingConfiguration['coverage-xml'];
            }

            if (isset($loggingConfiguration['json']) &&
                !isset($arguments['jsonLogfile'])) {
                $arguments['jsonLogfile'] = $loggingConfiguration['json'];
            }

            if (isset($loggingConfiguration['plain'])) {
                $arguments['listeners'][] = new PHPUnit_TextUI_ResultPrinter(
                    $loggingConfiguration['plain'],
                    true
                );
            }

            if (isset($loggingConfiguration['tap']) &&
                !isset($arguments['tapLogfile'])) {
                $arguments['tapLogfile'] = $loggingConfiguration['tap'];
            }

            if (isset($loggingConfiguration['teamcity']) &&
                !isset($arguments['teamcityLogfile'])) {
                $arguments['teamcityLogfile'] = $loggingConfiguration['teamcity'];
            }

            if (isset($loggingConfiguration['junit']) &&
                !isset($arguments['junitLogfile'])) {
                $arguments['junitLogfile'] = $loggingConfiguration['junit'];

                if (isset($loggingConfiguration['logIncompleteSkipped']) &&
                    !isset($arguments['logIncompleteSkipped'])) {
                    $arguments['logIncompleteSkipped'] = $loggingConfiguration['logIncompleteSkipped'];
                }
            }

            if (isset($loggingConfiguration['testdox-html']) &&
                !isset($arguments['testdoxHTMLFile'])) {
                $arguments['testdoxHTMLFile'] = $loggingConfiguration['testdox-html'];
            }

            if (isset($loggingConfiguration['testdox-text']) &&
                !isset($arguments['testdoxTextFile'])) {
                $arguments['testdoxTextFile'] = $loggingConfiguration['testdox-text'];
            }

            if (isset($loggingConfiguration['testdox-xml']) &&
                !isset($arguments['testdoxXMLFile'])) {
                $arguments['testdoxXMLFile'] = $loggingConfiguration['testdox-xml'];
            }

            $testdoxGroupConfiguration = $arguments['configuration']->getTestdoxGroupConfiguration();

            if (isset($testdoxGroupConfiguration['include']) &&
                !isset($arguments['testdoxGroups'])) {
                $arguments['testdoxGroups'] = $testdoxGroupConfiguration['include'];
            }

            if (isset($testdoxGroupConfiguration['exclude']) &&
                !isset($arguments['testdoxExcludeGroups'])) {
                $arguments['testdoxExcludeGroups'] = $testdoxGroupConfiguration['exclude'];
            }
        }

        $arguments['addUncoveredFilesFromWhitelist']                  = isset($arguments['addUncoveredFilesFromWhitelist'])                  ? $arguments['addUncoveredFilesFromWhitelist']                  : true;
        $arguments['processUncoveredFilesFromWhitelist']              = isset($arguments['processUncoveredFilesFromWhitelist'])              ? $arguments['processUncoveredFilesFromWhitelist']              : false;
        $arguments['backupGlobals']                                   = isset($arguments['backupGlobals'])                                   ? $arguments['backupGlobals']                                   : null;
        $arguments['backupStaticAttributes']                          = isset($arguments['backupStaticAttributes'])                          ? $arguments['backupStaticAttributes']                          : null;
        $arguments['beStrictAboutChangesToGlobalState']               = isset($arguments['beStrictAboutChangesToGlobalState'])               ? $arguments['beStrictAboutChangesToGlobalState']               : null;
        $arguments['cacheTokens']                                     = isset($arguments['cacheTokens'])                                     ? $arguments['cacheTokens']                                     : false;
        $arguments['columns']                                         = isset($arguments['columns'])                                         ? $arguments['columns']                                         : 80;
        $arguments['colors']                                          = isset($arguments['colors'])                                          ? $arguments['colors']                                          : PHPUnit_TextUI_ResultPrinter::COLOR_DEFAULT;
        $arguments['convertErrorsToExceptions']                       = isset($arguments['convertErrorsToExceptions'])                       ? $arguments['convertErrorsToExceptions']                       : true;
        $arguments['convertNoticesToExceptions']                      = isset($arguments['convertNoticesToExceptions'])                      ? $arguments['convertNoticesToExceptions']                      : true;
        $arguments['convertWarningsToExceptions']                     = isset($arguments['convertWarningsToExceptions'])                     ? $arguments['convertWarningsToExceptions']                     : true;
        $arguments['excludeGroups']                                   = isset($arguments['excludeGroups'])                                   ? $arguments['excludeGroups']                                   : [];
        $arguments['groups']                                          = isset($arguments['groups'])                                          ? $arguments['groups']                                          : [];
        $arguments['logIncompleteSkipped']                            = isset($arguments['logIncompleteSkipped'])                            ? $arguments['logIncompleteSkipped']                            : false;
        $arguments['processIsolation']                                = isset($arguments['processIsolation'])                                ? $arguments['processIsolation']                                : false;
        $arguments['repeat']                                          = isset($arguments['repeat'])                                          ? $arguments['repeat']                                          : false;
        $arguments['reportHighLowerBound']                            = isset($arguments['reportHighLowerBound'])                            ? $arguments['reportHighLowerBound']                            : 90;
        $arguments['reportLowUpperBound']                             = isset($arguments['reportLowUpperBound'])                             ? $arguments['reportLowUpperBound']                             : 50;
        $arguments['crap4jThreshold']                                 = isset($arguments['crap4jThreshold'])                                 ? $arguments['crap4jThreshold']                                 : 30;
        $arguments['stopOnError']                                     = isset($arguments['stopOnError'])                                     ? $arguments['stopOnError']                                     : false;
        $arguments['stopOnFailure']                                   = isset($arguments['stopOnFailure'])                                   ? $arguments['stopOnFailure']                                   : false;
        $arguments['stopOnWarning']                                   = isset($arguments['stopOnWarning'])                                   ? $arguments['stopOnWarning']                                   : false;
        $arguments['stopOnIncomplete']                                = isset($arguments['stopOnIncomplete'])                                ? $arguments['stopOnIncomplete']                                : false;
        $arguments['stopOnRisky']                                     = isset($arguments['stopOnRisky'])                                     ? $arguments['stopOnRisky']                                     : false;
        $arguments['stopOnSkipped']                                   = isset($arguments['stopOnSkipped'])                                   ? $arguments['stopOnSkipped']                                   : false;
        $arguments['failOnWarning']                                   = isset($arguments['failOnWarning'])                                   ? $arguments['failOnWarning']                                   : false;
        $arguments['failOnRisky']                                     = isset($arguments['failOnRisky'])                                     ? $arguments['failOnRisky']                                     : false;
        $arguments['timeoutForSmallTests']                            = isset($arguments['timeoutForSmallTests'])                            ? $arguments['timeoutForSmallTests']                            : 1;
        $arguments['timeoutForMediumTests']                           = isset($arguments['timeoutForMediumTests'])                           ? $arguments['timeoutForMediumTests']                           : 10;
        $arguments['timeoutForLargeTests']                            = isset($arguments['timeoutForLargeTests'])                            ? $arguments['timeoutForLargeTests']                            : 60;
        $arguments['reportUselessTests']                              = isset($arguments['reportUselessTests'])                              ? $arguments['reportUselessTests']                              : false;
        $arguments['strictCoverage']                                  = isset($arguments['strictCoverage'])                                  ? $arguments['strictCoverage']                                  : false;
        $arguments['disallowTestOutput']                              = isset($arguments['disallowTestOutput'])                              ? $arguments['disallowTestOutput']                              : false;
        $arguments['enforceTimeLimit']                                = isset($arguments['enforceTimeLimit'])                                ? $arguments['enforceTimeLimit']                                : false;
        $arguments['disallowTodoAnnotatedTests']                      = isset($arguments['disallowTodoAnnotatedTests'])                      ? $arguments['disallowTodoAnnotatedTests']                      : false;
        $arguments['beStrictAboutResourceUsageDuringSmallTests']      = isset($arguments['beStrictAboutResourceUsageDuringSmallTests'])      ? $arguments['beStrictAboutResourceUsageDuringSmallTests']      : false;
        $arguments['reverseList']                                     = isset($arguments['reverseList'])                                     ? $arguments['reverseList']                                     : false;
        $arguments['registerMockObjectsFromTestArgumentsRecursively'] = isset($arguments['registerMockObjectsFromTestArgumentsRecursively']) ? $arguments['registerMockObjectsFromTestArgumentsRecursively'] : false;
        $arguments['verbose']                                         = isset($arguments['verbose'])                                         ? $arguments['verbose']                                         : false;
        $arguments['testdoxExcludeGroups']                            = isset($arguments['testdoxExcludeGroups'])                            ? $arguments['testdoxExcludeGroups']                            : [];
        $arguments['testdoxGroups']                                   = isset($arguments['testdoxGroups'])                                   ? $arguments['testdoxGroups']                                   : [];
    }

    /**
     * @param string $type
     * @param string $message
     */
    private function writeMessage($type, $message)
    {
        if (!$this->messagePrinted) {
            $this->write("\n");
        }

        $this->write(
            sprintf(
                "%-15s%s\n",
                $type . ':',
                $message
            )
        );

        $this->messagePrinted = true;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A TestRunner for the Command Line Interface (CLI)
 * PHP SAPI Module.
 */
class PHPUnit_TextUI_Command
{
    /**
     * @var array
     */
    protected $arguments = [
        'listGroups'              => false,
        'listSuites'              => false,
        'loader'                  => null,
        'useDefaultConfiguration' => true,
        'loadedExtensions'        => [],
        'notLoadedExtensions'     => []
    ];

    /**
     * @var array
     */
    protected $options = [];

    /**
     * @var array
     */
    protected $longOptions = [
        'atleast-version='        => null,
        'bootstrap='              => null,
        'colors=='                => null,
        'columns='                => null,
        'configuration='          => null,
        'coverage-clover='        => null,
        'coverage-crap4j='        => null,
        'coverage-html='          => null,
        'coverage-php='           => null,
        'coverage-text=='         => null,
        'coverage-xml='           => null,
        'debug'                   => null,
        'disallow-test-output'    => null,
        'disallow-resource-usage' => null,
        'disallow-todo-tests'     => null,
        'enforce-time-limit'      => null,
        'exclude-group='          => null,
        'filter='                 => null,
        'generate-configuration'  => null,
        'group='                  => null,
        'help'                    => null,
        'include-path='           => null,
        'list-groups'             => null,
        'list-suites'             => null,
        'loader='                 => null,
        'log-json='               => null,
        'log-junit='              => null,
        'log-tap='                => null,
        'log-teamcity='           => null,
        'no-configuration'        => null,
        'no-coverage'             => null,
        'no-extensions'           => null,
        'no-globals-backup'       => null,
        'printer='                => null,
        'process-isolation'       => null,
        'repeat='                 => null,
        'report-useless-tests'    => null,
        'reverse-list'            => null,
        'static-backup'           => null,
        'stderr'                  => null,
        'stop-on-error'           => null,
        'stop-on-failure'         => null,
        'stop-on-warning'         => null,
        'stop-on-incomplete'      => null,
        'stop-on-risky'           => null,
        'stop-on-skipped'         => null,
        'fail-on-warning'         => null,
        'fail-on-risky'           => null,
        'strict-coverage'         => null,
        'disable-coverage-ignore' => null,
        'strict-global-state'     => null,
        'tap'                     => null,
        'teamcity'                => null,
        'testdox'                 => null,
        'testdox-group='          => null,
        'testdox-exclude-group='  => null,
        'testdox-html='           => null,
        'testdox-text='           => null,
        'testdox-xml='            => null,
        'test-suffix='            => null,
        'testsuite='              => null,
        'verbose'                 => null,
        'version'                 => null,
        'whitelist='              => null
    ];

    /**
     * @var bool
     */
    private $versionStringPrinted = false;

    /**
     * @param bool $exit
     */
    public static function main($exit = true)
    {
        $command = new static;

        return $command->run($_SERVER['argv'], $exit);
    }

    /**
     * @param array $argv
     * @param bool  $exit
     *
     * @return int
     */
    public function run(array $argv, $exit = true)
    {
        $this->handleArguments($argv);

        $runner = $this->createRunner();

        if (is_object($this->arguments['test']) &&
            $this->arguments['test'] instanceof PHPUnit_Framework_Test) {
            $suite = $this->arguments['test'];
        } else {
            $suite = $runner->getTest(
                $this->arguments['test'],
                $this->arguments['testFile'],
                $this->arguments['testSuffixes']
            );
        }

        if ($this->arguments['listGroups']) {
            $this->printVersionString();

            print "Available test group(s):\n";

            $groups = $suite->getGroups();
            sort($groups);

            foreach ($groups as $group) {
                print " - $group\n";
            }

            if ($exit) {
                exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
            } else {
                return PHPUnit_TextUI_TestRunner::SUCCESS_EXIT;
            }
        }

        if ($this->arguments['listSuites']) {
            $this->printVersionString();

            print "Available test suite(s):\n";

            $configuration = PHPUnit_Util_Configuration::getInstance(
                $this->arguments['configuration']
            );

            $suiteNames = $configuration->getTestSuiteNames();
            foreach ($suiteNames as $suiteName) {
                print " - $suiteName\n";
            }

            if ($exit) {
                exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
            } else {
                return PHPUnit_TextUI_TestRunner::SUCCESS_EXIT;
            }
        }

        unset($this->arguments['test']);
        unset($this->arguments['testFile']);

        try {
            $result = $runner->doRun($suite, $this->arguments, $exit);
        } catch (PHPUnit_Framework_Exception $e) {
            print $e->getMessage() . "\n";
        }

        $return = PHPUnit_TextUI_TestRunner::FAILURE_EXIT;

        if (isset($result) && $result->wasSuccessful(false)) {
            $return = PHPUnit_TextUI_TestRunner::SUCCESS_EXIT;
        } elseif (!isset($result) || $result->errorCount() > 0) {
            $return = PHPUnit_TextUI_TestRunner::EXCEPTION_EXIT;
        }

        if ($exit) {
            exit($return);
        }

        return $return;
    }

    /**
     * Create a TestRunner, override in subclasses.
     *
     * @return PHPUnit_TextUI_TestRunner
     */
    protected function createRunner()
    {
        return new PHPUnit_TextUI_TestRunner($this->arguments['loader']);
    }

    /**
     * Handles the command-line arguments.
     *
     * A child class of PHPUnit_TextUI_Command can hook into the argument
     * parsing by adding the switch(es) to the $longOptions array and point to a
     * callback method that handles the switch(es) in the child class like this
     *
     * <code>
     * <?php
     * class MyCommand extends PHPUnit_TextUI_Command
     * {
     *     public function __construct()
     *     {
     *         // my-switch won't accept a value, it's an on/off
     *         $this->longOptions['my-switch'] = 'myHandler';
     *         // my-secondswitch will accept a value - note the equals sign
     *         $this->longOptions['my-secondswitch='] = 'myOtherHandler';
     *     }
     *
     *     // --my-switch  -> myHandler()
     *     protected function myHandler()
     *     {
     *     }
     *
     *     // --my-secondswitch foo -> myOtherHandler('foo')
     *     protected function myOtherHandler ($value)
     *     {
     *     }
     *
     *     // You will also need this - the static keyword in the
     *     // PHPUnit_TextUI_Command will mean that it'll be
     *     // PHPUnit_TextUI_Command that gets instantiated,
     *     // not MyCommand
     *     public static function main($exit = true)
     *     {
     *         $command = new static;
     *
     *         return $command->run($_SERVER['argv'], $exit);
     *     }
     *
     * }
     * </code>
     *
     * @param array $argv
     */
    protected function handleArguments(array $argv)
    {
        if (defined('__PHPUNIT_PHAR__')) {
            $this->longOptions['check-version'] = null;
            $this->longOptions['selfupdate']    = null;
            $this->longOptions['self-update']   = null;
            $this->longOptions['selfupgrade']   = null;
            $this->longOptions['self-upgrade']  = null;
        }

        try {
            $this->options = PHPUnit_Util_Getopt::getopt(
                $argv,
                'd:c:hv',
                array_keys($this->longOptions)
            );
        } catch (PHPUnit_Framework_Exception $e) {
            $this->showError($e->getMessage());
        }

        foreach ($this->options[0] as $option) {
            switch ($option[0]) {
                case '--colors':
                    $this->arguments['colors'] = $option[1] ?: PHPUnit_TextUI_ResultPrinter::COLOR_AUTO;
                    break;

                case '--bootstrap':
                    $this->arguments['bootstrap'] = $option[1];
                    break;

                case '--columns':
                    if (is_numeric($option[1])) {
                        $this->arguments['columns'] = (int) $option[1];
                    } elseif ($option[1] == 'max') {
                        $this->arguments['columns'] = 'max';
                    }
                    break;

                case 'c':
                case '--configuration':
                    $this->arguments['configuration'] = $option[1];
                    break;

                case '--coverage-clover':
                    $this->arguments['coverageClover'] = $option[1];
                    break;

                case '--coverage-crap4j':
                    $this->arguments['coverageCrap4J'] = $option[1];
                    break;

                case '--coverage-html':
                    $this->arguments['coverageHtml'] = $option[1];
                    break;

                case '--coverage-php':
                    $this->arguments['coveragePHP'] = $option[1];
                    break;

                case '--coverage-text':
                    if ($option[1] === null) {
                        $option[1] = 'php://stdout';
                    }

                    $this->arguments['coverageText']                   = $option[1];
                    $this->arguments['coverageTextShowUncoveredFiles'] = false;
                    $this->arguments['coverageTextShowOnlySummary']    = false;
                    break;

                case '--coverage-xml':
                    $this->arguments['coverageXml'] = $option[1];
                    break;

                case 'd':
                    $ini = explode('=', $option[1]);

                    if (isset($ini[0])) {
                        if (isset($ini[1])) {
                            ini_set($ini[0], $ini[1]);
                        } else {
                            ini_set($ini[0], true);
                        }
                    }
                    break;

                case '--debug':
                    $this->arguments['debug'] = true;
                    break;

                case 'h':
                case '--help':
                    $this->showHelp();
                    exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
                    break;

                case '--filter':
                    $this->arguments['filter'] = $option[1];
                    break;

                case '--testsuite':
                    $this->arguments['testsuite'] = $option[1];
                    break;

                case '--generate-configuration':
                    $this->printVersionString();

                    printf(
                        "Generating phpunit.xml in %s\n\n",
                        getcwd()
                    );

                    print 'Bootstrap script (relative to path shown above; default: vendor/autoload.php): ';
                    $bootstrapScript = trim(fgets(STDIN));

                    print 'Tests directory (relative to path shown above; default: tests): ';
                    $testsDirectory = trim(fgets(STDIN));

                    print 'Source directory (relative to path shown above; default: src): ';
                    $src = trim(fgets(STDIN));

                    if ($bootstrapScript == '') {
                        $bootstrapScript = 'vendor/autoload.php';
                    }

                    if ($testsDirectory == '') {
                        $testsDirectory = 'tests';
                    }

                    if ($src == '') {
                        $src = 'src';
                    }

                    $generator = new PHPUnit_Util_ConfigurationGenerator;

                    file_put_contents(
                        'phpunit.xml',
                        $generator->generateDefaultConfiguration(
                            PHPUnit_Runner_Version::series(),
                            $bootstrapScript,
                            $testsDirectory,
                            $src
                        )
                    );

                    printf(
                        "\nGenerated phpunit.xml in %s\n",
                        getcwd()
                    );

                    exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
                    break;

                case '--group':
                    $this->arguments['groups'] = explode(',', $option[1]);
                    break;

                case '--exclude-group':
                    $this->arguments['excludeGroups'] = explode(
                        ',',
                        $option[1]
                    );
                    break;

                case '--test-suffix':
                    $this->arguments['testSuffixes'] = explode(
                        ',',
                        $option[1]
                    );
                    break;

                case '--include-path':
                    $includePath = $option[1];
                    break;

                case '--list-groups':
                    $this->arguments['listGroups'] = true;
                    break;

                case '--list-suites':
                    $this->arguments['listSuites'] = true;
                    break;

                case '--printer':
                    $this->arguments['printer'] = $option[1];
                    break;

                case '--loader':
                    $this->arguments['loader'] = $option[1];
                    break;

                case '--log-json':
                    $this->arguments['jsonLogfile'] = $option[1];
                    break;

                case '--log-junit':
                    $this->arguments['junitLogfile'] = $option[1];
                    break;

                case '--log-tap':
                    $this->arguments['tapLogfile'] = $option[1];
                    break;

                case '--log-teamcity':
                    $this->arguments['teamcityLogfile'] = $option[1];
                    break;

                case '--process-isolation':
                    $this->arguments['processIsolation'] = true;
                    break;

                case '--repeat':
                    $this->arguments['repeat'] = (int) $option[1];
                    break;

                case '--stderr':
                    $this->arguments['stderr'] = true;
                    break;

                case '--stop-on-error':
                    $this->arguments['stopOnError'] = true;
                    break;

                case '--stop-on-failure':
                    $this->arguments['stopOnFailure'] = true;
                    break;

                case '--stop-on-warning':
                    $this->arguments['stopOnWarning'] = true;
                    break;

                case '--stop-on-incomplete':
                    $this->arguments['stopOnIncomplete'] = true;
                    break;

                case '--stop-on-risky':
                    $this->arguments['stopOnRisky'] = true;
                    break;

                case '--stop-on-skipped':
                    $this->arguments['stopOnSkipped'] = true;
                    break;

                case '--fail-on-warning':
                    $this->arguments['failOnWarning'] = true;
                    break;

                case '--fail-on-risky':
                    $this->arguments['failOnRisky'] = true;
                    break;

                case '--tap':
                    $this->arguments['printer'] = 'PHPUnit_Util_Log_TAP';
                    break;

                case '--teamcity':
                    $this->arguments['printer'] = 'PHPUnit_Util_Log_TeamCity';
                    break;

                case '--testdox':
                    $this->arguments['printer'] = 'PHPUnit_Util_TestDox_ResultPrinter_Text';
                    break;

                case '--testdox-group':
                    $this->arguments['testdoxGroups'] = explode(
                        ',',
                        $option[1]
                    );
                    break;

                case '--testdox-exclude-group':
                    $this->arguments['testdoxExcludeGroups'] = explode(
                        ',',
                        $option[1]
                    );
                    break;

                case '--testdox-html':
                    $this->arguments['testdoxHTMLFile'] = $option[1];
                    break;

                case '--testdox-text':
                    $this->arguments['testdoxTextFile'] = $option[1];
                    break;

                case '--testdox-xml':
                    $this->arguments['testdoxXMLFile'] = $option[1];
                    break;

                case '--no-configuration':
                    $this->arguments['useDefaultConfiguration'] = false;
                    break;

                case '--no-extensions':
                    $this->arguments['noExtensions'] = true;
                    break;

                case '--no-coverage':
                    $this->arguments['noCoverage'] = true;
                    break;

                case '--no-globals-backup':
                    $this->arguments['backupGlobals'] = false;
                    break;

                case '--static-backup':
                    $this->arguments['backupStaticAttributes'] = true;
                    break;

                case 'v':
                case '--verbose':
                    $this->arguments['verbose'] = true;
                    break;

                case '--atleast-version':
                    exit(version_compare(PHPUnit_Runner_Version::id(), $option[1], '>=')
                        ? PHPUnit_TextUI_TestRunner::SUCCESS_EXIT
                        : PHPUnit_TextUI_TestRunner::FAILURE_EXIT
                    );
                    break;

                case '--version':
                    $this->printVersionString();
                    exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
                    break;

                case '--report-useless-tests':
                    $this->arguments['reportUselessTests'] = true;
                    break;

                case '--strict-coverage':
                    $this->arguments['strictCoverage'] = true;
                    break;

                case '--disable-coverage-ignore':
                    $this->arguments['disableCodeCoverageIgnore'] = true;
                    break;

                case '--strict-global-state':
                    $this->arguments['beStrictAboutChangesToGlobalState'] = true;
                    break;

                case '--disallow-test-output':
                    $this->arguments['disallowTestOutput'] = true;
                    break;

                case '--disallow-resource-usage':
                    $this->arguments['beStrictAboutResourceUsageDuringSmallTests'] = true;
                    break;

                case '--enforce-time-limit':
                    $this->arguments['enforceTimeLimit'] = true;
                    break;

                case '--disallow-todo-tests':
                    $this->arguments['disallowTodoAnnotatedTests'] = true;
                    break;

                case '--reverse-list':
                    $this->arguments['reverseList'] = true;
                    break;

                case '--check-version':
                    $this->handleVersionCheck();
                    break;

                case '--selfupdate':
                case '--self-update':
                    $this->handleSelfUpdate();
                    break;

                case '--selfupgrade':
                case '--self-upgrade':
                    $this->handleSelfUpdate(true);
                    break;

                case '--whitelist':
                    $this->arguments['whitelist'] = $option[1];
                    break;

                default:
                    $optionName = str_replace('--', '', $option[0]);

                    $handler = null;
                    if (isset($this->longOptions[$optionName])) {
                        $handler = $this->longOptions[$optionName];
                    } elseif (isset($this->longOptions[$optionName . '='])) {
                        $handler = $this->longOptions[$optionName . '='];
                    }

                    if (isset($handler) && is_callable([$this, $handler])) {
                        $this->$handler($option[1]);
                    }
            }
        }

        $this->handleCustomTestSuite();

        if (!isset($this->arguments['test'])) {
            if (isset($this->options[1][0])) {
                $this->arguments['test'] = $this->options[1][0];
            }

            if (isset($this->options[1][1])) {
                $this->arguments['testFile'] = realpath($this->options[1][1]);
            } else {
                $this->arguments['testFile'] = '';
            }

            if (isset($this->arguments['test']) &&
                is_file($this->arguments['test']) &&
                substr($this->arguments['test'], -5, 5) != '.phpt') {
                $this->arguments['testFile'] = realpath($this->arguments['test']);
                $this->arguments['test']     = substr($this->arguments['test'], 0, strrpos($this->arguments['test'], '.'));
            }
        }

        if (!isset($this->arguments['testSuffixes'])) {
            $this->arguments['testSuffixes'] = ['Test.php', '.phpt'];
        }

        if (isset($includePath)) {
            ini_set(
                'include_path',
                $includePath . PATH_SEPARATOR . ini_get('include_path')
            );
        }

        if ($this->arguments['loader'] !== null) {
            $this->arguments['loader'] = $this->handleLoader($this->arguments['loader']);
        }

        if (isset($this->arguments['configuration']) &&
            is_dir($this->arguments['configuration'])) {
            $configurationFile = $this->arguments['configuration'] . '/phpunit.xml';

            if (file_exists($configurationFile)) {
                $this->arguments['configuration'] = realpath(
                    $configurationFile
                );
            } elseif (file_exists($configurationFile . '.dist')) {
                $this->arguments['configuration'] = realpath(
                    $configurationFile . '.dist'
                );
            }
        } elseif (!isset($this->arguments['configuration']) &&
                  $this->arguments['useDefaultConfiguration']) {
            if (file_exists('phpunit.xml')) {
                $this->arguments['configuration'] = realpath('phpunit.xml');
            } elseif (file_exists('phpunit.xml.dist')) {
                $this->arguments['configuration'] = realpath(
                    'phpunit.xml.dist'
                );
            }
        }

        if (isset($this->arguments['configuration'])) {
            try {
                $configuration = PHPUnit_Util_Configuration::getInstance(
                    $this->arguments['configuration']
                );
            } catch (Throwable $e) {
                print $e->getMessage() . "\n";
                exit(PHPUnit_TextUI_TestRunner::FAILURE_EXIT);
            } catch (Exception $e) {
                print $e->getMessage() . "\n";
                exit(PHPUnit_TextUI_TestRunner::FAILURE_EXIT);
            }

            $phpunitConfiguration = $configuration->getPHPUnitConfiguration();

            $configuration->handlePHPConfiguration();

            /*
             * Issue #1216
             */
            if (isset($this->arguments['bootstrap'])) {
                $this->handleBootstrap($this->arguments['bootstrap']);
            } elseif (isset($phpunitConfiguration['bootstrap'])) {
                $this->handleBootstrap($phpunitConfiguration['bootstrap']);
            }

            /*
             * Issue #657
             */
            if (isset($phpunitConfiguration['stderr']) && ! isset($this->arguments['stderr'])) {
                $this->arguments['stderr'] = $phpunitConfiguration['stderr'];
            }

            if (isset($phpunitConfiguration['extensionsDirectory']) && !isset($this->arguments['noExtensions']) && extension_loaded('phar')) {
                $this->handleExtensions($phpunitConfiguration['extensionsDirectory']);
            }

            if (isset($phpunitConfiguration['columns']) && ! isset($this->arguments['columns'])) {
                $this->arguments['columns'] = $phpunitConfiguration['columns'];
            }

            if (!isset($this->arguments['printer']) && isset($phpunitConfiguration['printerClass'])) {
                if (isset($phpunitConfiguration['printerFile'])) {
                    $file = $phpunitConfiguration['printerFile'];
                } else {
                    $file = '';
                }

                $this->arguments['printer'] = $this->handlePrinter(
                    $phpunitConfiguration['printerClass'],
                    $file
                );
            }

            if (isset($phpunitConfiguration['testSuiteLoaderClass'])) {
                if (isset($phpunitConfiguration['testSuiteLoaderFile'])) {
                    $file = $phpunitConfiguration['testSuiteLoaderFile'];
                } else {
                    $file = '';
                }

                $this->arguments['loader'] = $this->handleLoader(
                    $phpunitConfiguration['testSuiteLoaderClass'],
                    $file
                );
            }

            if (!isset($this->arguments['test'])) {
                $testSuite = $configuration->getTestSuiteConfiguration(isset($this->arguments['testsuite']) ? $this->arguments['testsuite'] : null);

                if ($testSuite !== null) {
                    $this->arguments['test'] = $testSuite;
                }
            }
        } elseif (isset($this->arguments['bootstrap'])) {
            $this->handleBootstrap($this->arguments['bootstrap']);
        }

        if (isset($this->arguments['printer']) &&
            is_string($this->arguments['printer'])) {
            $this->arguments['printer'] = $this->handlePrinter($this->arguments['printer']);
        }

        if (isset($this->arguments['test']) && is_string($this->arguments['test']) && substr($this->arguments['test'], -5, 5) == '.phpt') {
            $test = new PHPUnit_Extensions_PhptTestCase($this->arguments['test']);

            $this->arguments['test'] = new PHPUnit_Framework_TestSuite;
            $this->arguments['test']->addTest($test);
        }

        if (!isset($this->arguments['test']) ||
            (isset($this->arguments['testDatabaseLogRevision']) && !isset($this->arguments['testDatabaseDSN']))) {
            $this->showHelp();
            exit(PHPUnit_TextUI_TestRunner::EXCEPTION_EXIT);
        }
    }

    /**
     * Handles the loading of the PHPUnit_Runner_TestSuiteLoader implementation.
     *
     * @param string $loaderClass
     * @param string $loaderFile
     *
     * @return PHPUnit_Runner_TestSuiteLoader
     */
    protected function handleLoader($loaderClass, $loaderFile = '')
    {
        if (!class_exists($loaderClass, false)) {
            if ($loaderFile == '') {
                $loaderFile = PHPUnit_Util_Filesystem::classNameToFilename(
                    $loaderClass
                );
            }

            $loaderFile = stream_resolve_include_path($loaderFile);

            if ($loaderFile) {
                require $loaderFile;
            }
        }

        if (class_exists($loaderClass, false)) {
            $class = new ReflectionClass($loaderClass);

            if ($class->implementsInterface('PHPUnit_Runner_TestSuiteLoader') &&
                $class->isInstantiable()) {
                return $class->newInstance();
            }
        }

        if ($loaderClass == 'PHPUnit_Runner_StandardTestSuiteLoader') {
            return;
        }

        $this->showError(
            sprintf(
                'Could not use "%s" as loader.',
                $loaderClass
            )
        );
    }

    /**
     * Handles the loading of the PHPUnit_Util_Printer implementation.
     *
     * @param string $printerClass
     * @param string $printerFile
     *
     * @return PHPUnit_Util_Printer|string
     */
    protected function handlePrinter($printerClass, $printerFile = '')
    {
        if (!class_exists($printerClass, false)) {
            if ($printerFile == '') {
                $printerFile = PHPUnit_Util_Filesystem::classNameToFilename(
                    $printerClass
                );
            }

            $printerFile = stream_resolve_include_path($printerFile);

            if ($printerFile) {
                require $printerFile;
            }
        }

        if (class_exists($printerClass)) {
            $class = new ReflectionClass($printerClass);

            if ($class->implementsInterface('PHPUnit_Framework_TestListener') &&
                $class->isSubclassOf('PHPUnit_Util_Printer') &&
                $class->isInstantiable()) {
                if ($class->isSubclassOf('PHPUnit_TextUI_ResultPrinter')) {
                    return $printerClass;
                }

                $outputStream = isset($this->arguments['stderr']) ? 'php://stderr' : null;

                return $class->newInstance($outputStream);
            }
        }

        $this->showError(
            sprintf(
                'Could not use "%s" as printer.',
                $printerClass
            )
        );
    }

    /**
     * Loads a bootstrap file.
     *
     * @param string $filename
     */
    protected function handleBootstrap($filename)
    {
        try {
            PHPUnit_Util_Fileloader::checkAndLoad($filename);
        } catch (PHPUnit_Framework_Exception $e) {
            $this->showError($e->getMessage());
        }
    }

    protected function handleSelfUpdate($upgrade = false)
    {
        $this->printVersionString();

        if ($upgrade) {
            print "Warning: Deprecated --self-upgrade used\n\n";
        } else {
            print "Warning: Deprecated --self-update used\n\n";
        }

        $localFilename = realpath($_SERVER['argv'][0]);

        if (!is_writable($localFilename)) {
            print 'No write permission to update ' . $localFilename . "\n";
            exit(PHPUnit_TextUI_TestRunner::EXCEPTION_EXIT);
        }

        if (!extension_loaded('openssl')) {
            print "The OpenSSL extension is not loaded.\n";
            exit(PHPUnit_TextUI_TestRunner::EXCEPTION_EXIT);
        }

        if (!$upgrade) {
            $remoteFilename = sprintf(
                'https://phar.phpunit.de/phpunit-%s.phar',
                file_get_contents(
                    sprintf(
                        'https://phar.phpunit.de/latest-version-of/phpunit-%s',
                        PHPUnit_Runner_Version::series()
                    )
                )
            );
        } else {
            $remoteFilename = sprintf(
                'https://phar.phpunit.de/phpunit%s.phar',
                PHPUnit_Runner_Version::getReleaseChannel()
            );
        }

        $tempFilename = tempnam(sys_get_temp_dir(), 'phpunit') . '.phar';

        // Workaround for https://bugs.php.net/bug.php?id=65538
        $caFile = dirname($tempFilename) . '/ca.pem';
        copy(__PHPUNIT_PHAR_ROOT__ . '/ca.pem', $caFile);

        print 'Updating the PHPUnit PHAR ... ';

        $options = [
            'ssl' => [
                'allow_self_signed' => false,
                'cafile'            => $caFile,
                'verify_peer'       => true
            ]
        ];

        file_put_contents(
            $tempFilename,
            file_get_contents(
                $remoteFilename,
                false,
                stream_context_create($options)
            )
        );

        chmod($tempFilename, 0777 & ~umask());

        try {
            $phar = new Phar($tempFilename);
            unset($phar);
            rename($tempFilename, $localFilename);
            unlink($caFile);
        } catch (Throwable $_e) {
            $e = $_e;
        } catch (Exception $_e) {
            $e = $_e;
        }

        if (isset($e)) {
            unlink($caFile);
            unlink($tempFilename);
            print " done\n\n" . $e->getMessage() . "\n";
            exit(2);
        }

        print " done\n";
        exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
    }

    protected function handleVersionCheck()
    {
        $this->printVersionString();

        $latestVersion = file_get_contents('https://phar.phpunit.de/latest-version-of/phpunit');
        $isOutdated    = version_compare($latestVersion, PHPUnit_Runner_Version::id(), '>');

        if ($isOutdated) {
            print "You are not using the latest version of PHPUnit.\n";
            print 'Use "phpunit --self-upgrade" to install PHPUnit ' . $latestVersion . "\n";
        } else {
            print "You are using the latest version of PHPUnit.\n";
        }

        exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
    }

    /**
     * Show the help message.
     */
    protected function showHelp()
    {
        $this->printVersionString();

        print <<<EOT
Usage: phpunit [options] UnitTest [UnitTest.php]
       phpunit [options] <directory>

Code Coverage Options:

  --coverage-clover <file>  Generate code coverage report in Clover XML format.
  --coverage-crap4j <file>  Generate code coverage report in Crap4J XML format.
  --coverage-html <dir>     Generate code coverage report in HTML format.
  --coverage-php <file>     Export PHP_CodeCoverage object to file.
  --coverage-text=<file>    Generate code coverage report in text format.
                            Default: Standard output.
  --coverage-xml <dir>      Generate code coverage report in PHPUnit XML format.
  --whitelist <dir>         Whitelist <dir> for code coverage analysis.
  --disable-coverage-ignore Disable annotations for ignoring code coverage.

Logging Options:

  --log-junit <file>        Log test execution in JUnit XML format to file.
  --log-teamcity <file>     Log test execution in TeamCity format to file.
  --testdox-html <file>     Write agile documentation in HTML format to file.
  --testdox-text <file>     Write agile documentation in Text format to file.
  --testdox-xml <file>      Write agile documentation in XML format to file.
  --reverse-list            Print defects in reverse order

Test Selection Options:

  --filter <pattern>        Filter which tests to run.
  --testsuite <name>        Filter which testsuite to run.
  --group ...               Only runs tests from the specified group(s).
  --exclude-group ...       Exclude tests from the specified group(s).
  --list-groups             List available test groups.
  --list-suites             List available test suites.
  --test-suffix ...         Only search for test in files with specified
                            suffix(es). Default: Test.php,.phpt

Test Execution Options:

  --report-useless-tests    Be strict about tests that do not test anything.
  --strict-coverage         Be strict about @covers annotation usage.
  --strict-global-state     Be strict about changes to global state
  --disallow-test-output    Be strict about output during tests.
  --disallow-resource-usage Be strict about resource usage during small tests.
  --enforce-time-limit      Enforce time limit based on test size.
  --disallow-todo-tests     Disallow @todo-annotated tests.

  --process-isolation       Run each test in a separate PHP process.
  --no-globals-backup       Do not backup and restore \$GLOBALS for each test.
  --static-backup           Backup and restore static attributes for each test.

  --colors=<flag>           Use colors in output ("never", "auto" or "always").
  --columns <n>             Number of columns to use for progress output.
  --columns max             Use maximum number of columns for progress output.
  --stderr                  Write to STDERR instead of STDOUT.
  --stop-on-error           Stop execution upon first error.
  --stop-on-failure         Stop execution upon first error or failure.
  --stop-on-warning         Stop execution upon first warning.
  --stop-on-risky           Stop execution upon first risky test.
  --stop-on-skipped         Stop execution upon first skipped test.
  --stop-on-incomplete      Stop execution upon first incomplete test.
  --fail-on-warning         Treat tests with warnings as failures.
  --fail-on-risky           Treat risky tests as failures.
  -v|--verbose              Output more verbose information.
  --debug                   Display debugging information during test execution.

  --loader <loader>         TestSuiteLoader implementation to use.
  --repeat <times>          Runs the test(s) repeatedly.
  --teamcity                Report test execution progress in TeamCity format.
  --testdox                 Report test execution progress in TestDox format.
  --testdox-group           Only include tests from the specified group(s).
  --testdox-exclude-group   Exclude tests from the specified group(s).
  --printer <printer>       TestListener implementation to use.

Configuration Options:

  --bootstrap <file>        A "bootstrap" PHP file that is run before the tests.
  -c|--configuration <file> Read configuration from XML file.
  --no-configuration        Ignore default configuration file (phpunit.xml).
  --no-coverage             Ignore code coverage configuration.
  --no-extensions           Do not load PHPUnit extensions.
  --include-path <path(s)>  Prepend PHP's include_path with given path(s).
  -d key[=value]            Sets a php.ini value.
  --generate-configuration  Generate configuration file with suggested settings.

Miscellaneous Options:

  -h|--help                 Prints this usage information.
  --version                 Prints the version and exits.
  --atleast-version <min>   Checks that version is greater than min and exits.

EOT;

        if (defined('__PHPUNIT_PHAR__')) {
            print "\n  --check-version           Check whether PHPUnit is the latest version.";
        }
    }

    /**
     * Custom callback for test suite discovery.
     */
    protected function handleCustomTestSuite()
    {
    }

    private function printVersionString()
    {
        if ($this->versionStringPrinted) {
            return;
        }

        print PHPUnit_Runner_Version::getVersionString() . "\n\n";

        $this->versionStringPrinted = true;
    }

    /**
     * @param string $message
     */
    private function showError($message)
    {
        $this->printVersionString();

        print $message . "\n";

        exit(PHPUnit_TextUI_TestRunner::FAILURE_EXIT);
    }

    /**
     * @param string $directory
     */
    private function handleExtensions($directory)
    {
        $facade = new File_Iterator_Facade;

        foreach ($facade->getFilesAsArray($directory, '.phar') as $file) {
            require $file;

            $this->arguments['loadedExtensions'][] = $file;
        }
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Utility class for textual type (and value) representation.
 */
class PHPUnit_Util_Type
{
    /**
     * @param string $type
     *
     * @return bool
     */
    public static function isType($type)
    {
        return in_array(
            $type,
            [
                'numeric',
                'integer',
                'int',
                'float',
                'string',
                'boolean',
                'bool',
                'null',
                'array',
                'object',
                'resource',
                'scalar'
            ]
        );
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Error handler that converts PHP errors and warnings to exceptions.
 */
class PHPUnit_Util_Regex
{
    /**
     * @param string $pattern
     * @param string $subject
     * @param null   $matches
     * @param int    $flags
     * @param int    $offset
     *
     * @return int
     */
    public static function pregMatchSafe($pattern, $subject, $matches = null, $flags = 0, $offset = 0)
    {
        $handler_terminator = PHPUnit_Util_ErrorHandler::handleErrorOnce(E_WARNING);
        $match              = preg_match($pattern, $subject, $matches, $flags, $offset);
        $handler_terminator(); // cleaning

        return $match;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Utility methods to load PHP sourcefiles.
 */
class PHPUnit_Util_Fileloader
{
    /**
     * Checks if a PHP sourcefile is readable.
     * The sourcefile is loaded through the load() method.
     *
     * @param string $filename
     *
     * @return string
     *
     * @throws PHPUnit_Framework_Exception
     */
    public static function checkAndLoad($filename)
    {
        $includePathFilename = stream_resolve_include_path($filename);

        if (!$includePathFilename || !is_readable($includePathFilename)) {
            throw new PHPUnit_Framework_Exception(
                sprintf('Cannot open file "%s".' . "\n", $filename)
            );
        }

        self::load($includePathFilename);

        return $includePathFilename;
    }

    /**
     * Loads a PHP sourcefile.
     *
     * @param string $filename
     *
     * @return mixed
     */
    public static function load($filename)
    {
        $oldVariableNames = array_keys(get_defined_vars());

        include_once $filename;

        $newVariables     = get_defined_vars();
        $newVariableNames = array_diff(
            array_keys($newVariables),
            $oldVariableNames
        );

        foreach ($newVariableNames as $variableName) {
            if ($variableName != 'oldVariableNames') {
                $GLOBALS[$variableName] = $newVariables[$variableName];
            }
        }

        return $filename;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Iterator for test suites.
 */
class PHPUnit_Util_TestSuiteIterator implements RecursiveIterator
{
    /**
     * @var int
     */
    protected $position;

    /**
     * @var PHPUnit_Framework_Test[]
     */
    protected $tests;

    /**
     * @param PHPUnit_Framework_TestSuite $testSuite
     */
    public function __construct(PHPUnit_Framework_TestSuite $testSuite)
    {
        $this->tests = $testSuite->tests();
    }

    /**
     * Rewinds the Iterator to the first element.
     */
    public function rewind()
    {
        $this->position = 0;
    }

    /**
     * Checks if there is a current element after calls to rewind() or next().
     *
     * @return bool
     */
    public function valid()
    {
        return $this->position < count($this->tests);
    }

    /**
     * Returns the key of the current element.
     *
     * @return int
     */
    public function key()
    {
        return $this->position;
    }

    /**
     * Returns the current element.
     *
     * @return PHPUnit_Framework_Test
     */
    public function current()
    {
        return $this->valid() ? $this->tests[$this->position] : null;
    }

    /**
     * Moves forward to next element.
     */
    public function next()
    {
        $this->position++;
    }

    /**
     * Returns the sub iterator for the current element.
     *
     * @return PHPUnit_Util_TestSuiteIterator
     */
    public function getChildren()
    {
        return new self(
            $this->tests[$this->position]
        );
    }

    /**
     * Checks whether the current element has children.
     *
     * @return bool
     */
    public function hasChildren()
    {
        return $this->tests[$this->position] instanceof PHPUnit_Framework_TestSuite;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Filesystem helpers.
 */
class PHPUnit_Util_Filesystem
{
    /**
     * @var array
     */
    protected static $buffer = [];

    /**
     * Maps class names to source file names:
     *   - PEAR CS:   Foo_Bar_Baz -> Foo/Bar/Baz.php
     *   - Namespace: Foo\Bar\Baz -> Foo/Bar/Baz.php
     *
     * @param string $className
     *
     * @return string
     */
    public static function classNameToFilename($className)
    {
        return str_replace(
            ['_', '\\'],
            DIRECTORY_SEPARATOR,
            $className
        ) . '.php';
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

class PHPUnit_Util_ConfigurationGenerator
{
    /**
     * @var string
     */
    private $defaultTemplate = <<<EOT
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/{phpunit_version}/phpunit.xsd"
         bootstrap="{bootstrap_script}"
         backupGlobals="false"
         beStrictAboutCoversAnnotation="true"
         beStrictAboutOutputDuringTests="true"
         beStrictAboutTestsThatDoNotTestAnything="true"
         beStrictAboutTodoAnnotatedTests="true"
         verbose="true">
    <testsuite name="default">
        <directory suffix="Test.php">{tests_directory}</directory>
    </testsuite>

    <filter>
        <whitelist processUncoveredFilesFromWhitelist="true">
            <directory suffix=".php">{src_directory}</directory>
        </whitelist>
    </filter>
</phpunit>

EOT;

    /**
     * @param string $phpunitVersion
     * @param string $bootstrapScript
     * @param string $testsDirectory
     * @param string $srcDirectory
     *
     * @return string
     */
    public function generateDefaultConfiguration($phpunitVersion, $bootstrapScript, $testsDirectory, $srcDirectory)
    {
        return str_replace(
            [
                '{phpunit_version}',
                '{bootstrap_script}',
                '{tests_directory}',
                '{src_directory}'
            ],
            [
                $phpunitVersion,
                $bootstrapScript,
                $testsDirectory,
                $srcDirectory
            ],
            $this->defaultTemplate
        );
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Utility class for blacklisting PHPUnit's own source code files.
 */
class PHPUnit_Util_Blacklist
{
    /**
     * @var array
     */
    public static $blacklistedClassNames = [
        'File_Iterator'                               => 1,
        'PHP_Invoker'                                 => 1,
        'PHP_Timer'                                   => 1,
        'PHP_Token'                                   => 1,
        'PHPUnit_Framework_TestCase'                  => 2,
        'PHPUnit_Extensions_Database_TestCase'        => 2,
        'PHPUnit_Framework_MockObject_Generator'      => 2,
        'Text_Template'                               => 1,
        'Symfony\Component\Yaml\Yaml'                 => 1,
        'SebastianBergmann\CodeCoverage\CodeCoverage' => 1,
        'SebastianBergmann\Diff\Diff'                 => 1,
        'SebastianBergmann\Environment\Runtime'       => 1,
        'SebastianBergmann\Comparator\Comparator'     => 1,
        'SebastianBergmann\Exporter\Exporter'         => 1,
        'SebastianBergmann\GlobalState\Snapshot'      => 1,
        'SebastianBergmann\RecursionContext\Context'  => 1,
        'SebastianBergmann\Version'                   => 1,
        'Composer\Autoload\ClassLoader'               => 1,
        'Doctrine\Instantiator\Instantiator'          => 1,
        'phpDocumentor\Reflection\DocBlock'           => 1,
        'Prophecy\Prophet'                            => 1,
        'DeepCopy\DeepCopy'                           => 1
    ];

    /**
     * @var array
     */
    private static $directories;

    /**
     * @return array
     */
    public function getBlacklistedDirectories()
    {
        $this->initialize();

        return self::$directories;
    }

    /**
     * @param string $file
     *
     * @return bool
     */
    public function isBlacklisted($file)
    {
        if (defined('PHPUNIT_TESTSUITE')) {
            return false;
        }

        $this->initialize();

        foreach (self::$directories as $directory) {
            if (strpos($file, $directory) === 0) {
                return true;
            }
        }

        return false;
    }

    private function initialize()
    {
        if (self::$directories === null) {
            self::$directories = [];

            foreach (self::$blacklistedClassNames as $className => $parent) {
                if (!class_exists($className)) {
                    continue;
                }

                $reflector = new ReflectionClass($className);
                $directory = $reflector->getFileName();

                for ($i = 0; $i < $parent; $i++) {
                    $directory = dirname($directory);
                }

                self::$directories[] = $directory;
            }

            // Hide process isolation workaround on Windows.
            // @see PHPUnit_Util_PHP::factory()
            // @see PHPUnit_Util_PHP_Windows::process()
            if (DIRECTORY_SEPARATOR === '\\') {
                // tempnam() prefix is limited to first 3 chars.
                // @see http://php.net/manual/en/function.tempnam.php
                self::$directories[] = sys_get_temp_dir() . '\\PHP';
            }
        }
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Command-line options parsing class.
 */
class PHPUnit_Util_Getopt
{
    public static function getopt(array $args, $short_options, $long_options = null)
    {
        if (empty($args)) {
            return [[], []];
        }

        $opts     = [];
        $non_opts = [];

        if ($long_options) {
            sort($long_options);
        }

        if (isset($args[0][0]) && $args[0][0] != '-') {
            array_shift($args);
        }

        reset($args);

        $args = array_map('trim', $args);

        while (false !== $arg = current($args)) {
            $i = key($args);
            next($args);
            if ($arg == '') {
                continue;
            }

            if ($arg == '--') {
                $non_opts = array_merge($non_opts, array_slice($args, $i + 1));

                break;
            }

            if ($arg[0] != '-' || (strlen($arg) > 1 && $arg[1] == '-' && !$long_options)) {
                $non_opts[] = $args[$i];

                continue;
            } elseif (strlen($arg) > 1 && $arg[1] == '-') {
                self::parseLongOption(
                    substr($arg, 2),
                    $long_options,
                    $opts,
                    $args
                );
            } else {
                self::parseShortOption(
                    substr($arg, 1),
                    $short_options,
                    $opts,
                    $args
                );
            }
        }

        return [$opts, $non_opts];
    }

    protected static function parseShortOption($arg, $short_options, &$opts, &$args)
    {
        $argLen = strlen($arg);

        for ($i = 0; $i < $argLen; $i++) {
            $opt     = $arg[$i];
            $opt_arg = null;

            if (($spec = strstr($short_options, $opt)) === false || $arg[$i] == ':') {
                throw new PHPUnit_Framework_Exception(
                    "unrecognized option -- $opt"
                );
            }

            if (strlen($spec) > 1 && $spec[1] == ':') {
                if ($i + 1 < $argLen) {
                    $opts[] = [$opt, substr($arg, $i + 1)];

                    break;
                }
                if (!(strlen($spec) > 2 && $spec[2] == ':')) {
                    if (false === $opt_arg = current($args)) {
                        throw new PHPUnit_Framework_Exception(
                            "option requires an argument -- $opt"
                        );
                    }
                    next($args);
                }
            }

            $opts[] = [$opt, $opt_arg];
        }
    }

    protected static function parseLongOption($arg, $long_options, &$opts, &$args)
    {
        $count   = count($long_options);
        $list    = explode('=', $arg);
        $opt     = $list[0];
        $opt_arg = null;

        if (count($list) > 1) {
            $opt_arg = $list[1];
        }

        $opt_len = strlen($opt);

        for ($i = 0; $i < $count; $i++) {
            $long_opt  = $long_options[$i];
            $opt_start = substr($long_opt, 0, $opt_len);

            if ($opt_start != $opt) {
                continue;
            }

            $opt_rest = substr($long_opt, $opt_len);

            if ($opt_rest != '' && $opt[0] != '=' && $i + 1 < $count &&
                $opt == substr($long_options[$i + 1], 0, $opt_len)) {
                throw new PHPUnit_Framework_Exception(
                    "option --$opt is ambiguous"
                );
            }

            if (substr($long_opt, -1) == '=') {
                if (substr($long_opt, -2) != '==') {
                    if (!strlen($opt_arg)) {
                        if (false === $opt_arg = current($args)) {
                            throw new PHPUnit_Framework_Exception(
                                "option --$opt requires an argument"
                            );
                        }
                        next($args);
                    }
                }
            } elseif ($opt_arg) {
                throw new PHPUnit_Framework_Exception(
                    "option --$opt doesn't allow an argument"
                );
            }

            $full_option = '--' . preg_replace('/={1,2}$/', '', $long_opt);
            $opts[]      = [$full_option, $opt_arg];

            return;
        }

        throw new PHPUnit_Framework_Exception("unrecognized option --$opt");
    }
}

<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * XML helpers.
 */
class PHPUnit_Util_XML
{
    /**
     * Load an $actual document into a DOMDocument.  This is called
     * from the selector assertions.
     *
     * If $actual is already a DOMDocument, it is returned with
     * no changes.  Otherwise, $actual is loaded into a new DOMDocument
     * as either HTML or XML, depending on the value of $isHtml. If $isHtml is
     * false and $xinclude is true, xinclude is performed on the loaded
     * DOMDocument.
     *
     * Note: prior to PHPUnit 3.3.0, this method loaded a file and
     * not a string as it currently does.  To load a file into a
     * DOMDocument, use loadFile() instead.
     *
     * @param string|DOMDocument $actual
     * @param bool               $isHtml
     * @param string             $filename
     * @param bool               $xinclude
     * @param bool               $strict
     *
     * @return DOMDocument
     */
    public static function load($actual, $isHtml = false, $filename = '', $xinclude = false, $strict = false)
    {
        if ($actual instanceof DOMDocument) {
            return $actual;
        }

        if (!is_string($actual)) {
            throw new PHPUnit_Framework_Exception('Could not load XML from ' . gettype($actual));
        }

        if ($actual === '') {
            throw new PHPUnit_Framework_Exception('Could not load XML from empty string');
        }

        // Required for XInclude on Windows.
        if ($xinclude) {
            $cwd = getcwd();
            @chdir(dirname($filename));
        }

        $document                     = new DOMDocument;
        $document->preserveWhiteSpace = false;

        $internal  = libxml_use_internal_errors(true);
        $message   = '';
        $reporting = error_reporting(0);

        if ('' !== $filename) {
            // Necessary for xinclude
            $document->documentURI = $filename;
        }

        if ($isHtml) {
            $loaded = $document->loadHTML($actual);
        } else {
            $loaded = $document->loadXML($actual);
        }

        if (!$isHtml && $xinclude) {
            $document->xinclude();
        }

        foreach (libxml_get_errors() as $error) {
            $message .= "\n" . $error->message;
        }

        libxml_use_internal_errors($internal);
        error_reporting($reporting);

        if ($xinclude) {
            @chdir($cwd);
        }

        if ($loaded === false || ($strict && $message !== '')) {
            if ($filename !== '') {
                throw new PHPUnit_Framework_Exception(
                    sprintf(
                        'Could not load "%s".%s',
                        $filename,
                        $message != '' ? "\n" . $message : ''
                    )
                );
            } else {
                if ($message === '') {
                    $message = 'Could not load XML for unknown reason';
                }
                throw new PHPUnit_Framework_Exception($message);
            }
        }

        return $document;
    }

    /**
     * Loads an XML (or HTML) file into a DOMDocument object.
     *
     * @param string $filename
     * @param bool   $isHtml
     * @param bool   $xinclude
     * @param bool   $strict
     *
     * @return DOMDocument
     */
    public static function loadFile($filename, $isHtml = false, $xinclude = false, $strict = false)
    {
        $reporting = error_reporting(0);
        $contents  = file_get_contents($filename);
        error_reporting($reporting);

        if ($contents === false) {
            throw new PHPUnit_Framework_Exception(
                sprintf(
                    'Could not read "%s".',
                    $filename
                )
            );
        }

        return self::load($contents, $isHtml, $filename, $xinclude, $strict);
    }

    /**
     * @param DOMNode $node
     */
    public static function removeCharacterDataNodes(DOMNode $node)
    {
        if ($node->hasChildNodes()) {
            for ($i = $node->childNodes->length - 1; $i >= 0; $i--) {
                if (($child = $node->childNodes->item($i)) instanceof DOMCharacterData) {
                    $node->removeChild($child);
                }
            }
        }
    }

    /**
     * Escapes a string for the use in XML documents
     * Any Unicode character is allowed, excluding the surrogate blocks, FFFE,
     * and FFFF (not even as character reference).
     * See http://www.w3.org/TR/xml/#charsets
     *
     * @param string $string
     *
     * @return string
     */
    public static function prepareString($string)
    {
        return preg_replace(
            '/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]/',
            '',
            htmlspecialchars(
                PHPUnit_Util_String::convertToUtf8($string),
                ENT_QUOTES,
                'UTF-8'
            )
        );
    }

    /**
     * "Convert" a DOMElement object into a PHP variable.
     *
     * @param DOMElement $element
     *
     * @return mixed
     */
    public static function xmlToVariable(DOMElement $element)
    {
        $variable = null;

        switch ($element->tagName) {
            case 'array':
                $variable = [];

                foreach ($element->childNodes as $entry) {
                    if (!$entry instanceof DOMElement || $entry->tagName !== 'element') {
                        continue;
                    }
                    $item = $entry->childNodes->item(0);

                    if ($item instanceof DOMText) {
                        $item = $entry->childNodes->item(1);
                    }

                    $value = self::xmlToVariable($item);

                    if ($entry->hasAttribute('key')) {
                        $variable[(string) $entry->getAttribute('key')] = $value;
                    } else {
                        $variable[] = $value;
                    }
                }
                break;

            case 'object':
                $className = $element->getAttribute('class');

                if ($element->hasChildNodes()) {
                    $arguments       = $element->childNodes->item(1)->childNodes;
                    $constructorArgs = [];

                    foreach ($arguments as $argument) {
                        if ($argument instanceof DOMElement) {
                            $constructorArgs[] = self::xmlToVariable($argument);
                        }
                    }

                    $class    = new ReflectionClass($className);
                    $variable = $class->newInstanceArgs($constructorArgs);
                } else {
                    $variable = new $className;
                }
                break;

            case 'boolean':
                $variable = $element->textContent == 'true' ? true : false;
                break;

            case 'integer':
            case 'double':
            case 'string':
                $variable = $element->textContent;

                settype($variable, $element->tagName);
                break;
        }

        return $variable;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Factory for PHPUnit_Framework_Exception objects that are used to describe
 * invalid arguments passed to a function or method.
 */
class PHPUnit_Util_InvalidArgumentHelper
{
    /**
     * @param int    $argument
     * @param string $type
     * @param mixed  $value
     *
     * @return PHPUnit_Framework_Exception
     */
    public static function factory($argument, $type, $value = null)
    {
        $stack = debug_backtrace(false);

        return new PHPUnit_Framework_Exception(
            sprintf(
                'Argument #%d%sof %s::%s() must be a %s',
                $argument,
                $value !== null ? ' (' . gettype($value) . '#' . $value . ')' : ' (No Value) ',
                $stack[1]['class'],
                $stack[1]['function'],
                $type
            )
        );
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Wrapper for the PHPUnit XML configuration file.
 *
 * Example XML configuration file:
 * <code>
 * <?xml version="1.0" encoding="utf-8" ?>
 *
 * <phpunit backupGlobals="true"
 *          backupStaticAttributes="false"
 *          bootstrap="/path/to/bootstrap.php"
 *          cacheTokens="false"
 *          columns="80"
 *          colors="false"
 *          stderr="false"
 *          convertErrorsToExceptions="true"
 *          convertNoticesToExceptions="true"
 *          convertWarningsToExceptions="true"
 *          forceCoversAnnotation="false"
 *          processIsolation="false"
 *          stopOnError="false"
 *          stopOnFailure="false"
 *          stopOnWarning="false"
 *          stopOnIncomplete="false"
 *          stopOnRisky="false"
 *          stopOnSkipped="false"
 *          failOnWarning="false"
 *          failOnRisky="false"
 *          extensionsDirectory="tools/phpunit.d"
 *          printerClass="PHPUnit_TextUI_ResultPrinter"
 *          testSuiteLoaderClass="PHPUnit_Runner_StandardTestSuiteLoader"
 *          beStrictAboutChangesToGlobalState="false"
 *          beStrictAboutCoversAnnotation="false"
 *          beStrictAboutOutputDuringTests="false"
 *          beStrictAboutResourceUsageDuringSmallTests="false"
 *          beStrictAboutTestsThatDoNotTestAnything="false"
 *          beStrictAboutTodoAnnotatedTests="false"
 *          checkForUnintentionallyCoveredCode="false"
 *          enforceTimeLimit="false"
 *          timeoutForSmallTests="1"
 *          timeoutForMediumTests="10"
 *          timeoutForLargeTests="60"
 *          verbose="false"
 *          reverseDefectList="false"
 *          registerMockObjectsFromTestArgumentsRecursively="false">
 *   <testsuites>
 *     <testsuite name="My Test Suite">
 *       <directory suffix="Test.php" phpVersion="5.3.0" phpVersionOperator=">=">/path/to/files</directory>
 *       <file phpVersion="5.3.0" phpVersionOperator=">=">/path/to/MyTest.php</file>
 *       <exclude>/path/to/files/exclude</exclude>
 *     </testsuite>
 *   </testsuites>
 *
 *   <groups>
 *     <include>
 *       <group>name</group>
 *     </include>
 *     <exclude>
 *       <group>name</group>
 *     </exclude>
 *   </groups>
 *
 *   <testdoxGroups>
 *     <include>
 *       <group>name</group>
 *     </include>
 *     <exclude>
 *       <group>name</group>
 *     </exclude>
 *   </testdoxGroups>
 *
 *   <filter>
 *     <whitelist addUncoveredFilesFromWhitelist="true"
 *                processUncoveredFilesFromWhitelist="false">
 *       <directory suffix=".php">/path/to/files</directory>
 *       <file>/path/to/file</file>
 *       <exclude>
 *         <directory suffix=".php">/path/to/files</directory>
 *         <file>/path/to/file</file>
 *       </exclude>
 *     </whitelist>
 *   </filter>
 *
 *   <listeners>
 *     <listener class="MyListener" file="/optional/path/to/MyListener.php">
 *       <arguments>
 *         <array>
 *           <element key="0">
 *             <string>Sebastian</string>
 *           </element>
 *         </array>
 *         <integer>22</integer>
 *         <string>April</string>
 *         <double>19.78</double>
 *         <null/>
 *         <object class="stdClass"/>
 *         <file>MyRelativeFile.php</file>
 *         <directory>MyRelativeDir</directory>
 *       </arguments>
 *     </listener>
 *   </listeners>
 *
 *   <logging>
 *     <log type="coverage-html" target="/tmp/report" lowUpperBound="50" highLowerBound="90"/>
 *     <log type="coverage-clover" target="/tmp/clover.xml"/>
 *     <log type="coverage-crap4j" target="/tmp/crap.xml" threshold="30"/>
 *     <log type="json" target="/tmp/logfile.json"/>
 *     <log type="plain" target="/tmp/logfile.txt"/>
 *     <log type="tap" target="/tmp/logfile.tap"/>
 *     <log type="teamcity" target="/tmp/logfile.txt"/>
 *     <log type="junit" target="/tmp/logfile.xml" logIncompleteSkipped="false"/>
 *     <log type="testdox-html" target="/tmp/testdox.html"/>
 *     <log type="testdox-text" target="/tmp/testdox.txt"/>
 *     <log type="testdox-xml" target="/tmp/testdox.xml"/>
 *   </logging>
 *
 *   <php>
 *     <includePath>.</includePath>
 *     <ini name="foo" value="bar"/>
 *     <const name="foo" value="bar"/>
 *     <var name="foo" value="bar"/>
 *     <env name="foo" value="bar"/>
 *     <post name="foo" value="bar"/>
 *     <get name="foo" value="bar"/>
 *     <cookie name="foo" value="bar"/>
 *     <server name="foo" value="bar"/>
 *     <files name="foo" value="bar"/>
 *     <request name="foo" value="bar"/>
 *   </php>
 * </phpunit>
 * </code>
 */
class PHPUnit_Util_Configuration
{
    private static $instances = [];

    protected $document;
    protected $xpath;
    protected $filename;

    /**
     * Loads a PHPUnit configuration file.
     *
     * @param string $filename
     */
    protected function __construct($filename)
    {
        $this->filename = $filename;
        $this->document = PHPUnit_Util_XML::loadFile($filename, false, true, true);
        $this->xpath    = new DOMXPath($this->document);
    }

    final private function __clone()
    {
    }

    /**
     * Returns a PHPUnit configuration object.
     *
     * @param string $filename
     *
     * @return PHPUnit_Util_Configuration
     */
    public static function getInstance($filename)
    {
        $realpath = realpath($filename);

        if ($realpath === false) {
            throw new PHPUnit_Framework_Exception(
                sprintf(
                    'Could not read "%s".',
                    $filename
                )
            );
        }

        if (!isset(self::$instances[$realpath])) {
            self::$instances[$realpath] = new self($realpath);
        }

        return self::$instances[$realpath];
    }

    /**
     * Returns the realpath to the configuration file.
     *
     * @return string
     */
    public function getFilename()
    {
        return $this->filename;
    }

    /**
     * Returns the configuration for SUT filtering.
     *
     * @return array
     */
    public function getFilterConfiguration()
    {
        $addUncoveredFilesFromWhitelist     = true;
        $processUncoveredFilesFromWhitelist = false;

        $tmp = $this->xpath->query('filter/whitelist');

        if ($tmp->length == 1) {
            if ($tmp->item(0)->hasAttribute('addUncoveredFilesFromWhitelist')) {
                $addUncoveredFilesFromWhitelist = $this->getBoolean(
                    (string) $tmp->item(0)->getAttribute(
                        'addUncoveredFilesFromWhitelist'
                    ),
                    true
                );
            }

            if ($tmp->item(0)->hasAttribute('processUncoveredFilesFromWhitelist')) {
                $processUncoveredFilesFromWhitelist = $this->getBoolean(
                    (string) $tmp->item(0)->getAttribute(
                        'processUncoveredFilesFromWhitelist'
                    ),
                    false
                );
            }
        }

        return [
          'whitelist' => [
            'addUncoveredFilesFromWhitelist'     => $addUncoveredFilesFromWhitelist,
            'processUncoveredFilesFromWhitelist' => $processUncoveredFilesFromWhitelist,
            'include'                            => [
              'directory' => $this->readFilterDirectories(
                  'filter/whitelist/directory'
              ),
              'file' => $this->readFilterFiles(
                  'filter/whitelist/file'
              )
            ],
            'exclude' => [
              'directory' => $this->readFilterDirectories(
                  'filter/whitelist/exclude/directory'
              ),
              'file' => $this->readFilterFiles(
                  'filter/whitelist/exclude/file'
              )
            ]
          ]
        ];
    }

    /**
     * Returns the configuration for groups.
     *
     * @return array
     */
    public function getGroupConfiguration()
    {
        return $this->parseGroupConfiguration('groups');
    }

    /**
     * Returns the configuration for testdox groups.
     *
     * @return array
     */
    public function getTestdoxGroupConfiguration()
    {
        return $this->parseGroupConfiguration('testdoxGroups');
    }

    /**
     * @param string $root
     *
     * @return array
     */
    private function parseGroupConfiguration($root)
    {
        $groups = [
            'include' => [],
            'exclude' => []
        ];

        foreach ($this->xpath->query($root . '/include/group') as $group) {
            $groups['include'][] = (string) $group->textContent;
        }

        foreach ($this->xpath->query($root . '/exclude/group') as $group) {
            $groups['exclude'][] = (string) $group->textContent;
        }

        return $groups;
    }

    /**
     * Returns the configuration for listeners.
     *
     * @return array
     */
    public function getListenerConfiguration()
    {
        $result = [];

        foreach ($this->xpath->query('listeners/listener') as $listener) {
            $class     = (string) $listener->getAttribute('class');
            $file      = '';
            $arguments = [];

            if ($listener->getAttribute('file')) {
                $file = $this->toAbsolutePath(
                    (string) $listener->getAttribute('file'),
                    true
                );
            }

            foreach ($listener->childNodes as $node) {
                if ($node instanceof DOMElement && $node->tagName == 'arguments') {
                    foreach ($node->childNodes as $argument) {
                        if ($argument instanceof DOMElement) {
                            if ($argument->tagName == 'file' ||
                            $argument->tagName == 'directory') {
                                $arguments[] = $this->toAbsolutePath((string) $argument->textContent);
                            } else {
                                $arguments[] = PHPUnit_Util_XML::xmlToVariable($argument);
                            }
                        }
                    }
                }
            }

            $result[] = [
              'class'     => $class,
              'file'      => $file,
              'arguments' => $arguments
            ];
        }

        return $result;
    }

    /**
     * Returns the logging configuration.
     *
     * @return array
     */
    public function getLoggingConfiguration()
    {
        $result = [];

        foreach ($this->xpath->query('logging/log') as $log) {
            $type   = (string) $log->getAttribute('type');
            $target = (string) $log->getAttribute('target');

            if (!$target) {
                continue;
            }

            $target = $this->toAbsolutePath($target);

            if ($type == 'coverage-html') {
                if ($log->hasAttribute('lowUpperBound')) {
                    $result['lowUpperBound'] = $this->getInteger(
                        (string) $log->getAttribute('lowUpperBound'),
                        50
                    );
                }

                if ($log->hasAttribute('highLowerBound')) {
                    $result['highLowerBound'] = $this->getInteger(
                        (string) $log->getAttribute('highLowerBound'),
                        90
                    );
                }
            } elseif ($type == 'coverage-crap4j') {
                if ($log->hasAttribute('threshold')) {
                    $result['crap4jThreshold'] = $this->getInteger(
                        (string) $log->getAttribute('threshold'),
                        30
                    );
                }
            } elseif ($type == 'junit') {
                if ($log->hasAttribute('logIncompleteSkipped')) {
                    $result['logIncompleteSkipped'] = $this->getBoolean(
                        (string) $log->getAttribute('logIncompleteSkipped'),
                        false
                    );
                }
            } elseif ($type == 'coverage-text') {
                if ($log->hasAttribute('showUncoveredFiles')) {
                    $result['coverageTextShowUncoveredFiles'] = $this->getBoolean(
                        (string) $log->getAttribute('showUncoveredFiles'),
                        false
                    );
                }
                if ($log->hasAttribute('showOnlySummary')) {
                    $result['coverageTextShowOnlySummary'] = $this->getBoolean(
                        (string) $log->getAttribute('showOnlySummary'),
                        false
                    );
                }
            }

            $result[$type] = $target;
        }

        return $result;
    }

    /**
     * Returns the PHP configuration.
     *
     * @return array
     */
    public function getPHPConfiguration()
    {
        $result = [
          'include_path' => [],
          'ini'          => [],
          'const'        => [],
          'var'          => [],
          'env'          => [],
          'post'         => [],
          'get'          => [],
          'cookie'       => [],
          'server'       => [],
          'files'        => [],
          'request'      => []
        ];

        foreach ($this->xpath->query('php/includePath') as $includePath) {
            $path = (string) $includePath->textContent;
            if ($path) {
                $result['include_path'][] = $this->toAbsolutePath($path);
            }
        }

        foreach ($this->xpath->query('php/ini') as $ini) {
            $name  = (string) $ini->getAttribute('name');
            $value = (string) $ini->getAttribute('value');

            $result['ini'][$name] = $value;
        }

        foreach ($this->xpath->query('php/const') as $const) {
            $name  = (string) $const->getAttribute('name');
            $value = (string) $const->getAttribute('value');

            $result['const'][$name] = $this->getBoolean($value, $value);
        }

        foreach (['var', 'env', 'post', 'get', 'cookie', 'server', 'files', 'request'] as $array) {
            foreach ($this->xpath->query('php/' . $array) as $var) {
                $name  = (string) $var->getAttribute('name');
                $value = (string) $var->getAttribute('value');

                $result[$array][$name] = $this->getBoolean($value, $value);
            }
        }

        return $result;
    }

    /**
     * Handles the PHP configuration.
     */
    public function handlePHPConfiguration()
    {
        $configuration = $this->getPHPConfiguration();

        if (! empty($configuration['include_path'])) {
            ini_set(
                'include_path',
                implode(PATH_SEPARATOR, $configuration['include_path']) .
                PATH_SEPARATOR .
                ini_get('include_path')
            );
        }

        foreach ($configuration['ini'] as $name => $value) {
            if (defined($value)) {
                $value = constant($value);
            }

            ini_set($name, $value);
        }

        foreach ($configuration['const'] as $name => $value) {
            if (!defined($name)) {
                define($name, $value);
            }
        }

        foreach (['var', 'post', 'get', 'cookie', 'server', 'files', 'request'] as $array) {
            // See https://github.com/sebastianbergmann/phpunit/issues/277
            switch ($array) {
                case 'var':
                    $target = &$GLOBALS;
                    break;

                case 'server':
                    $target = &$_SERVER;
                    break;

                default:
                    $target = &$GLOBALS['_' . strtoupper($array)];
                    break;
            }

            foreach ($configuration[$array] as $name => $value) {
                $target[$name] = $value;
            }
        }

        foreach ($configuration['env'] as $name => $value) {
            if (false === getenv($name)) {
                putenv("{$name}={$value}");
            }
            if (!isset($_ENV[$name])) {
                $_ENV[$name] = $value;
            }
        }
    }

    /**
     * Returns the PHPUnit configuration.
     *
     * @return array
     */
    public function getPHPUnitConfiguration()
    {
        $result = [];
        $root   = $this->document->documentElement;

        if ($root->hasAttribute('cacheTokens')) {
            $result['cacheTokens'] = $this->getBoolean(
                (string) $root->getAttribute('cacheTokens'),
                false
            );
        }

        if ($root->hasAttribute('columns')) {
            $columns = (string) $root->getAttribute('columns');

            if ($columns == 'max') {
                $result['columns'] = 'max';
            } else {
                $result['columns'] = $this->getInteger($columns, 80);
            }
        }

        if ($root->hasAttribute('colors')) {
            /* only allow boolean for compatibility with previous versions
              'always' only allowed from command line */
            if ($this->getBoolean($root->getAttribute('colors'), false)) {
                $result['colors'] = PHPUnit_TextUI_ResultPrinter::COLOR_AUTO;
            } else {
                $result['colors'] = PHPUnit_TextUI_ResultPrinter::COLOR_NEVER;
            }
        }

        /*
         * Issue #657
         */
        if ($root->hasAttribute('stderr')) {
            $result['stderr'] = $this->getBoolean(
                (string) $root->getAttribute('stderr'),
                false
            );
        }

        if ($root->hasAttribute('backupGlobals')) {
            $result['backupGlobals'] = $this->getBoolean(
                (string) $root->getAttribute('backupGlobals'),
                true
            );
        }

        if ($root->hasAttribute('backupStaticAttributes')) {
            $result['backupStaticAttributes'] = $this->getBoolean(
                (string) $root->getAttribute('backupStaticAttributes'),
                false
            );
        }

        if ($root->getAttribute('bootstrap')) {
            $result['bootstrap'] = $this->toAbsolutePath(
                (string) $root->getAttribute('bootstrap')
            );
        }

        if ($root->hasAttribute('convertErrorsToExceptions')) {
            $result['convertErrorsToExceptions'] = $this->getBoolean(
                (string) $root->getAttribute('convertErrorsToExceptions'),
                true
            );
        }

        if ($root->hasAttribute('convertNoticesToExceptions')) {
            $result['convertNoticesToExceptions'] = $this->getBoolean(
                (string) $root->getAttribute('convertNoticesToExceptions'),
                true
            );
        }

        if ($root->hasAttribute('convertWarningsToExceptions')) {
            $result['convertWarningsToExceptions'] = $this->getBoolean(
                (string) $root->getAttribute('convertWarningsToExceptions'),
                true
            );
        }

        if ($root->hasAttribute('forceCoversAnnotation')) {
            $result['forceCoversAnnotation'] = $this->getBoolean(
                (string) $root->getAttribute('forceCoversAnnotation'),
                false
            );
        }

        if ($root->hasAttribute('disableCodeCoverageIgnore')) {
            $result['disableCodeCoverageIgnore'] = $this->getBoolean(
                (string) $root->getAttribute('disableCodeCoverageIgnore'),
                false
            );
        }

        if ($root->hasAttribute('processIsolation')) {
            $result['processIsolation'] = $this->getBoolean(
                (string) $root->getAttribute('processIsolation'),
                false
            );
        }

        if ($root->hasAttribute('stopOnError')) {
            $result['stopOnError'] = $this->getBoolean(
                (string) $root->getAttribute('stopOnError'),
                false
            );
        }

        if ($root->hasAttribute('stopOnFailure')) {
            $result['stopOnFailure'] = $this->getBoolean(
                (string) $root->getAttribute('stopOnFailure'),
                false
            );
        }

        if ($root->hasAttribute('stopOnWarning')) {
            $result['stopOnWarning'] = $this->getBoolean(
                (string) $root->getAttribute('stopOnWarning'),
                false
            );
        }

        if ($root->hasAttribute('stopOnIncomplete')) {
            $result['stopOnIncomplete'] = $this->getBoolean(
                (string) $root->getAttribute('stopOnIncomplete'),
                false
            );
        }

        if ($root->hasAttribute('stopOnRisky')) {
            $result['stopOnRisky'] = $this->getBoolean(
                (string) $root->getAttribute('stopOnRisky'),
                false
            );
        }

        if ($root->hasAttribute('stopOnSkipped')) {
            $result['stopOnSkipped'] = $this->getBoolean(
                (string) $root->getAttribute('stopOnSkipped'),
                false
            );
        }

        if ($root->hasAttribute('failOnWarning')) {
            $result['failOnWarning'] = $this->getBoolean(
                (string) $root->getAttribute('failOnWarning'),
                false
            );
        }

        if ($root->hasAttribute('failOnRisky')) {
            $result['failOnRisky'] = $this->getBoolean(
                (string) $root->getAttribute('failOnRisky'),
                false
            );
        }

        if ($root->hasAttribute('testSuiteLoaderClass')) {
            $result['testSuiteLoaderClass'] = (string) $root->getAttribute(
                'testSuiteLoaderClass'
            );
        }

        if ($root->getAttribute('testSuiteLoaderFile')) {
            $result['testSuiteLoaderFile'] = $this->toAbsolutePath(
                (string) $root->getAttribute('testSuiteLoaderFile')
            );
        }

        if ($root->hasAttribute('printerClass')) {
            $result['printerClass'] = (string) $root->getAttribute(
                'printerClass'
            );
        }

        if ($root->getAttribute('printerFile')) {
            $result['printerFile'] = $this->toAbsolutePath(
                (string) $root->getAttribute('printerFile')
            );
        }

        if ($root->hasAttribute('beStrictAboutChangesToGlobalState')) {
            $result['beStrictAboutChangesToGlobalState'] = $this->getBoolean(
                (string) $root->getAttribute('beStrictAboutChangesToGlobalState'),
                false
            );
        }

        if ($root->hasAttribute('beStrictAboutOutputDuringTests')) {
            $result['disallowTestOutput'] = $this->getBoolean(
                (string) $root->getAttribute('beStrictAboutOutputDuringTests'),
                false
            );
        }

        if ($root->hasAttribute('beStrictAboutResourceUsageDuringSmallTests')) {
            $result['beStrictAboutResourceUsageDuringSmallTests'] = $this->getBoolean(
                (string) $root->getAttribute('beStrictAboutResourceUsageDuringSmallTests'),
                false
            );
        }

        if ($root->hasAttribute('beStrictAboutTestsThatDoNotTestAnything')) {
            $result['reportUselessTests'] = $this->getBoolean(
                (string) $root->getAttribute('beStrictAboutTestsThatDoNotTestAnything'),
                false
            );
        }

        if ($root->hasAttribute('beStrictAboutTodoAnnotatedTests')) {
            $result['disallowTodoAnnotatedTests'] = $this->getBoolean(
                (string) $root->getAttribute('beStrictAboutTodoAnnotatedTests'),
                false
            );
        }

        if ($root->hasAttribute('beStrictAboutCoversAnnotation')) {
            $result['strictCoverage'] = $this->getBoolean(
                (string) $root->getAttribute('beStrictAboutCoversAnnotation'),
                false
            );
        } elseif ($root->hasAttribute('checkForUnintentionallyCoveredCode')) {
            $result['strictCoverage'] = $this->getBoolean(
                (string) $root->getAttribute('checkForUnintentionallyCoveredCode'),
                false
            );

            $result['deprecatedCheckForUnintentionallyCoveredCodeSettingUsed'] = true;
        }

        if ($root->hasAttribute('enforceTimeLimit')) {
            $result['enforceTimeLimit'] = $this->getBoolean(
                (string) $root->getAttribute('enforceTimeLimit'),
                false
            );
        }

        if ($root->hasAttribute('timeoutForSmallTests')) {
            $result['timeoutForSmallTests'] = $this->getInteger(
                (string) $root->getAttribute('timeoutForSmallTests'),
                1
            );
        }

        if ($root->hasAttribute('timeoutForMediumTests')) {
            $result['timeoutForMediumTests'] = $this->getInteger(
                (string) $root->getAttribute('timeoutForMediumTests'),
                10
            );
        }

        if ($root->hasAttribute('timeoutForLargeTests')) {
            $result['timeoutForLargeTests'] = $this->getInteger(
                (string) $root->getAttribute('timeoutForLargeTests'),
                60
            );
        }

        if ($root->hasAttribute('reverseDefectList')) {
            $result['reverseDefectList'] = $this->getBoolean(
                (string) $root->getAttribute('reverseDefectList'),
                false
            );
        }

        if ($root->hasAttribute('verbose')) {
            $result['verbose'] = $this->getBoolean(
                (string) $root->getAttribute('verbose'),
                false
            );
        }

        if ($root->hasAttribute('registerMockObjectsFromTestArgumentsRecursively')) {
            $result['registerMockObjectsFromTestArgumentsRecursively'] = $this->getBoolean(
                (string) $root->getAttribute('registerMockObjectsFromTestArgumentsRecursively'),
                false
            );
        }

        if ($root->hasAttribute('extensionsDirectory')) {
            $result['extensionsDirectory'] = $this->toAbsolutePath(
                    (string) $root->getAttribute(
                        'extensionsDirectory'
                    )
            );
        }

        return $result;
    }

    /**
     * Returns the test suite configuration.
     *
     * @return PHPUnit_Framework_TestSuite
     */
    public function getTestSuiteConfiguration($testSuiteFilter = null)
    {
        $testSuiteNodes = $this->xpath->query('testsuites/testsuite');

        if ($testSuiteNodes->length == 0) {
            $testSuiteNodes = $this->xpath->query('testsuite');
        }

        if ($testSuiteNodes->length == 1) {
            return $this->getTestSuite($testSuiteNodes->item(0), $testSuiteFilter);
        }

        if ($testSuiteNodes->length > 1) {
            $suite = new PHPUnit_Framework_TestSuite;

            foreach ($testSuiteNodes as $testSuiteNode) {
                $suite->addTestSuite(
                    $this->getTestSuite($testSuiteNode, $testSuiteFilter)
                );
            }

            return $suite;
        }
    }

    /**
     * Returns the test suite names from the configuration.
     *
     * @return array
     */
    public function getTestSuiteNames()
    {
        $names = [];
        $nodes = $this->xpath->query('*/testsuite');
        foreach ($nodes as $node) {
            $names[] = $node->getAttribute('name');
        }

        return $names;
    }

    /**
     * @param DOMElement $testSuiteNode
     *
     * @return PHPUnit_Framework_TestSuite
     */
    protected function getTestSuite(DOMElement $testSuiteNode, $testSuiteFilter = null)
    {
        if ($testSuiteNode->hasAttribute('name')) {
            $suite = new PHPUnit_Framework_TestSuite(
                (string) $testSuiteNode->getAttribute('name')
            );
        } else {
            $suite = new PHPUnit_Framework_TestSuite;
        }

        $exclude = [];

        foreach ($testSuiteNode->getElementsByTagName('exclude') as $excludeNode) {
            $excludeFile = (string) $excludeNode->textContent;
            if ($excludeFile) {
                $exclude[] = $this->toAbsolutePath($excludeFile);
            }
        }

        $fileIteratorFacade = new File_Iterator_Facade;

        foreach ($testSuiteNode->getElementsByTagName('directory') as $directoryNode) {
            if ($testSuiteFilter && $directoryNode->parentNode->getAttribute('name') != $testSuiteFilter) {
                continue;
            }

            $directory = (string) $directoryNode->textContent;

            if (empty($directory)) {
                continue;
            }

            if ($directoryNode->hasAttribute('phpVersion')) {
                $phpVersion = (string) $directoryNode->getAttribute('phpVersion');
            } else {
                $phpVersion = PHP_VERSION;
            }

            if ($directoryNode->hasAttribute('phpVersionOperator')) {
                $phpVersionOperator = (string) $directoryNode->getAttribute('phpVersionOperator');
            } else {
                $phpVersionOperator = '>=';
            }

            if (!version_compare(PHP_VERSION, $phpVersion, $phpVersionOperator)) {
                continue;
            }

            if ($directoryNode->hasAttribute('prefix')) {
                $prefix = (string) $directoryNode->getAttribute('prefix');
            } else {
                $prefix = '';
            }

            if ($directoryNode->hasAttribute('suffix')) {
                $suffix = (string) $directoryNode->getAttribute('suffix');
            } else {
                $suffix = 'Test.php';
            }

            $files = $fileIteratorFacade->getFilesAsArray(
                $this->toAbsolutePath($directory),
                $suffix,
                $prefix,
                $exclude
            );
            $suite->addTestFiles($files);
        }

        foreach ($testSuiteNode->getElementsByTagName('file') as $fileNode) {
            if ($testSuiteFilter && $fileNode->parentNode->getAttribute('name') != $testSuiteFilter) {
                continue;
            }

            $file = (string) $fileNode->textContent;

            if (empty($file)) {
                continue;
            }

            // Get the absolute path to the file
            $file = $fileIteratorFacade->getFilesAsArray(
                $this->toAbsolutePath($file)
            );

            if (!isset($file[0])) {
                continue;
            }

            $file = $file[0];

            if ($fileNode->hasAttribute('phpVersion')) {
                $phpVersion = (string) $fileNode->getAttribute('phpVersion');
            } else {
                $phpVersion = PHP_VERSION;
            }

            if ($fileNode->hasAttribute('phpVersionOperator')) {
                $phpVersionOperator = (string) $fileNode->getAttribute('phpVersionOperator');
            } else {
                $phpVersionOperator = '>=';
            }

            if (!version_compare(PHP_VERSION, $phpVersion, $phpVersionOperator)) {
                continue;
            }

            $suite->addTestFile($file);
        }

        return $suite;
    }

    /**
     * @param string $value
     * @param bool   $default
     *
     * @return bool
     */
    protected function getBoolean($value, $default)
    {
        if (strtolower($value) == 'false') {
            return false;
        } elseif (strtolower($value) == 'true') {
            return true;
        }

        return $default;
    }

    /**
     * @param string $value
     * @param bool   $default
     *
     * @return bool
     */
    protected function getInteger($value, $default)
    {
        if (is_numeric($value)) {
            return (int) $value;
        }

        return $default;
    }

    /**
     * @param string $query
     *
     * @return array
     */
    protected function readFilterDirectories($query)
    {
        $directories = [];

        foreach ($this->xpath->query($query) as $directory) {
            $directoryPath = (string) $directory->textContent;

            if (!$directoryPath) {
                continue;
            }

            if ($directory->hasAttribute('prefix')) {
                $prefix = (string) $directory->getAttribute('prefix');
            } else {
                $prefix = '';
            }

            if ($directory->hasAttribute('suffix')) {
                $suffix = (string) $directory->getAttribute('suffix');
            } else {
                $suffix = '.php';
            }

            if ($directory->hasAttribute('group')) {
                $group = (string) $directory->getAttribute('group');
            } else {
                $group = 'DEFAULT';
            }

            $directories[] = [
              'path'   => $this->toAbsolutePath($directoryPath),
              'prefix' => $prefix,
              'suffix' => $suffix,
              'group'  => $group
            ];
        }

        return $directories;
    }

    /**
     * @param string $query
     *
     * @return array
     */
    protected function readFilterFiles($query)
    {
        $files = [];

        foreach ($this->xpath->query($query) as $file) {
            $filePath = (string) $file->textContent;

            if ($filePath) {
                $files[] = $this->toAbsolutePath($filePath);
            }
        }

        return $files;
    }

    /**
     * @param string $path
     * @param bool   $useIncludePath
     *
     * @return string
     */
    protected function toAbsolutePath($path, $useIncludePath = false)
    {
        $path = trim($path);

        if ($path[0] === '/') {
            return $path;
        }

        // Matches the following on Windows:
        //  - \\NetworkComputer\Path
        //  - \\.\D:
        //  - \\.\c:
        //  - C:\Windows
        //  - C:\windows
        //  - C:/windows
        //  - c:/windows
        if (defined('PHP_WINDOWS_VERSION_BUILD') &&
            ($path[0] === '\\' ||
            (strlen($path) >= 3 && preg_match('#^[A-Z]\:[/\\\]#i', substr($path, 0, 3))))) {
            return $path;
        }

        // Stream
        if (strpos($path, '://') !== false) {
            return $path;
        }

        $file = dirname($this->filename) . DIRECTORY_SEPARATOR . $path;

        if ($useIncludePath && !file_exists($file)) {
            $includePathFile = stream_resolve_include_path($path);

            if ($includePathFile) {
                $file = $includePathFile;
            }
        }

        return $file;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
eval('?>' . file_get_contents('php://stdin'));
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Windows utility for PHP sub-processes.
 *
 * Reading from STDOUT or STDERR hangs forever on Windows if the output is
 * too large.
 *
 * @see https://bugs.php.net/bug.php?id=51800
 */
class PHPUnit_Util_PHP_Windows extends PHPUnit_Util_PHP_Default
{
    protected $useTempFile = true;

    protected function getHandles()
    {
        if (false === $stdout_handle = tmpfile()) {
            throw new PHPUnit_Framework_Exception(
                'A temporary file could not be created; verify that your TEMP environment variable is writable'
            );
        }

        return [
            1 => $stdout_handle
        ];
    }

    public function getCommand(array $settings, $file = null)
    {
        return '"' . parent::getCommand($settings, $file) . '"';
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Default utility for PHP sub-processes.
 */
class PHPUnit_Util_PHP_Default extends PHPUnit_Util_PHP
{
    /**
     * @var string
     */
    protected $tempFile;

    /**
     * @var bool
     */
    protected $useTempFile = false;

    /**
     * Runs a single job (PHP code) using a separate PHP process.
     *
     * @param string $job
     * @param array  $settings
     *
     * @return array
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function runJob($job, array $settings = [])
    {
        if ($this->useTempFile || $this->stdin) {
            if (!($this->tempFile = tempnam(sys_get_temp_dir(), 'PHPUnit')) ||
                file_put_contents($this->tempFile, $job) === false) {
                throw new PHPUnit_Framework_Exception(
                    'Unable to write temporary file'
                );
            }

            $job = $this->stdin;
        }

        return $this->runProcess($job, $settings);
    }

    /**
     * Returns an array of file handles to be used in place of pipes
     *
     * @return array
     */
    protected function getHandles()
    {
        return [];
    }

    /**
     * Handles creating the child process and returning the STDOUT and STDERR
     *
     * @param string $job
     * @param array  $settings
     *
     * @return array
     *
     * @throws PHPUnit_Framework_Exception
     */
    protected function runProcess($job, $settings)
    {
        $handles = $this->getHandles();

        $env = null;
        if ($this->env) {
            $env = isset($_SERVER) ? $_SERVER : [];
            unset($env['argv'], $env['argc']);
            $env = array_merge($env, $this->env);

            foreach ($env as $envKey => $envVar) {
                if (is_array($envVar)) {
                    unset($env[$envKey]);
                }
            }
        }

        $pipeSpec = [
            0 => isset($handles[0]) ? $handles[0] : ['pipe', 'r'],
            1 => isset($handles[1]) ? $handles[1] : ['pipe', 'w'],
            2 => isset($handles[2]) ? $handles[2] : ['pipe', 'w'],
        ];
        $process = proc_open(
            $this->getCommand($settings, $this->tempFile),
            $pipeSpec,
            $pipes,
            null,
            $env
        );

        if (!is_resource($process)) {
            throw new PHPUnit_Framework_Exception(
                'Unable to spawn worker process'
            );
        }

        if ($job) {
            $this->process($pipes[0], $job);
        }
        fclose($pipes[0]);

        if ($this->timeout) {
            $stderr = $stdout = '';
            unset($pipes[0]);

            while (true) {
                $r = $pipes;
                $w = null;
                $e = null;

                $n = @stream_select($r, $w, $e, $this->timeout);

                if ($n === false) {
                    break;
                } elseif ($n === 0) {
                    proc_terminate($process, 9);
                    throw new PHPUnit_Framework_Exception(sprintf('Job execution aborted after %d seconds', $this->timeout));
                } elseif ($n > 0) {
                    foreach ($r as $pipe) {
                        $pipeOffset = 0;
                        foreach ($pipes as $i => $origPipe) {
                            if ($pipe == $origPipe) {
                                $pipeOffset = $i;
                                break;
                            }
                        }

                        if (!$pipeOffset) {
                            break;
                        }

                        $line = fread($pipe, 8192);
                        if (strlen($line) == 0) {
                            fclose($pipes[$pipeOffset]);
                            unset($pipes[$pipeOffset]);
                        } else {
                            if ($pipeOffset == 1) {
                                $stdout .= $line;
                            } else {
                                $stderr .= $line;
                            }
                        }
                    }

                    if (empty($pipes)) {
                        break;
                    }
                }
            }
        } else {
            if (isset($pipes[1])) {
                $stdout = stream_get_contents($pipes[1]);
                fclose($pipes[1]);
            }

            if (isset($pipes[2])) {
                $stderr = stream_get_contents($pipes[2]);
                fclose($pipes[2]);
            }
        }

        if (isset($handles[1])) {
            rewind($handles[1]);
            $stdout = stream_get_contents($handles[1]);
            fclose($handles[1]);
        }

        if (isset($handles[2])) {
            rewind($handles[2]);
            $stderr = stream_get_contents($handles[2]);
            fclose($handles[2]);
        }

        proc_close($process);
        $this->cleanup();

        return ['stdout' => $stdout, 'stderr' => $stderr];
    }

    /**
     * @param resource $pipe
     * @param string   $job
     *
     * @throws PHPUnit_Framework_Exception
     */
    protected function process($pipe, $job)
    {
        fwrite($pipe, $job);
    }

    protected function cleanup()
    {
        if ($this->tempFile) {
            unlink($this->tempFile);
        }
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Test helpers.
 */
class PHPUnit_Util_Test
{
    const REGEX_DATA_PROVIDER      = '/@dataProvider\s+([a-zA-Z0-9._:-\\\\x7f-\xff]+)/';
    const REGEX_TEST_WITH          = '/@testWith\s+/';
    const REGEX_EXPECTED_EXCEPTION = '(@expectedException\s+([:.\w\\\\x7f-\xff]+)(?:[\t ]+(\S*))?(?:[\t ]+(\S*))?\s*$)m';
    const REGEX_REQUIRES_VERSION   = '/@requires\s+(?P<name>PHP(?:Unit)?)\s+(?P<operator>[<>=!]{0,2})\s*(?P<version>[\d\.-]+(dev|(RC|alpha|beta)[\d\.])?)[ \t]*\r?$/m';
    const REGEX_REQUIRES_OS        = '/@requires\s+OS\s+(?P<value>.+?)[ \t]*\r?$/m';
    const REGEX_REQUIRES           = '/@requires\s+(?P<name>function|extension)\s+(?P<value>([^ ]+?))\s*(?P<operator>[<>=!]{0,2})\s*(?P<version>[\d\.-]+[\d\.]?)?[ \t]*\r?$/m';

    const UNKNOWN = -1;
    const SMALL   = 0;
    const MEDIUM  = 1;
    const LARGE   = 2;

    private static $annotationCache = [];

    private static $hookMethods = [];

    /**
     * @param PHPUnit_Framework_Test $test
     * @param bool                   $asString
     *
     * @return mixed
     */
    public static function describe(PHPUnit_Framework_Test $test, $asString = true)
    {
        if ($asString) {
            if ($test instanceof PHPUnit_Framework_SelfDescribing) {
                return $test->toString();
            } else {
                return get_class($test);
            }
        } else {
            if ($test instanceof PHPUnit_Framework_TestCase) {
                return [
                  get_class($test), $test->getName()
                ];
            } elseif ($test instanceof PHPUnit_Framework_SelfDescribing) {
                return ['', $test->toString()];
            } else {
                return ['', get_class($test)];
            }
        }
    }

    /**
     * @param string $className
     * @param string $methodName
     *
     * @return array|bool
     *
     * @throws PHPUnit_Framework_CodeCoverageException
     */
    public static function getLinesToBeCovered($className, $methodName)
    {
        $annotations = self::parseTestMethodAnnotations(
            $className,
            $methodName
        );

        if (isset($annotations['class']['coversNothing']) || isset($annotations['method']['coversNothing'])) {
            return false;
        }

        return self::getLinesToBeCoveredOrUsed($className, $methodName, 'covers');
    }

    /**
     * Returns lines of code specified with the @uses annotation.
     *
     * @param string $className
     * @param string $methodName
     *
     * @return array
     */
    public static function getLinesToBeUsed($className, $methodName)
    {
        return self::getLinesToBeCoveredOrUsed($className, $methodName, 'uses');
    }

    /**
     * @param string $className
     * @param string $methodName
     * @param string $mode
     *
     * @return array
     *
     * @throws PHPUnit_Framework_CodeCoverageException
     */
    private static function getLinesToBeCoveredOrUsed($className, $methodName, $mode)
    {
        $annotations = self::parseTestMethodAnnotations(
            $className,
            $methodName
        );

        $classShortcut = null;

        if (!empty($annotations['class'][$mode . 'DefaultClass'])) {
            if (count($annotations['class'][$mode . 'DefaultClass']) > 1) {
                throw new PHPUnit_Framework_CodeCoverageException(
                    sprintf(
                        'More than one @%sClass annotation in class or interface "%s".',
                        $mode,
                        $className
                    )
                );
            }

            $classShortcut = $annotations['class'][$mode . 'DefaultClass'][0];
        }

        $list = [];

        if (isset($annotations['class'][$mode])) {
            $list = $annotations['class'][$mode];
        }

        if (isset($annotations['method'][$mode])) {
            $list = array_merge($list, $annotations['method'][$mode]);
        }

        $codeList = [];

        foreach (array_unique($list) as $element) {
            if ($classShortcut && strncmp($element, '::', 2) === 0) {
                $element = $classShortcut . $element;
            }

            $element = preg_replace('/[\s()]+$/', '', $element);
            $element = explode(' ', $element);
            $element = $element[0];

            $codeList = array_merge(
                $codeList,
                self::resolveElementToReflectionObjects($element)
            );
        }

        return self::resolveReflectionObjectsToLines($codeList);
    }

    /**
     * Returns the requirements for a test.
     *
     * @param string $className
     * @param string $methodName
     *
     * @return array
     */
    public static function getRequirements($className, $methodName)
    {
        $reflector  = new ReflectionClass($className);
        $docComment = $reflector->getDocComment();
        $reflector  = new ReflectionMethod($className, $methodName);
        $docComment .= "\n" . $reflector->getDocComment();
        $requires   = [];

        if ($count = preg_match_all(self::REGEX_REQUIRES_OS, $docComment, $matches)) {
            $requires['OS'] = sprintf(
                '/%s/i',
                addcslashes($matches['value'][$count - 1], '/')
            );
        }
        if ($count = preg_match_all(self::REGEX_REQUIRES_VERSION, $docComment, $matches)) {
            for ($i = 0; $i < $count; $i++) {
                $requires[$matches['name'][$i]] = [
                    'version'  => $matches['version'][$i],
                    'operator' => $matches['operator'][$i]
                ];
            }
        }

        // https://bugs.php.net/bug.php?id=63055
        $matches = [];

        if ($count = preg_match_all(self::REGEX_REQUIRES, $docComment, $matches)) {
            for ($i = 0; $i < $count; $i++) {
                $name = $matches['name'][$i] . 's';
                if (!isset($requires[$name])) {
                    $requires[$name] = [];
                }
                $requires[$name][] = $matches['value'][$i];
                if (empty($matches['version'][$i]) || $name != 'extensions') {
                    continue;
                }
                $requires['extension_versions'][$matches['value'][$i]] = [
                    'version'  => $matches['version'][$i],
                    'operator' => $matches['operator'][$i]
                ];
            }
        }

        return $requires;
    }

    /**
     * Returns the missing requirements for a test.
     *
     * @param string $className
     * @param string $methodName
     *
     * @return array
     */
    public static function getMissingRequirements($className, $methodName)
    {
        $required = static::getRequirements($className, $methodName);
        $missing  = [];

        $operator = empty($required['PHP']['operator']) ? '>=' : $required['PHP']['operator'];
        if (!empty($required['PHP']) && !version_compare(PHP_VERSION, $required['PHP']['version'], $operator)) {
            $missing[] = sprintf('PHP %s %s is required.', $operator, $required['PHP']['version']);
        }

        if (!empty($required['PHPUnit'])) {
            $phpunitVersion = PHPUnit_Runner_Version::id();

            $operator = empty($required['PHPUnit']['operator']) ? '>=' : $required['PHPUnit']['operator'];
            if (!version_compare($phpunitVersion, $required['PHPUnit']['version'], $operator)) {
                $missing[] = sprintf('PHPUnit %s %s is required.', $operator, $required['PHPUnit']['version']);
            }
        }

        if (!empty($required['OS']) && !preg_match($required['OS'], PHP_OS)) {
            $missing[] = sprintf('Operating system matching %s is required.', $required['OS']);
        }

        if (!empty($required['functions'])) {
            foreach ($required['functions'] as $function) {
                $pieces = explode('::', $function);
                if (2 === count($pieces) && method_exists($pieces[0], $pieces[1])) {
                    continue;
                }
                if (function_exists($function)) {
                    continue;
                }
                $missing[] = sprintf('Function %s is required.', $function);
            }
        }

        if (!empty($required['extensions'])) {
            foreach ($required['extensions'] as $extension) {
                if (isset($required['extension_versions'][$extension])) {
                    continue;
                }
                if (!extension_loaded($extension)) {
                    $missing[] = sprintf('Extension %s is required.', $extension);
                }
            }
        }

        if (!empty($required['extension_versions'])) {
            foreach ($required['extension_versions'] as $extension => $required) {
                $actualVersion = phpversion($extension);

                $operator = empty($required['operator']) ? '>=' : $required['operator'];
                if (false === $actualVersion || !version_compare($actualVersion, $required['version'], $operator)) {
                    $missing[] = sprintf('Extension %s %s %s is required.', $extension, $operator, $required['version']);
                }
            }
        }

        return $missing;
    }

    /**
     * Returns the expected exception for a test.
     *
     * @param string $className
     * @param string $methodName
     *
     * @return array
     */
    public static function getExpectedException($className, $methodName)
    {
        $reflector  = new ReflectionMethod($className, $methodName);
        $docComment = $reflector->getDocComment();
        $docComment = substr($docComment, 3, -2);

        if (preg_match(self::REGEX_EXPECTED_EXCEPTION, $docComment, $matches)) {
            $annotations = self::parseTestMethodAnnotations(
                $className,
                $methodName
            );

            $class         = $matches[1];
            $code          = null;
            $message       = '';
            $messageRegExp = '';

            if (isset($matches[2])) {
                $message = trim($matches[2]);
            } elseif (isset($annotations['method']['expectedExceptionMessage'])) {
                $message = self::parseAnnotationContent(
                    $annotations['method']['expectedExceptionMessage'][0]
                );
            }

            if (isset($annotations['method']['expectedExceptionMessageRegExp'])) {
                $messageRegExp = self::parseAnnotationContent(
                    $annotations['method']['expectedExceptionMessageRegExp'][0]
                );
            }

            if (isset($matches[3])) {
                $code = $matches[3];
            } elseif (isset($annotations['method']['expectedExceptionCode'])) {
                $code = self::parseAnnotationContent(
                    $annotations['method']['expectedExceptionCode'][0]
                );
            }

            if (is_numeric($code)) {
                $code = (int) $code;
            } elseif (is_string($code) && defined($code)) {
                $code = (int) constant($code);
            }

            return [
              'class' => $class, 'code' => $code, 'message' => $message, 'message_regex' => $messageRegExp
            ];
        }

        return false;
    }

    /**
     * Parse annotation content to use constant/class constant values
     *
     * Constants are specified using a starting '@'. For example: @ClassName::CONST_NAME
     *
     * If the constant is not found the string is used as is to ensure maximum BC.
     *
     * @param string $message
     *
     * @return string
     */
    private static function parseAnnotationContent($message)
    {
        if (strpos($message, '::') !== false && count(explode('::', $message)) == 2) {
            if (defined($message)) {
                $message = constant($message);
            }
        }

        return $message;
    }

    /**
     * Returns the provided data for a method.
     *
     * @param string $className
     * @param string $methodName
     *
     * @return array When a data provider is specified and exists
     *         null  When no data provider is specified
     *
     * @throws PHPUnit_Framework_Exception
     */
    public static function getProvidedData($className, $methodName)
    {
        $reflector  = new ReflectionMethod($className, $methodName);
        $docComment = $reflector->getDocComment();

        $data = self::getDataFromDataProviderAnnotation($docComment, $className, $methodName);

        if ($data === null) {
            $data = self::getDataFromTestWithAnnotation($docComment);
        }

        if (is_array($data) && empty($data)) {
            throw new PHPUnit_Framework_SkippedTestError;
        }

        if ($data !== null) {
            foreach ($data as $key => $value) {
                if (!is_array($value)) {
                    throw new PHPUnit_Framework_Exception(
                        sprintf(
                            'Data set %s is invalid.',
                            is_int($key) ? '#' . $key : '"' . $key . '"'
                        )
                    );
                }
            }
        }

        return $data;
    }

    /**
     * Returns the provided data for a method.
     *
     * @param string $docComment
     * @param string $className
     * @param string $methodName
     *
     * @return array|Iterator when a data provider is specified and exists
     *                        null           when no data provider is specified
     *
     * @throws PHPUnit_Framework_Exception
     */
    private static function getDataFromDataProviderAnnotation($docComment, $className, $methodName)
    {
        if (preg_match_all(self::REGEX_DATA_PROVIDER, $docComment, $matches)) {
            $result = [];

            foreach ($matches[1] as $match) {
                $dataProviderMethodNameNamespace = explode('\\', $match);
                $leaf                            = explode('::', array_pop($dataProviderMethodNameNamespace));
                $dataProviderMethodName          = array_pop($leaf);

                if (!empty($dataProviderMethodNameNamespace)) {
                    $dataProviderMethodNameNamespace = implode('\\', $dataProviderMethodNameNamespace) . '\\';
                } else {
                    $dataProviderMethodNameNamespace = '';
                }

                if (!empty($leaf)) {
                    $dataProviderClassName = $dataProviderMethodNameNamespace . array_pop($leaf);
                } else {
                    $dataProviderClassName = $className;
                }

                $dataProviderClass  = new ReflectionClass($dataProviderClassName);
                $dataProviderMethod = $dataProviderClass->getMethod(
                    $dataProviderMethodName
                );

                if ($dataProviderMethod->isStatic()) {
                    $object = null;
                } else {
                    $object = $dataProviderClass->newInstance();
                }

                if ($dataProviderMethod->getNumberOfParameters() == 0) {
                    $data = $dataProviderMethod->invoke($object);
                } else {
                    $data = $dataProviderMethod->invoke($object, $methodName);
                }

                if ($data instanceof Iterator) {
                    $data = iterator_to_array($data);
                }

                if (is_array($data)) {
                    $result = array_merge($result, $data);
                }
            }

            return $result;
        }
    }

    /**
     * @param string $docComment full docComment string
     *
     * @return array when @testWith annotation is defined
     *               null  when @testWith annotation is omitted
     *
     * @throws PHPUnit_Framework_Exception when @testWith annotation is defined but cannot be parsed
     */
    public static function getDataFromTestWithAnnotation($docComment)
    {
        $docComment = self::cleanUpMultiLineAnnotation($docComment);

        if (preg_match(self::REGEX_TEST_WITH, $docComment, $matches, PREG_OFFSET_CAPTURE)) {
            $offset            = strlen($matches[0][0]) + $matches[0][1];
            $annotationContent = substr($docComment, $offset);
            $data              = [];

            foreach (explode("\n", $annotationContent) as $candidateRow) {
                $candidateRow = trim($candidateRow);

                if ($candidateRow[0] !== '[') {
                    break;
                }

                $dataSet = json_decode($candidateRow, true);

                if (json_last_error() != JSON_ERROR_NONE) {
                    throw new PHPUnit_Framework_Exception(
                        'The dataset for the @testWith annotation cannot be parsed: ' . json_last_error_msg()
                    );
                }

                $data[] = $dataSet;
            }

            if (!$data) {
                throw new PHPUnit_Framework_Exception('The dataset for the @testWith annotation cannot be parsed.');
            }

            return $data;
        }
    }

    private static function cleanUpMultiLineAnnotation($docComment)
    {
        //removing initial '   * ' for docComment
        $docComment = str_replace("\r\n", "\n", $docComment);
        $docComment = preg_replace('/' . '\n' . '\s*' . '\*' . '\s?' . '/', "\n", $docComment);
        $docComment = substr($docComment, 0, -1);
        $docComment = rtrim($docComment, "\n");

        return $docComment;
    }

    /**
     * @param string $className
     * @param string $methodName
     *
     * @return array
     *
     * @throws ReflectionException
     */
    public static function parseTestMethodAnnotations($className, $methodName = '')
    {
        if (!isset(self::$annotationCache[$className])) {
            $class                             = new ReflectionClass($className);
            self::$annotationCache[$className] = self::parseAnnotations($class->getDocComment());
        }

        if (!empty($methodName) && !isset(self::$annotationCache[$className . '::' . $methodName])) {
            try {
                $method      = new ReflectionMethod($className, $methodName);
                $annotations = self::parseAnnotations($method->getDocComment());
            } catch (ReflectionException $e) {
                $annotations = [];
            }
            self::$annotationCache[$className . '::' . $methodName] = $annotations;
        }

        return [
          'class'  => self::$annotationCache[$className],
          'method' => !empty($methodName) ? self::$annotationCache[$className . '::' . $methodName] : []
        ];
    }

    /**
     * @param string $className
     * @param string $methodName
     *
     * @return array
     */
    public static function getInlineAnnotations($className, $methodName)
    {
        $method      = new ReflectionMethod($className, $methodName);
        $code        = file($method->getFileName());
        $lineNumber  = $method->getStartLine();
        $startLine   = $method->getStartLine() - 1;
        $endLine     = $method->getEndLine() - 1;
        $methodLines = array_slice($code, $startLine, $endLine - $startLine + 1);
        $annotations = [];

        foreach ($methodLines as $line) {
            if (preg_match('#/\*\*?\s*@(?P<name>[A-Za-z_-]+)(?:[ \t]+(?P<value>.*?))?[ \t]*\r?\*/$#m', $line, $matches)) {
                $annotations[strtolower($matches['name'])] = [
                    'line'  => $lineNumber,
                    'value' => $matches['value']
                ];
            }

            $lineNumber++;
        }

        return $annotations;
    }

    /**
     * @param string $docblock
     *
     * @return array
     */
    private static function parseAnnotations($docblock)
    {
        $annotations = [];
        // Strip away the docblock header and footer to ease parsing of one line annotations
        $docblock = substr($docblock, 3, -2);

        if (preg_match_all('/@(?P<name>[A-Za-z_-]+)(?:[ \t]+(?P<value>.*?))?[ \t]*\r?$/m', $docblock, $matches)) {
            $numMatches = count($matches[0]);

            for ($i = 0; $i < $numMatches; ++$i) {
                $annotations[$matches['name'][$i]][] = (string) $matches['value'][$i];
            }
        }

        return $annotations;
    }

    /**
     * Returns the backup settings for a test.
     *
     * @param string $className
     * @param string $methodName
     *
     * @return array
     */
    public static function getBackupSettings($className, $methodName)
    {
        return [
          'backupGlobals' => self::getBooleanAnnotationSetting(
              $className,
              $methodName,
              'backupGlobals'
          ),
          'backupStaticAttributes' => self::getBooleanAnnotationSetting(
              $className,
              $methodName,
              'backupStaticAttributes'
          )
        ];
    }

    /**
     * Returns the dependencies for a test class or method.
     *
     * @param string $className
     * @param string $methodName
     *
     * @return array
     */
    public static function getDependencies($className, $methodName)
    {
        $annotations = self::parseTestMethodAnnotations(
            $className,
            $methodName
        );

        $dependencies = [];

        if (isset($annotations['class']['depends'])) {
            $dependencies = $annotations['class']['depends'];
        }

        if (isset($annotations['method']['depends'])) {
            $dependencies = array_merge(
                $dependencies,
                $annotations['method']['depends']
            );
        }

        return array_unique($dependencies);
    }

    /**
     * Returns the error handler settings for a test.
     *
     * @param string $className
     * @param string $methodName
     *
     * @return bool
     */
    public static function getErrorHandlerSettings($className, $methodName)
    {
        return self::getBooleanAnnotationSetting(
            $className,
            $methodName,
            'errorHandler'
        );
    }

    /**
     * Returns the groups for a test class or method.
     *
     * @param string $className
     * @param string $methodName
     *
     * @return array
     */
    public static function getGroups($className, $methodName = '')
    {
        $annotations = self::parseTestMethodAnnotations(
            $className,
            $methodName
        );

        $groups = [];

        if (isset($annotations['method']['author'])) {
            $groups = $annotations['method']['author'];
        } elseif (isset($annotations['class']['author'])) {
            $groups = $annotations['class']['author'];
        }

        if (isset($annotations['class']['group'])) {
            $groups = array_merge($groups, $annotations['class']['group']);
        }

        if (isset($annotations['method']['group'])) {
            $groups = array_merge($groups, $annotations['method']['group']);
        }

        if (isset($annotations['class']['ticket'])) {
            $groups = array_merge($groups, $annotations['class']['ticket']);
        }

        if (isset($annotations['method']['ticket'])) {
            $groups = array_merge($groups, $annotations['method']['ticket']);
        }

        foreach (['method', 'class'] as $element) {
            foreach (['small', 'medium', 'large'] as $size) {
                if (isset($annotations[$element][$size])) {
                    $groups[] = $size;
                    break 2;
                }
            }
        }

        return array_unique($groups);
    }

    /**
     * Returns the size of the test.
     *
     * @param string $className
     * @param string $methodName
     *
     * @return int
     */
    public static function getSize($className, $methodName)
    {
        $groups = array_flip(self::getGroups($className, $methodName));
        $size   = self::UNKNOWN;
        $class  = new ReflectionClass($className);

        if (isset($groups['large']) ||
            (class_exists('PHPUnit_Extensions_Database_TestCase', false) &&
             $class->isSubclassOf('PHPUnit_Extensions_Database_TestCase'))) {
            $size = self::LARGE;
        } elseif (isset($groups['medium'])) {
            $size = self::MEDIUM;
        } elseif (isset($groups['small'])) {
            $size = self::SMALL;
        }

        return $size;
    }

    /**
     * Returns the tickets for a test class or method.
     *
     * @param string $className
     * @param string $methodName
     *
     * @return array
     */
    public static function getTickets($className, $methodName)
    {
        $annotations = self::parseTestMethodAnnotations(
            $className,
            $methodName
        );

        $tickets = [];

        if (isset($annotations['class']['ticket'])) {
            $tickets = $annotations['class']['ticket'];
        }

        if (isset($annotations['method']['ticket'])) {
            $tickets = array_merge($tickets, $annotations['method']['ticket']);
        }

        return array_unique($tickets);
    }

    /**
     * Returns the process isolation settings for a test.
     *
     * @param string $className
     * @param string $methodName
     *
     * @return bool
     */
    public static function getProcessIsolationSettings($className, $methodName)
    {
        $annotations = self::parseTestMethodAnnotations(
            $className,
            $methodName
        );

        if (isset($annotations['class']['runTestsInSeparateProcesses']) ||
            isset($annotations['method']['runInSeparateProcess'])) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Returns the preserve global state settings for a test.
     *
     * @param string $className
     * @param string $methodName
     *
     * @return bool
     */
    public static function getPreserveGlobalStateSettings($className, $methodName)
    {
        return self::getBooleanAnnotationSetting(
            $className,
            $methodName,
            'preserveGlobalState'
        );
    }

    /**
     * @param string $className
     *
     * @return array
     */
    public static function getHookMethods($className)
    {
        if (!class_exists($className, false)) {
            return self::emptyHookMethodsArray();
        }

        if (!isset(self::$hookMethods[$className])) {
            self::$hookMethods[$className] = self::emptyHookMethodsArray();

            try {
                $class = new ReflectionClass($className);

                foreach ($class->getMethods() as $method) {
                    if (self::isBeforeClassMethod($method)) {
                        self::$hookMethods[$className]['beforeClass'][] = $method->getName();
                    }

                    if (self::isBeforeMethod($method)) {
                        self::$hookMethods[$className]['before'][] = $method->getName();
                    }

                    if (self::isAfterMethod($method)) {
                        self::$hookMethods[$className]['after'][] = $method->getName();
                    }

                    if (self::isAfterClassMethod($method)) {
                        self::$hookMethods[$className]['afterClass'][] = $method->getName();
                    }
                }
            } catch (ReflectionException $e) {
            }
        }

        return self::$hookMethods[$className];
    }

    /**
     * @return array
     */
    private static function emptyHookMethodsArray()
    {
        return [
            'beforeClass' => ['setUpBeforeClass'],
            'before'      => ['setUp'],
            'after'       => ['tearDown'],
            'afterClass'  => ['tearDownAfterClass']
        ];
    }

    /**
     * @param string $className
     * @param string $methodName
     * @param string $settingName
     *
     * @return bool
     */
    private static function getBooleanAnnotationSetting($className, $methodName, $settingName)
    {
        $annotations = self::parseTestMethodAnnotations(
            $className,
            $methodName
        );

        $result = null;

        if (isset($annotations['class'][$settingName])) {
            if ($annotations['class'][$settingName][0] == 'enabled') {
                $result = true;
            } elseif ($annotations['class'][$settingName][0] == 'disabled') {
                $result = false;
            }
        }

        if (isset($annotations['method'][$settingName])) {
            if ($annotations['method'][$settingName][0] == 'enabled') {
                $result = true;
            } elseif ($annotations['method'][$settingName][0] == 'disabled') {
                $result = false;
            }
        }

        return $result;
    }

    /**
     * @param string $element
     *
     * @return array
     *
     * @throws PHPUnit_Framework_InvalidCoversTargetException
     */
    private static function resolveElementToReflectionObjects($element)
    {
        $codeToCoverList = [];

        if (strpos($element, '\\') !== false && function_exists($element)) {
            $codeToCoverList[] = new ReflectionFunction($element);
        } elseif (strpos($element, '::') !== false) {
            list($className, $methodName) = explode('::', $element);

            if (isset($methodName[0]) && $methodName[0] == '<') {
                $classes = [$className];

                foreach ($classes as $className) {
                    if (!class_exists($className) &&
                        !interface_exists($className) &&
                        !trait_exists($className)) {
                        throw new PHPUnit_Framework_InvalidCoversTargetException(
                            sprintf(
                                'Trying to @cover or @use not existing class or ' .
                                'interface "%s".',
                                $className
                            )
                        );
                    }

                    $class   = new ReflectionClass($className);
                    $methods = $class->getMethods();
                    $inverse = isset($methodName[1]) && $methodName[1] == '!';

                    if (strpos($methodName, 'protected')) {
                        $visibility = 'isProtected';
                    } elseif (strpos($methodName, 'private')) {
                        $visibility = 'isPrivate';
                    } elseif (strpos($methodName, 'public')) {
                        $visibility = 'isPublic';
                    }

                    foreach ($methods as $method) {
                        if ($inverse && !$method->$visibility()) {
                            $codeToCoverList[] = $method;
                        } elseif (!$inverse && $method->$visibility()) {
                            $codeToCoverList[] = $method;
                        }
                    }
                }
            } else {
                $classes = [$className];

                foreach ($classes as $className) {
                    if ($className == '' && function_exists($methodName)) {
                        $codeToCoverList[] = new ReflectionFunction(
                            $methodName
                        );
                    } else {
                        if (!((class_exists($className) ||
                               interface_exists($className) ||
                               trait_exists($className)) &&
                              method_exists($className, $methodName))) {
                            throw new PHPUnit_Framework_InvalidCoversTargetException(
                                sprintf(
                                    'Trying to @cover or @use not existing method "%s::%s".',
                                    $className,
                                    $methodName
                                )
                            );
                        }

                        $codeToCoverList[] = new ReflectionMethod(
                            $className,
                            $methodName
                        );
                    }
                }
            }
        } else {
            $extended = false;

            if (strpos($element, '<extended>') !== false) {
                $element  = str_replace('<extended>', '', $element);
                $extended = true;
            }

            $classes = [$element];

            if ($extended) {
                $classes = array_merge(
                    $classes,
                    class_implements($element),
                    class_parents($element)
                );
            }

            foreach ($classes as $className) {
                if (!class_exists($className) &&
                    !interface_exists($className) &&
                    !trait_exists($className)) {
                    throw new PHPUnit_Framework_InvalidCoversTargetException(
                        sprintf(
                            'Trying to @cover or @use not existing class or ' .
                            'interface "%s".',
                            $className
                        )
                    );
                }

                $codeToCoverList[] = new ReflectionClass($className);
            }
        }

        return $codeToCoverList;
    }

    /**
     * @param array $reflectors
     *
     * @return array
     */
    private static function resolveReflectionObjectsToLines(array $reflectors)
    {
        $result = [];

        foreach ($reflectors as $reflector) {
            $filename = $reflector->getFileName();

            if (!isset($result[$filename])) {
                $result[$filename] = [];
            }

            $result[$filename] = array_merge(
                $result[$filename],
                range($reflector->getStartLine(), $reflector->getEndLine())
            );
        }

        foreach ($result as $filename => $lineNumbers) {
            $result[$filename] = array_keys(array_flip($lineNumbers));
        }

        return $result;
    }

    /**
     * @param ReflectionMethod $method
     *
     * @return bool
     */
    private static function isBeforeClassMethod(ReflectionMethod $method)
    {
        return $method->isStatic() && strpos($method->getDocComment(), '@beforeClass') !== false;
    }

    /**
     * @param ReflectionMethod $method
     *
     * @return bool
     */
    private static function isBeforeMethod(ReflectionMethod $method)
    {
        return preg_match('/@before\b/', $method->getDocComment());
    }

    /**
     * @param ReflectionMethod $method
     *
     * @return bool
     */
    private static function isAfterClassMethod(ReflectionMethod $method)
    {
        return $method->isStatic() && strpos($method->getDocComment(), '@afterClass') !== false;
    }

    /**
     * @param ReflectionMethod $method
     *
     * @return bool
     */
    private static function isAfterMethod(ReflectionMethod $method)
    {
        return preg_match('/@after\b/', $method->getDocComment());
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

class PHPUnit_Util_GlobalState
{
    /**
     * @var array
     */
    protected static $superGlobalArrays = [
      '_ENV',
      '_POST',
      '_GET',
      '_COOKIE',
      '_SERVER',
      '_FILES',
      '_REQUEST'
    ];

    /**
     * @var array
     */
    protected static $superGlobalArraysLong = [
      'HTTP_ENV_VARS',
      'HTTP_POST_VARS',
      'HTTP_GET_VARS',
      'HTTP_COOKIE_VARS',
      'HTTP_SERVER_VARS',
      'HTTP_POST_FILES'
    ];

    /**
     * @return string
     */
    public static function getIncludedFilesAsString()
    {
        return static::processIncludedFilesAsString(get_included_files());
    }

    /**
     * @param array $files
     *
     * @return string
     */
    public static function processIncludedFilesAsString(array $files)
    {
        $blacklist = new PHPUnit_Util_Blacklist;
        $prefix    = false;
        $result    = '';

        if (defined('__PHPUNIT_PHAR__')) {
            $prefix = 'phar://' . __PHPUNIT_PHAR__ . '/';
        }

        for ($i = count($files) - 1; $i > 0; $i--) {
            $file = $files[$i];

            if ($prefix !== false && strpos($file, $prefix) === 0) {
                continue;
            }

            // Skip virtual file system protocols
            if (preg_match('/^(vfs|phpvfs[a-z0-9]+):/', $file)) {
                continue;
            }

            if (!$blacklist->isBlacklisted($file) && is_file($file)) {
                $result = 'require_once \'' . $file . "';\n" . $result;
            }
        }

        return $result;
    }

    /**
     * @return string
     */
    public static function getIniSettingsAsString()
    {
        $result      = '';
        $iniSettings = ini_get_all(null, false);

        foreach ($iniSettings as $key => $value) {
            $result .= sprintf(
                '@ini_set(%s, %s);' . "\n",
                self::exportVariable($key),
                self::exportVariable($value)
            );
        }

        return $result;
    }

    /**
     * @return string
     */
    public static function getConstantsAsString()
    {
        $constants = get_defined_constants(true);
        $result    = '';

        if (isset($constants['user'])) {
            foreach ($constants['user'] as $name => $value) {
                $result .= sprintf(
                    'if (!defined(\'%s\')) define(\'%s\', %s);' . "\n",
                    $name,
                    $name,
                    self::exportVariable($value)
                );
            }
        }

        return $result;
    }

    /**
     * @return string
     */
    public static function getGlobalsAsString()
    {
        $result            = '';
        $superGlobalArrays = self::getSuperGlobalArrays();

        foreach ($superGlobalArrays as $superGlobalArray) {
            if (isset($GLOBALS[$superGlobalArray]) &&
                is_array($GLOBALS[$superGlobalArray])) {
                foreach (array_keys($GLOBALS[$superGlobalArray]) as $key) {
                    if ($GLOBALS[$superGlobalArray][$key] instanceof Closure) {
                        continue;
                    }

                    $result .= sprintf(
                        '$GLOBALS[\'%s\'][\'%s\'] = %s;' . "\n",
                        $superGlobalArray,
                        $key,
                        self::exportVariable($GLOBALS[$superGlobalArray][$key])
                    );
                }
            }
        }

        $blacklist   = $superGlobalArrays;
        $blacklist[] = 'GLOBALS';

        foreach (array_keys($GLOBALS) as $key) {
            if (!in_array($key, $blacklist) && !$GLOBALS[$key] instanceof Closure) {
                $result .= sprintf(
                    '$GLOBALS[\'%s\'] = %s;' . "\n",
                    $key,
                    self::exportVariable($GLOBALS[$key])
                );
            }
        }

        return $result;
    }

    /**
     * @return array
     */
    protected static function getSuperGlobalArrays()
    {
        if (ini_get('register_long_arrays') == '1') {
            return array_merge(
                self::$superGlobalArrays,
                self::$superGlobalArraysLong
            );
        } else {
            return self::$superGlobalArrays;
        }
    }

    protected static function exportVariable($variable)
    {
        if (is_scalar($variable) || is_null($variable) ||
           (is_array($variable) && self::arrayOnlyContainsScalars($variable))) {
            return var_export($variable, true);
        }

        return 'unserialize(' .
                var_export(serialize($variable), true) .
                ')';
    }

    /**
     * @param array $array
     *
     * @return bool
     */
    protected static function arrayOnlyContainsScalars(array $array)
    {
        $result = true;

        foreach ($array as $element) {
            if (is_array($element)) {
                $result = self::arrayOnlyContainsScalars($element);
            } elseif (!is_scalar($element) && !is_null($element)) {
                $result = false;
            }

            if ($result === false) {
                break;
            }
        }

        return $result;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Base class for printers of TestDox documentation.
 */
abstract class PHPUnit_Util_TestDox_ResultPrinter extends PHPUnit_Util_Printer implements PHPUnit_Framework_TestListener
{
    /**
     * @var PHPUnit_Util_TestDox_NamePrettifier
     */
    protected $prettifier;

    /**
     * @var string
     */
    protected $testClass = '';

    /**
     * @var int
     */
    protected $testStatus = false;

    /**
     * @var array
     */
    protected $tests = [];

    /**
     * @var int
     */
    protected $successful = 0;

    /**
     * @var int
     */
    protected $warned = 0;

    /**
     * @var int
     */
    protected $failed = 0;

    /**
     * @var int
     */
    protected $risky = 0;

    /**
     * @var int
     */
    protected $skipped = 0;

    /**
     * @var int
     */
    protected $incomplete = 0;

    /**
     * @var string
     */
    protected $currentTestClassPrettified;

    /**
     * @var string
     */
    protected $currentTestMethodPrettified;

    /**
     * @var array
     */
    private $groups;

    /**
     * @var array
     */
    private $excludeGroups;

    /**
     * @param resource $out
     * @param array    $groups
     * @param array    $excludeGroups
     */
    public function __construct($out = null, array $groups = [], array $excludeGroups = [])
    {
        parent::__construct($out);

        $this->groups        = $groups;
        $this->excludeGroups = $excludeGroups;

        $this->prettifier = new PHPUnit_Util_TestDox_NamePrettifier;
        $this->startRun();
    }

    /**
     * Flush buffer and close output.
     */
    public function flush()
    {
        $this->doEndClass();
        $this->endRun();

        parent::flush();
    }

    /**
     * An error occurred.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addError(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        if (!$this->isOfInterest($test)) {
            return;
        }

        $this->testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_ERROR;
        $this->failed++;
    }

    /**
     * A warning occurred.
     *
     * @param PHPUnit_Framework_Test    $test
     * @param PHPUnit_Framework_Warning $e
     * @param float                     $time
     */
    public function addWarning(PHPUnit_Framework_Test $test, PHPUnit_Framework_Warning $e, $time)
    {
        if (!$this->isOfInterest($test)) {
            return;
        }

        $this->testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_WARNING;
        $this->warned++;
    }

    /**
     * A failure occurred.
     *
     * @param PHPUnit_Framework_Test                 $test
     * @param PHPUnit_Framework_AssertionFailedError $e
     * @param float                                  $time
     */
    public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time)
    {
        if (!$this->isOfInterest($test)) {
            return;
        }

        $this->testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE;
        $this->failed++;
    }

    /**
     * Incomplete test.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        if (!$this->isOfInterest($test)) {
            return;
        }

        $this->testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE;
        $this->incomplete++;
    }

    /**
     * Risky test.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        if (!$this->isOfInterest($test)) {
            return;
        }

        $this->testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_RISKY;
        $this->risky++;
    }

    /**
     * Skipped test.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        if (!$this->isOfInterest($test)) {
            return;
        }

        $this->testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED;
        $this->skipped++;
    }

    /**
     * A testsuite started.
     *
     * @param PHPUnit_Framework_TestSuite $suite
     */
    public function startTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
    }

    /**
     * A testsuite ended.
     *
     * @param PHPUnit_Framework_TestSuite $suite
     */
    public function endTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
    }

    /**
     * A test started.
     *
     * @param PHPUnit_Framework_Test $test
     */
    public function startTest(PHPUnit_Framework_Test $test)
    {
        if (!$this->isOfInterest($test)) {
            return;
        }

        $class = get_class($test);

        if ($this->testClass != $class) {
            if ($this->testClass != '') {
                $this->doEndClass();
            }

            $classAnnotations = PHPUnit_Util_Test::parseTestMethodAnnotations($class);
            if (isset($classAnnotations['class']['testdox'][0])) {
                $this->currentTestClassPrettified = $classAnnotations['class']['testdox'][0];
            } else {
                $this->currentTestClassPrettified = $this->prettifier->prettifyTestClass($class);
            }

            $this->startClass($class);

            $this->testClass = $class;
            $this->tests     = [];
        }

        $annotations = $test->getAnnotations();

        if (isset($annotations['method']['testdox'][0])) {
            $this->currentTestMethodPrettified = $annotations['method']['testdox'][0];
        } else {
            $this->currentTestMethodPrettified = $this->prettifier->prettifyTestMethod($test->getName(false));
        }

        if ($test instanceof PHPUnit_Framework_TestCase && $test->usesDataProvider()) {
            $this->currentTestMethodPrettified .= ' ' . $test->dataDescription();
        }

        $this->testStatus = PHPUnit_Runner_BaseTestRunner::STATUS_PASSED;
    }

    /**
     * A test ended.
     *
     * @param PHPUnit_Framework_Test $test
     * @param float                  $time
     */
    public function endTest(PHPUnit_Framework_Test $test, $time)
    {
        if (!$this->isOfInterest($test)) {
            return;
        }

        if (!isset($this->tests[$this->currentTestMethodPrettified])) {
            if ($this->testStatus == PHPUnit_Runner_BaseTestRunner::STATUS_PASSED) {
                $this->tests[$this->currentTestMethodPrettified]['success'] = 1;
                $this->tests[$this->currentTestMethodPrettified]['failure'] = 0;
            } else {
                $this->tests[$this->currentTestMethodPrettified]['success'] = 0;
                $this->tests[$this->currentTestMethodPrettified]['failure'] = 1;
            }
        } else {
            if ($this->testStatus == PHPUnit_Runner_BaseTestRunner::STATUS_PASSED) {
                $this->tests[$this->currentTestMethodPrettified]['success']++;
            } else {
                $this->tests[$this->currentTestMethodPrettified]['failure']++;
            }
        }

        $this->currentTestClassPrettified  = null;
        $this->currentTestMethodPrettified = null;
    }

    protected function doEndClass()
    {
        foreach ($this->tests as $name => $data) {
            $this->onTest($name, $data['failure'] == 0);
        }

        $this->endClass($this->testClass);
    }

    /**
     * Handler for 'start run' event.
     */
    protected function startRun()
    {
    }

    /**
     * Handler for 'start class' event.
     *
     * @param string $name
     */
    protected function startClass($name)
    {
    }

    /**
     * Handler for 'on test' event.
     *
     * @param string $name
     * @param bool   $success
     */
    protected function onTest($name, $success = true)
    {
    }

    /**
     * Handler for 'end class' event.
     *
     * @param string $name
     */
    protected function endClass($name)
    {
    }

    /**
     * Handler for 'end run' event.
     */
    protected function endRun()
    {
    }

    /**
     * @param PHPUnit_Framework_Test $test
     *
     * @return bool
     */
    private function isOfInterest(PHPUnit_Framework_Test $test)
    {
        if (!$test instanceof PHPUnit_Framework_TestCase) {
            return false;
        }

        if ($test instanceof PHPUnit_Framework_WarningTestCase) {
            return false;
        }

        if (!empty($this->groups)) {
            foreach ($test->getGroups() as $group) {
                if (in_array($group, $this->groups)) {
                    return true;
                }
            }

            return false;
        }

        if (!empty($this->excludeGroups)) {
            foreach ($test->getGroups() as $group) {
                if (in_array($group, $this->excludeGroups)) {
                    return false;
                }
            }

            return true;
        }

        return true;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Prettifies class and method names for use in TestDox documentation.
 */
class PHPUnit_Util_TestDox_NamePrettifier
{
    /**
     * @var string
     */
    protected $prefix = 'Test';

    /**
     * @var string
     */
    protected $suffix = 'Test';

    /**
     * @var array
     */
    protected $strings = [];

    /**
     * Prettifies the name of a test class.
     *
     * @param string $name
     *
     * @return string
     */
    public function prettifyTestClass($name)
    {
        $title = $name;

        if ($this->suffix !== null &&
            $this->suffix == substr($name, -1 * strlen($this->suffix))) {
            $title = substr($title, 0, strripos($title, $this->suffix));
        }

        if ($this->prefix !== null &&
            $this->prefix == substr($name, 0, strlen($this->prefix))) {
            $title = substr($title, strlen($this->prefix));
        }

        if (substr($title, 0, 1) == '\\') {
            $title = substr($title, 1);
        }

        return $title;
    }

    /**
     * Prettifies the name of a test method.
     *
     * @param string $name
     *
     * @return string
     */
    public function prettifyTestMethod($name)
    {
        $buffer = '';

        if (!is_string($name) || strlen($name) == 0) {
            return $buffer;
        }

        $string = preg_replace('#\d+$#', '', $name, -1, $count);

        if (in_array($string, $this->strings)) {
            $name = $string;
        } elseif ($count == 0) {
            $this->strings[] = $string;
        }

        if (substr($name, 0, 4) == 'test') {
            $name = substr($name, 4);
        }

        if (strlen($name) == 0) {
            return $buffer;
        }

        $name[0] = strtoupper($name[0]);

        if (strpos($name, '_') !== false) {
            return trim(str_replace('_', ' ', $name));
        }

        $max        = strlen($name);
        $wasNumeric = false;

        for ($i = 0; $i < $max; $i++) {
            if ($i > 0 &&
                ord($name[$i]) >= 65 &&
                ord($name[$i]) <= 90) {
                $buffer .= ' ' . strtolower($name[$i]);
            } else {
                $isNumeric = is_numeric($name[$i]);

                if (!$wasNumeric && $isNumeric) {
                    $buffer    .= ' ';
                    $wasNumeric = true;
                }

                if ($wasNumeric && !$isNumeric) {
                    $wasNumeric = false;
                }

                $buffer .= $name[$i];
            }
        }

        return $buffer;
    }

    /**
     * Sets the prefix of test names.
     *
     * @param string $prefix
     */
    public function setPrefix($prefix)
    {
        $this->prefix = $prefix;
    }

    /**
     * Sets the suffix of test names.
     *
     * @param string $suffix
     */
    public function setSuffix($suffix)
    {
        $this->suffix = $suffix;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

class PHPUnit_Util_TestDox_ResultPrinter_XML extends PHPUnit_Util_Printer implements PHPUnit_Framework_TestListener
{
    /**
     * @var DOMDocument
     */
    private $document;

    /**
     * @var DOMElement
     */
    private $root;

    /**
     * @var PHPUnit_Util_TestDox_NamePrettifier
     */
    private $prettifier;

    /**
     * @var Exception
     */
    private $exception;

    /**
     * @param string|resource $out
     */
    public function __construct($out = null)
    {
        $this->document               = new DOMDocument('1.0', 'UTF-8');
        $this->document->formatOutput = true;

        $this->root = $this->document->createElement('tests');
        $this->document->appendChild($this->root);

        $this->prettifier = new PHPUnit_Util_TestDox_NamePrettifier;

        parent::__construct($out);
    }

    /**
     * Flush buffer and close output.
     */
    public function flush()
    {
        $this->write($this->document->saveXML());

        parent::flush();
    }

    /**
     * An error occurred.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addError(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        $this->exception = $e;
    }

    /**
     * A warning occurred.
     *
     * @param PHPUnit_Framework_Test    $test
     * @param PHPUnit_Framework_Warning $e
     * @param float                     $time
     */
    public function addWarning(PHPUnit_Framework_Test $test, PHPUnit_Framework_Warning $e, $time)
    {
    }

    /**
     * A failure occurred.
     *
     * @param PHPUnit_Framework_Test                 $test
     * @param PHPUnit_Framework_AssertionFailedError $e
     * @param float                                  $time
     */
    public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time)
    {
        $this->exception = $e;
    }

    /**
     * Incomplete test.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
    }

    /**
     * Risky test.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
    }

    /**
     * Skipped test.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
    }

    /**
     * A test suite started.
     *
     * @param PHPUnit_Framework_TestSuite $suite
     */
    public function startTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
    }

    /**
     * A test suite ended.
     *
     * @param PHPUnit_Framework_TestSuite $suite
     */
    public function endTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
    }

    /**
     * A test started.
     *
     * @param PHPUnit_Framework_Test $test
     */
    public function startTest(PHPUnit_Framework_Test $test)
    {
        $this->exception = null;
    }

    /**
     * A test ended.
     *
     * @param PHPUnit_Framework_Test $test
     * @param float                  $time
     */
    public function endTest(PHPUnit_Framework_Test $test, $time)
    {
        if (!$test instanceof PHPUnit_Framework_TestCase) {
            return;
        }

        /* @var PHPUnit_Framework_TestCase $test */

        $groups = array_filter(
            $test->getGroups(),
            function ($group) {
                if ($group == 'small' || $group == 'medium' || $group == 'large') {
                    return false;
                }

                return true;
            }
        );

        $node = $this->document->createElement('test');

        $node->setAttribute('className', get_class($test));
        $node->setAttribute('methodName', $test->getName());
        $node->setAttribute('prettifiedClassName', $this->prettifier->prettifyTestClass(get_class($test)));
        $node->setAttribute('prettifiedMethodName', $this->prettifier->prettifyTestMethod($test->getName()));
        $node->setAttribute('status', $test->getStatus());
        $node->setAttribute('time', $time);
        $node->setAttribute('size', $test->getSize());
        $node->setAttribute('groups', implode(',', $groups));

        $inlineAnnotations = PHPUnit_Util_Test::getInlineAnnotations(get_class($test), $test->getName());

        if (isset($inlineAnnotations['given']) && isset($inlineAnnotations['when']) && isset($inlineAnnotations['then'])) {
            $node->setAttribute('given', $inlineAnnotations['given']['value']);
            $node->setAttribute('givenStartLine', $inlineAnnotations['given']['line']);
            $node->setAttribute('when', $inlineAnnotations['when']['value']);
            $node->setAttribute('whenStartLine', $inlineAnnotations['when']['line']);
            $node->setAttribute('then', $inlineAnnotations['then']['value']);
            $node->setAttribute('thenStartLine', $inlineAnnotations['then']['line']);
        }

        if ($this->exception !== null) {
            if ($this->exception instanceof PHPUnit_Framework_Exception) {
                $steps = $this->exception->getSerializableTrace();
            } else {
                $steps = $this->exception->getTrace();
            }

            $class = new ReflectionClass($test);
            $file  = $class->getFileName();

            foreach ($steps as $step) {
                if (isset($step['file']) && $step['file'] == $file) {
                    $node->setAttribute('exceptionLine', $step['line']);

                    break;
                }
            }

            $node->setAttribute('exceptionMessage', $this->exception->getMessage());
        }

        $this->root->appendChild($node);
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Prints TestDox documentation in HTML format.
 */
class PHPUnit_Util_TestDox_ResultPrinter_HTML extends PHPUnit_Util_TestDox_ResultPrinter
{
    /**
     * @var string
     */
    private $pageHeader = <<<EOT
<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8"/>
        <title>Test Documentation</title>
        <style>
            body {
                text-rendering: optimizeLegibility;
                font-variant-ligatures: common-ligatures;
                font-kerning: normal;
                margin-left: 2em;
            }

            body > ul > li {
                font-family: Source Serif Pro, PT Sans, Trebuchet MS, Helvetica, Arial;
                font-size: 2em;
            }

            h2 {
                font-family: Tahoma, Helvetica, Arial;
                font-size: 3em;
            }

            ul {
                list-style: none;
                margin-bottom: 1em;
            }
        </style>
    </head>
    <body>
EOT;

    /**
     * @var string
     */
    private $classHeader = <<<EOT

        <h2 id="%s">%s</h2>
        <ul>

EOT;

    /**
     * @var string
     */
    private $classFooter = <<<EOT
        </ul>
EOT;

    /**
     * @var string
     */
    private $pageFooter = <<<EOT

    </body>
</html>
EOT;

    /**
     * Handler for 'start run' event.
     */
    protected function startRun()
    {
        $this->write($this->pageHeader);
    }

    /**
     * Handler for 'start class' event.
     *
     * @param string $name
     */
    protected function startClass($name)
    {
        $this->write(
            sprintf(
                $this->classHeader,
                $name,
                $this->currentTestClassPrettified
            )
        );
    }

    /**
     * Handler for 'on test' event.
     *
     * @param string $name
     * @param bool   $success
     */
    protected function onTest($name, $success = true)
    {
        $this->write(
            sprintf(
                "            <li style=\"color: %s;\">%s %s</li>\n",
                $success ? '#555753' : '#ef2929',
                $success ? 'âœ“' : 'â�Œ',
                $name
            )
        );
    }

    /**
     * Handler for 'end class' event.
     *
     * @param string $name
     */
    protected function endClass($name)
    {
        $this->write($this->classFooter);
    }

    /**
     * Handler for 'end run' event.
     */
    protected function endRun()
    {
        $this->write($this->pageFooter);
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Prints TestDox documentation in text format.
 */
class PHPUnit_Util_TestDox_ResultPrinter_Text extends PHPUnit_Util_TestDox_ResultPrinter
{
    /**
     * Handler for 'start class' event.
     *
     * @param string $name
     */
    protected function startClass($name)
    {
        $this->write($this->currentTestClassPrettified . "\n");
    }

    /**
     * Handler for 'on test' event.
     *
     * @param string $name
     * @param bool   $success
     */
    protected function onTest($name, $success = true)
    {
        if ($success) {
            $this->write(' [x] ');
        } else {
            $this->write(' [ ] ');
        }

        $this->write($name . "\n");
    }

    /**
     * Handler for 'end class' event.
     *
     * @param string $name
     */
    protected function endClass($name)
    {
        $this->write("\n");
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Utility class for code filtering.
 */
class PHPUnit_Util_Filter
{
    /**
     * Filters stack frames from PHPUnit classes.
     *
     * @param Exception $e
     * @param bool      $asString
     *
     * @return string
     */
    public static function getFilteredStacktrace($e, $asString = true)
    {
        $prefix = false;
        $script = realpath($GLOBALS['_SERVER']['SCRIPT_NAME']);

        if (defined('__PHPUNIT_PHAR_ROOT__')) {
            $prefix = __PHPUNIT_PHAR_ROOT__;
        }

        if ($asString === true) {
            $filteredStacktrace = '';
        } else {
            $filteredStacktrace = [];
        }

        if ($e instanceof PHPUnit_Framework_SyntheticError) {
            $eTrace = $e->getSyntheticTrace();
            $eFile  = $e->getSyntheticFile();
            $eLine  = $e->getSyntheticLine();
        } elseif ($e instanceof PHPUnit_Framework_Exception) {
            $eTrace = $e->getSerializableTrace();
            $eFile  = $e->getFile();
            $eLine  = $e->getLine();
        } else {
            if ($e->getPrevious()) {
                $e = $e->getPrevious();
            }
            $eTrace = $e->getTrace();
            $eFile  = $e->getFile();
            $eLine  = $e->getLine();
        }

        if (!self::frameExists($eTrace, $eFile, $eLine)) {
            array_unshift(
                $eTrace,
                ['file' => $eFile, 'line' => $eLine]
            );
        }

        $blacklist = new PHPUnit_Util_Blacklist;

        foreach ($eTrace as $frame) {
            if (isset($frame['file']) && is_file($frame['file']) &&
                !$blacklist->isBlacklisted($frame['file']) &&
                ($prefix === false || strpos($frame['file'], $prefix) !== 0) &&
                $frame['file'] !== $script) {
                if ($asString === true) {
                    $filteredStacktrace .= sprintf(
                        "%s:%s\n",
                        $frame['file'],
                        isset($frame['line']) ? $frame['line'] : '?'
                    );
                } else {
                    $filteredStacktrace[] = $frame;
                }
            }
        }

        return $filteredStacktrace;
    }

    /**
     * @param array  $trace
     * @param string $file
     * @param int    $line
     *
     * @return bool
     */
    private static function frameExists(array $trace, $file, $line)
    {
        foreach ($trace as $frame) {
            if (isset($frame['file']) && $frame['file'] == $file &&
                isset($frame['line']) && $frame['line'] == $line) {
                return true;
            }
        }

        return false;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * String helpers.
 */
class PHPUnit_Util_String
{
    /**
     * Converts a string to UTF-8 encoding.
     *
     * @param string $string
     *
     * @return string
     */
    public static function convertToUtf8($string)
    {
        return mb_convert_encoding($string, 'UTF-8');
    }

    /**
     * Checks a string for UTF-8 encoding.
     *
     * @param string $string
     *
     * @return bool
     */
    protected static function isUtf8($string)
    {
        $length = strlen($string);

        for ($i = 0; $i < $length; $i++) {
            if (ord($string[$i]) < 0x80) {
                $n = 0;
            } elseif ((ord($string[$i]) & 0xE0) == 0xC0) {
                $n = 1;
            } elseif ((ord($string[$i]) & 0xF0) == 0xE0) {
                $n = 2;
            } elseif ((ord($string[$i]) & 0xF0) == 0xF0) {
                $n = 3;
            } else {
                return false;
            }

            for ($j = 0; $j < $n; $j++) {
                if ((++$i == $length) || ((ord($string[$i]) & 0xC0) != 0x80)) {
                    return false;
                }
            }
        }

        return true;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use SebastianBergmann\Comparator\ComparisonFailure;

/**
 * A TestListener that generates a logfile of the test execution using the
 * TeamCity format (for use with PhpStorm, for instance).
 */
class PHPUnit_Util_Log_TeamCity extends PHPUnit_TextUI_ResultPrinter
{
    /**
     * @var bool
     */
    private $isSummaryTestCountPrinted = false;

    /**
     * @var string
     */
    private $startedTestName;

    /**
     * @var string
     */
    private $flowId;

    /**
     * @param string $progress
     */
    protected function writeProgress($progress)
    {
    }

    /**
     * @param PHPUnit_Framework_TestResult $result
     */
    public function printResult(PHPUnit_Framework_TestResult $result)
    {
        $this->printHeader();
        $this->printFooter($result);
    }

    /**
     * An error occurred.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addError(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        $this->printEvent(
            'testFailed',
            [
                'name'    => $test->getName(),
                'message' => self::getMessage($e),
                'details' => self::getDetails($e),
            ]
        );
    }

    /**
     * A warning occurred.
     *
     * @param PHPUnit_Framework_Test    $test
     * @param PHPUnit_Framework_Warning $e
     * @param float                     $time
     */
    public function addWarning(PHPUnit_Framework_Test $test, PHPUnit_Framework_Warning $e, $time)
    {
        $this->printEvent(
            'testFailed',
            [
                'name'    => $test->getName(),
                'message' => self::getMessage($e),
                'details' => self::getDetails($e)
            ]
        );
    }

    /**
     * A failure occurred.
     *
     * @param PHPUnit_Framework_Test                 $test
     * @param PHPUnit_Framework_AssertionFailedError $e
     * @param float                                  $time
     */
    public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time)
    {
        $parameters = [
            'name'    => $test->getName(),
            'message' => self::getMessage($e),
            'details' => self::getDetails($e),
        ];

        if ($e instanceof PHPUnit_Framework_ExpectationFailedException) {
            $comparisonFailure = $e->getComparisonFailure();

            if ($comparisonFailure instanceof ComparisonFailure) {
                $expectedString = $comparisonFailure->getExpectedAsString();

                if (is_null($expectedString) || empty($expectedString)) {
                    $expectedString = self::getPrimitiveValueAsString($comparisonFailure->getExpected());
                }

                $actualString = $comparisonFailure->getActualAsString();

                if (is_null($actualString) || empty($actualString)) {
                    $actualString = self::getPrimitiveValueAsString($comparisonFailure->getActual());
                }

                if (!is_null($actualString) && !is_null($expectedString)) {
                    $parameters['type']     = 'comparisonFailure';
                    $parameters['actual']   = $actualString;
                    $parameters['expected'] = $expectedString;
                }
            }
        }

        $this->printEvent('testFailed', $parameters);
    }

    /**
     * Incomplete test.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        $this->printIgnoredTest($test->getName(), $e);
    }

    /**
     * Risky test.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        $this->addError($test, $e, $time);
    }

    /**
     * Skipped test.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        $testName = $test->getName();
        if ($this->startedTestName != $testName) {
            $this->startTest($test);
            $this->printIgnoredTest($testName, $e);
            $this->endTest($test, $time);
        } else {
            $this->printIgnoredTest($testName, $e);
        }
    }

    public function printIgnoredTest($testName, Exception $e)
    {
        $this->printEvent(
            'testIgnored',
            [
                'name'    => $testName,
                'message' => self::getMessage($e),
                'details' => self::getDetails($e),
            ]
        );
    }

    /**
     * A testsuite started.
     *
     * @param PHPUnit_Framework_TestSuite $suite
     */
    public function startTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
        if (stripos(ini_get('disable_functions'), 'getmypid') === false) {
            $this->flowId = getmypid();
        } else {
            $this->flowId = false;
        }

        if (!$this->isSummaryTestCountPrinted) {
            $this->isSummaryTestCountPrinted = true;

            $this->printEvent(
                'testCount',
                ['count' => count($suite)]
            );
        }

        $suiteName = $suite->getName();

        if (empty($suiteName)) {
            return;
        }

        $parameters = ['name' => $suiteName];

        if (class_exists($suiteName, false)) {
            $fileName                   = self::getFileName($suiteName);
            $parameters['locationHint'] = "php_qn://$fileName::\\$suiteName";
        } else {
            $split = preg_split('/::/', $suiteName);

            if (count($split) == 2 && method_exists($split[0], $split[1])) {
                $fileName                   = self::getFileName($split[0]);
                $parameters['locationHint'] = "php_qn://$fileName::\\$suiteName";
                $parameters['name']         = $split[1];
            }
        }

        $this->printEvent('testSuiteStarted', $parameters);
    }

    /**
     * A testsuite ended.
     *
     * @param PHPUnit_Framework_TestSuite $suite
     */
    public function endTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
        $suiteName = $suite->getName();

        if (empty($suiteName)) {
            return;
        }

        $parameters = ['name' => $suiteName];

        if (!class_exists($suiteName, false)) {
            $split = preg_split('/::/', $suiteName);

            if (count($split) == 2 && method_exists($split[0], $split[1])) {
                $parameters['name'] = $split[1];
            }
        }

        $this->printEvent('testSuiteFinished', $parameters);
    }

    /**
     * A test started.
     *
     * @param PHPUnit_Framework_Test $test
     */
    public function startTest(PHPUnit_Framework_Test $test)
    {
        $testName              = $test->getName();
        $this->startedTestName = $testName;
        $params                = ['name' => $testName];

        if ($test instanceof PHPUnit_Framework_TestCase) {
            $className              = get_class($test);
            $fileName               = self::getFileName($className);
            $params['locationHint'] = "php_qn://$fileName::\\$className::$testName";
        }

        $this->printEvent('testStarted', $params);
    }

    /**
     * A test ended.
     *
     * @param PHPUnit_Framework_Test $test
     * @param float                  $time
     */
    public function endTest(PHPUnit_Framework_Test $test, $time)
    {
        parent::endTest($test, $time);

        $this->printEvent(
            'testFinished',
            [
                'name'     => $test->getName(),
                'duration' => (int) (round($time, 2) * 1000)
            ]
        );
    }

    /**
     * @param string $eventName
     * @param array  $params
     */
    private function printEvent($eventName, $params = [])
    {
        $this->write("\n##teamcity[$eventName");

        if ($this->flowId) {
            $params['flowId'] = $this->flowId;
        }

        foreach ($params as $key => $value) {
            $escapedValue = self::escapeValue($value);
            $this->write(" $key='$escapedValue'");
        }

        $this->write("]\n");
    }

    /**
     * @param Exception $e
     *
     * @return string
     */
    private static function getMessage(Exception $e)
    {
        $message = '';

        if ($e instanceof PHPUnit_Framework_ExceptionWrapper) {
            if (strlen($e->getClassName()) != 0) {
                $message = $message . $e->getClassName();
            }

            if (strlen($message) != 0 && strlen($e->getMessage()) != 0) {
                $message = $message . ' : ';
            }
        }

        return $message . $e->getMessage();
    }

    /**
     * @param Exception $e
     *
     * @return string
     */
    private static function getDetails(Exception $e)
    {
        $stackTrace = PHPUnit_Util_Filter::getFilteredStacktrace($e);
        $previous   = $e->getPrevious();

        while ($previous) {
            $stackTrace .= "\nCaused by\n" .
                PHPUnit_Framework_TestFailure::exceptionToString($previous) . "\n" .
                PHPUnit_Util_Filter::getFilteredStacktrace($previous);

            $previous = $previous->getPrevious();
        }

        return ' ' . str_replace("\n", "\n ", $stackTrace);
    }

    /**
     * @param mixed $value
     *
     * @return string
     */
    private static function getPrimitiveValueAsString($value)
    {
        if (is_null($value)) {
            return 'null';
        } elseif (is_bool($value)) {
            return $value == true ? 'true' : 'false';
        } elseif (is_scalar($value)) {
            return print_r($value, true);
        }

        return;
    }

    /**
     * @param  $text
     *
     * @return string
     */
    private static function escapeValue($text)
    {
        $text = str_replace('|', '||', $text);
        $text = str_replace("'", "|'", $text);
        $text = str_replace("\n", '|n', $text);
        $text = str_replace("\r", '|r', $text);
        $text = str_replace(']', '|]', $text);
        $text = str_replace('[', '|[', $text);

        return $text;
    }

    /**
     * @param string $className
     *
     * @return string
     */
    private static function getFileName($className)
    {
        $reflectionClass = new ReflectionClass($className);
        $fileName        = $reflectionClass->getFileName();

        return $fileName;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A TestListener that generates JSON messages.
 */
class PHPUnit_Util_Log_JSON extends PHPUnit_Util_Printer implements PHPUnit_Framework_TestListener
{
    /**
     * @var string
     */
    protected $currentTestSuiteName = '';

    /**
     * @var string
     */
    protected $currentTestName = '';

    /**
     * @var bool
     */
    protected $currentTestPass = true;

    /**
     * An error occurred.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addError(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        $this->writeCase(
            'error',
            $time,
            PHPUnit_Util_Filter::getFilteredStacktrace($e, false),
            PHPUnit_Framework_TestFailure::exceptionToString($e),
            $test
        );

        $this->currentTestPass = false;
    }

    /**
     * A warning occurred.
     *
     * @param PHPUnit_Framework_Test    $test
     * @param PHPUnit_Framework_Warning $e
     * @param float                     $time
     */
    public function addWarning(PHPUnit_Framework_Test $test, PHPUnit_Framework_Warning $e, $time)
    {
        $this->writeCase(
            'warning',
            $time,
            PHPUnit_Util_Filter::getFilteredStacktrace($e, false),
            PHPUnit_Framework_TestFailure::exceptionToString($e),
            $test
        );

        $this->currentTestPass = false;
    }

    /**
     * A failure occurred.
     *
     * @param PHPUnit_Framework_Test                 $test
     * @param PHPUnit_Framework_AssertionFailedError $e
     * @param float                                  $time
     */
    public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time)
    {
        $this->writeCase(
            'fail',
            $time,
            PHPUnit_Util_Filter::getFilteredStacktrace($e, false),
            PHPUnit_Framework_TestFailure::exceptionToString($e),
            $test
        );

        $this->currentTestPass = false;
    }

    /**
     * Incomplete test.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        $this->writeCase(
            'error',
            $time,
            PHPUnit_Util_Filter::getFilteredStacktrace($e, false),
            'Incomplete Test: ' . $e->getMessage(),
            $test
        );

        $this->currentTestPass = false;
    }

    /**
     * Risky test.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        $this->writeCase(
            'error',
            $time,
            PHPUnit_Util_Filter::getFilteredStacktrace($e, false),
            'Risky Test: ' . $e->getMessage(),
            $test
        );

        $this->currentTestPass = false;
    }

    /**
     * Skipped test.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        $this->writeCase(
            'error',
            $time,
            PHPUnit_Util_Filter::getFilteredStacktrace($e, false),
            'Skipped Test: ' . $e->getMessage(),
            $test
        );

        $this->currentTestPass = false;
    }

    /**
     * A testsuite started.
     *
     * @param PHPUnit_Framework_TestSuite $suite
     */
    public function startTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
        $this->currentTestSuiteName = $suite->getName();
        $this->currentTestName      = '';

        $this->write(
            [
            'event' => 'suiteStart',
            'suite' => $this->currentTestSuiteName,
            'tests' => count($suite)
            ]
        );
    }

    /**
     * A testsuite ended.
     *
     * @param PHPUnit_Framework_TestSuite $suite
     */
    public function endTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
        $this->currentTestSuiteName = '';
        $this->currentTestName      = '';
    }

    /**
     * A test started.
     *
     * @param PHPUnit_Framework_Test $test
     */
    public function startTest(PHPUnit_Framework_Test $test)
    {
        $this->currentTestName = PHPUnit_Util_Test::describe($test);
        $this->currentTestPass = true;

        $this->write(
            [
            'event' => 'testStart',
            'suite' => $this->currentTestSuiteName,
            'test'  => $this->currentTestName
            ]
        );
    }

    /**
     * A test ended.
     *
     * @param PHPUnit_Framework_Test $test
     * @param float                  $time
     */
    public function endTest(PHPUnit_Framework_Test $test, $time)
    {
        if ($this->currentTestPass) {
            $this->writeCase('pass', $time, [], '', $test);
        }
    }

    /**
     * @param string                          $status
     * @param float                           $time
     * @param array                           $trace
     * @param string                          $message
     * @param PHPUnit_Framework_TestCase|null $test
     */
    protected function writeCase($status, $time, array $trace = [], $message = '', $test = null)
    {
        $output = '';
        // take care of TestSuite producing error (e.g. by running into exception) as TestSuite doesn't have hasOutput
        if ($test !== null && method_exists($test, 'hasOutput') && $test->hasOutput()) {
            $output = $test->getActualOutput();
        }
        $this->write(
            [
            'event'   => 'test',
            'suite'   => $this->currentTestSuiteName,
            'test'    => $this->currentTestName,
            'status'  => $status,
            'time'    => $time,
            'trace'   => $trace,
            'message' => PHPUnit_Util_String::convertToUtf8($message),
            'output'  => $output,
            ]
        );
    }

    /**
     * @param string $buffer
     */
    public function write($buffer)
    {
        array_walk_recursive($buffer, function (&$input) {
            if (is_string($input)) {
                $input = PHPUnit_Util_String::convertToUtf8($input);
            }
        });

        parent::write(json_encode($buffer, JSON_PRETTY_PRINT));
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A TestListener that generates a logfile of the
 * test execution using the Test Anything Protocol (TAP).
 */
class PHPUnit_Util_Log_TAP extends PHPUnit_Util_Printer implements PHPUnit_Framework_TestListener
{
    /**
     * @var int
     */
    protected $testNumber = 0;

    /**
     * @var int
     */
    protected $testSuiteLevel = 0;

    /**
     * @var bool
     */
    protected $testSuccessful = true;

    /**
     * Constructor.
     *
     * @param mixed $out
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function __construct($out = null)
    {
        parent::__construct($out);
        $this->write("TAP version 13\n");
    }

    /**
     * An error occurred.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addError(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        $this->writeNotOk($test, 'Error');
    }

    /**
     * A warning occurred.
     *
     * @param PHPUnit_Framework_Test    $test
     * @param PHPUnit_Framework_Warning $e
     * @param float                     $time
     */
    public function addWarning(PHPUnit_Framework_Test $test, PHPUnit_Framework_Warning $e, $time)
    {
        $this->writeNotOk($test, 'Warning');
    }

    /**
     * A failure occurred.
     *
     * @param PHPUnit_Framework_Test                 $test
     * @param PHPUnit_Framework_AssertionFailedError $e
     * @param float                                  $time
     */
    public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time)
    {
        $this->writeNotOk($test, 'Failure');

        $message = explode(
            "\n",
            PHPUnit_Framework_TestFailure::exceptionToString($e)
        );

        $diagnostic = [
          'message'  => $message[0],
          'severity' => 'fail'
        ];

        if ($e instanceof PHPUnit_Framework_ExpectationFailedException) {
            $cf = $e->getComparisonFailure();

            if ($cf !== null) {
                $diagnostic['data'] = [
                  'got'      => $cf->getActual(),
                  'expected' => $cf->getExpected()
                ];
            }
        }

        $yaml = new Symfony\Component\Yaml\Dumper;

        $this->write(
            sprintf(
                "  ---\n%s  ...\n",
                $yaml->dump($diagnostic, 2, 2)
            )
        );
    }

    /**
     * Incomplete test.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        $this->writeNotOk($test, '', 'TODO Incomplete Test');
    }

    /**
     * Risky test.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        $this->write(
            sprintf(
                "ok %d - # RISKY%s\n",
                $this->testNumber,
                $e->getMessage() != '' ? ' ' . $e->getMessage() : ''
            )
        );

        $this->testSuccessful = false;
    }

    /**
     * Skipped test.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        $this->write(
            sprintf(
                "ok %d - # SKIP%s\n",
                $this->testNumber,
                $e->getMessage() != '' ? ' ' . $e->getMessage() : ''
            )
        );

        $this->testSuccessful = false;
    }

    /**
     * A testsuite started.
     *
     * @param PHPUnit_Framework_TestSuite $suite
     */
    public function startTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
        $this->testSuiteLevel++;
    }

    /**
     * A testsuite ended.
     *
     * @param PHPUnit_Framework_TestSuite $suite
     */
    public function endTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
        $this->testSuiteLevel--;

        if ($this->testSuiteLevel == 0) {
            $this->write(sprintf("1..%d\n", $this->testNumber));
        }
    }

    /**
     * A test started.
     *
     * @param PHPUnit_Framework_Test $test
     */
    public function startTest(PHPUnit_Framework_Test $test)
    {
        $this->testNumber++;
        $this->testSuccessful = true;
    }

    /**
     * A test ended.
     *
     * @param PHPUnit_Framework_Test $test
     * @param float                  $time
     */
    public function endTest(PHPUnit_Framework_Test $test, $time)
    {
        if ($this->testSuccessful === true) {
            $this->write(
                sprintf(
                    "ok %d - %s\n",
                    $this->testNumber,
                    PHPUnit_Util_Test::describe($test)
                )
            );
        }

        $this->writeDiagnostics($test);
    }

    /**
     * @param PHPUnit_Framework_Test $test
     * @param string                 $prefix
     * @param string                 $directive
     */
    protected function writeNotOk(PHPUnit_Framework_Test $test, $prefix = '', $directive = '')
    {
        $this->write(
            sprintf(
                "not ok %d - %s%s%s\n",
                $this->testNumber,
                $prefix != '' ? $prefix . ': ' : '',
                PHPUnit_Util_Test::describe($test),
                $directive != '' ? ' # ' . $directive : ''
            )
        );

        $this->testSuccessful = false;
    }

    /**
     * @param PHPUnit_Framework_Test $test
     */
    private function writeDiagnostics(PHPUnit_Framework_Test $test)
    {
        if (!$test instanceof PHPUnit_Framework_TestCase) {
            return;
        }

        if (!$test->hasOutput()) {
            return;
        }

        foreach (explode("\n", trim($test->getActualOutput())) as $line) {
            $this->write(
                sprintf(
                    "# %s\n",
                    $line
                )
            );
        }
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A TestListener that generates a logfile of the test execution in XML markup.
 *
 * The XML markup used is the same as the one that is used by the JUnit Ant task.
 */
class PHPUnit_Util_Log_JUnit extends PHPUnit_Util_Printer implements PHPUnit_Framework_TestListener
{
    /**
     * @var DOMDocument
     */
    protected $document;

    /**
     * @var DOMElement
     */
    protected $root;

    /**
     * @var bool
     */
    protected $logIncompleteSkipped = false;

    /**
     * @var bool
     */
    protected $writeDocument = true;

    /**
     * @var DOMElement[]
     */
    protected $testSuites = [];

    /**
     * @var int[]
     */
    protected $testSuiteTests = [0];

    /**
     * @var int[]
     */
    protected $testSuiteAssertions = [0];

    /**
     * @var int[]
     */
    protected $testSuiteErrors = [0];

    /**
     * @var int[]
     */
    protected $testSuiteFailures = [0];

    /**
     * @var int[]
     */
    protected $testSuiteTimes = [0];

    /**
     * @var int
     */
    protected $testSuiteLevel = 0;

    /**
     * @var DOMElement
     */
    protected $currentTestCase = null;

    /**
     * @var bool
     */
    protected $attachCurrentTestCase = true;

    /**
     * Constructor.
     *
     * @param mixed $out
     * @param bool  $logIncompleteSkipped
     */
    public function __construct($out = null, $logIncompleteSkipped = false)
    {
        $this->document               = new DOMDocument('1.0', 'UTF-8');
        $this->document->formatOutput = true;

        $this->root = $this->document->createElement('testsuites');
        $this->document->appendChild($this->root);

        parent::__construct($out);

        $this->logIncompleteSkipped = $logIncompleteSkipped;
    }

    /**
     * Flush buffer and close output.
     */
    public function flush()
    {
        if ($this->writeDocument === true) {
            $this->write($this->getXML());
        }

        parent::flush();
    }

    /**
     * An error occurred.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addError(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        $this->doAddFault($test, $e, $time, 'error');
        $this->testSuiteErrors[$this->testSuiteLevel]++;
    }

    /**
     * A warning occurred.
     *
     * @param PHPUnit_Framework_Test    $test
     * @param PHPUnit_Framework_Warning $e
     * @param float                     $time
     */
    public function addWarning(PHPUnit_Framework_Test $test, PHPUnit_Framework_Warning $e, $time)
    {
        if (!$this->logIncompleteSkipped) {
            return;
        }

        $this->doAddFault($test, $e, $time, 'warning');
        $this->testSuiteFailures[$this->testSuiteLevel]++;
    }

    /**
     * A failure occurred.
     *
     * @param PHPUnit_Framework_Test                 $test
     * @param PHPUnit_Framework_AssertionFailedError $e
     * @param float                                  $time
     */
    public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time)
    {
        $this->doAddFault($test, $e, $time, 'failure');
        $this->testSuiteFailures[$this->testSuiteLevel]++;
    }

    /**
     * Incomplete test.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        if ($this->logIncompleteSkipped && $this->currentTestCase !== null) {
            $error = $this->document->createElement(
                'error',
                PHPUnit_Util_XML::prepareString(
                    "Incomplete Test\n" .
                    PHPUnit_Util_Filter::getFilteredStacktrace($e)
                )
            );

            $error->setAttribute('type', get_class($e));

            $this->currentTestCase->appendChild($error);

            $this->testSuiteErrors[$this->testSuiteLevel]++;
        } else {
            $this->attachCurrentTestCase = false;
        }
    }

    /**
     * Risky test.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        if ($this->logIncompleteSkipped && $this->currentTestCase !== null) {
            $error = $this->document->createElement(
                'error',
                PHPUnit_Util_XML::prepareString(
                    "Risky Test\n" .
                    PHPUnit_Util_Filter::getFilteredStacktrace($e)
                )
            );

            $error->setAttribute('type', get_class($e));

            $this->currentTestCase->appendChild($error);

            $this->testSuiteErrors[$this->testSuiteLevel]++;
        } else {
            $this->attachCurrentTestCase = false;
        }
    }

    /**
     * Skipped test.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        if ($this->logIncompleteSkipped && $this->currentTestCase !== null) {
            $error = $this->document->createElement(
                'error',
                PHPUnit_Util_XML::prepareString(
                    "Skipped Test\n" .
                    PHPUnit_Util_Filter::getFilteredStacktrace($e)
                )
            );

            $error->setAttribute('type', get_class($e));

            $this->currentTestCase->appendChild($error);

            $this->testSuiteErrors[$this->testSuiteLevel]++;
        } else {
            $this->attachCurrentTestCase = false;
        }
    }

    /**
     * A testsuite started.
     *
     * @param PHPUnit_Framework_TestSuite $suite
     */
    public function startTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
        $testSuite = $this->document->createElement('testsuite');
        $testSuite->setAttribute('name', $suite->getName());

        if (class_exists($suite->getName(), false)) {
            try {
                $class = new ReflectionClass($suite->getName());

                $testSuite->setAttribute('file', $class->getFileName());
            } catch (ReflectionException $e) {
            }
        }

        if ($this->testSuiteLevel > 0) {
            $this->testSuites[$this->testSuiteLevel]->appendChild($testSuite);
        } else {
            $this->root->appendChild($testSuite);
        }

        $this->testSuiteLevel++;
        $this->testSuites[$this->testSuiteLevel]          = $testSuite;
        $this->testSuiteTests[$this->testSuiteLevel]      = 0;
        $this->testSuiteAssertions[$this->testSuiteLevel] = 0;
        $this->testSuiteErrors[$this->testSuiteLevel]     = 0;
        $this->testSuiteFailures[$this->testSuiteLevel]   = 0;
        $this->testSuiteTimes[$this->testSuiteLevel]      = 0;
    }

    /**
     * A testsuite ended.
     *
     * @param PHPUnit_Framework_TestSuite $suite
     */
    public function endTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
        $this->testSuites[$this->testSuiteLevel]->setAttribute(
            'tests',
            $this->testSuiteTests[$this->testSuiteLevel]
        );

        $this->testSuites[$this->testSuiteLevel]->setAttribute(
            'assertions',
            $this->testSuiteAssertions[$this->testSuiteLevel]
        );

        $this->testSuites[$this->testSuiteLevel]->setAttribute(
            'failures',
            $this->testSuiteFailures[$this->testSuiteLevel]
        );

        $this->testSuites[$this->testSuiteLevel]->setAttribute(
            'errors',
            $this->testSuiteErrors[$this->testSuiteLevel]
        );

        $this->testSuites[$this->testSuiteLevel]->setAttribute(
            'time',
            sprintf('%F', $this->testSuiteTimes[$this->testSuiteLevel])
        );

        if ($this->testSuiteLevel > 1) {
            $this->testSuiteTests[$this->testSuiteLevel - 1]      += $this->testSuiteTests[$this->testSuiteLevel];
            $this->testSuiteAssertions[$this->testSuiteLevel - 1] += $this->testSuiteAssertions[$this->testSuiteLevel];
            $this->testSuiteErrors[$this->testSuiteLevel - 1]     += $this->testSuiteErrors[$this->testSuiteLevel];
            $this->testSuiteFailures[$this->testSuiteLevel - 1]   += $this->testSuiteFailures[$this->testSuiteLevel];
            $this->testSuiteTimes[$this->testSuiteLevel - 1]      += $this->testSuiteTimes[$this->testSuiteLevel];
        }

        $this->testSuiteLevel--;
    }

    /**
     * A test started.
     *
     * @param PHPUnit_Framework_Test $test
     */
    public function startTest(PHPUnit_Framework_Test $test)
    {
        $testCase = $this->document->createElement('testcase');
        $testCase->setAttribute('name', $test->getName());

        if ($test instanceof PHPUnit_Framework_TestCase) {
            $class      = new ReflectionClass($test);
            $methodName = $test->getName();

            if ($class->hasMethod($methodName)) {
                $method = $class->getMethod($test->getName());

                $testCase->setAttribute('class', $class->getName());
                $testCase->setAttribute('file', $class->getFileName());
                $testCase->setAttribute('line', $method->getStartLine());
            }
        }

        $this->currentTestCase = $testCase;
    }

    /**
     * A test ended.
     *
     * @param PHPUnit_Framework_Test $test
     * @param float                  $time
     */
    public function endTest(PHPUnit_Framework_Test $test, $time)
    {
        if ($this->attachCurrentTestCase) {
            if ($test instanceof PHPUnit_Framework_TestCase) {
                $numAssertions = $test->getNumAssertions();
                $this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions;

                $this->currentTestCase->setAttribute(
                    'assertions',
                    $numAssertions
                );
            }

            $this->currentTestCase->setAttribute(
                'time',
                sprintf('%F', $time)
            );

            $this->testSuites[$this->testSuiteLevel]->appendChild(
                $this->currentTestCase
            );

            $this->testSuiteTests[$this->testSuiteLevel]++;
            $this->testSuiteTimes[$this->testSuiteLevel] += $time;

            if (method_exists($test, 'hasOutput') && $test->hasOutput()) {
                $systemOut = $this->document->createElement('system-out');
                $systemOut->appendChild(
                    $this->document->createTextNode($test->getActualOutput())
                );
                $this->currentTestCase->appendChild($systemOut);
            }
        }

        $this->attachCurrentTestCase = true;
        $this->currentTestCase       = null;
    }

    /**
     * Returns the XML as a string.
     *
     * @return string
     */
    public function getXML()
    {
        return $this->document->saveXML();
    }

    /**
     * Enables or disables the writing of the document
     * in flush().
     *
     * This is a "hack" needed for the integration of
     * PHPUnit with Phing.
     *
     * @return string
     */
    public function setWriteDocument($flag)
    {
        if (is_bool($flag)) {
            $this->writeDocument = $flag;
        }
    }

    /**
     * Method which generalizes addError() and addFailure()
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     * @param string                 $type
     */
    private function doAddFault(PHPUnit_Framework_Test $test, Exception $e, $time, $type)
    {
        if ($this->currentTestCase === null) {
            return;
        }

        if ($test instanceof PHPUnit_Framework_SelfDescribing) {
            $buffer = $test->toString() . PHP_EOL;
        } else {
            $buffer = '';
        }

        $buffer .= PHPUnit_Framework_TestFailure::exceptionToString($e) . PHP_EOL .
                   PHPUnit_Util_Filter::getFilteredStacktrace($e);

        $fault = $this->document->createElement(
            $type,
            PHPUnit_Util_XML::prepareString($buffer)
        );

        if ($e instanceof PHPUnit_Framework_ExceptionWrapper) {
            $fault->setAttribute('type', $e->getClassName());
        } else {
            $fault->setAttribute('type', get_class($e));
        }

        $this->currentTestCase->appendChild($fault);
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

// Workaround for http://bugs.php.net/bug.php?id=47987,
// see https://github.com/sebastianbergmann/phpunit/issues#issue/125 for details
// Use dirname(__DIR__) instead of using /../ because of https://github.com/facebook/hhvm/issues/5215
require_once dirname(__DIR__) . '/Framework/Error.php';
require_once dirname(__DIR__) . '/Framework/Error/Notice.php';
require_once dirname(__DIR__) . '/Framework/Error/Warning.php';
require_once dirname(__DIR__) . '/Framework/Error/Deprecated.php';

/**
 * Error handler that converts PHP errors and warnings to exceptions.
 */
class PHPUnit_Util_ErrorHandler
{
    protected static $errorStack = [];

    /**
     * Returns the error stack.
     *
     * @return array
     */
    public static function getErrorStack()
    {
        return self::$errorStack;
    }

    /**
     * @param int    $errno
     * @param string $errstr
     * @param string $errfile
     * @param int    $errline
     *
     * @throws PHPUnit_Framework_Error
     */
    public static function handleError($errno, $errstr, $errfile, $errline)
    {
        if (!($errno & error_reporting())) {
            return false;
        }

        self::$errorStack[] = [$errno, $errstr, $errfile, $errline];

        $trace = debug_backtrace(false);
        array_shift($trace);

        foreach ($trace as $frame) {
            if ($frame['function'] == '__toString') {
                return false;
            }
        }

        if ($errno == E_NOTICE || $errno == E_USER_NOTICE || $errno == E_STRICT) {
            if (PHPUnit_Framework_Error_Notice::$enabled !== true) {
                return false;
            }

            $exception = 'PHPUnit_Framework_Error_Notice';
        } elseif ($errno == E_WARNING || $errno == E_USER_WARNING) {
            if (PHPUnit_Framework_Error_Warning::$enabled !== true) {
                return false;
            }

            $exception = 'PHPUnit_Framework_Error_Warning';
        } elseif ($errno == E_DEPRECATED || $errno == E_USER_DEPRECATED) {
            if (PHPUnit_Framework_Error_Deprecated::$enabled !== true) {
                return false;
            }

            $exception = 'PHPUnit_Framework_Error_Deprecated';
        } else {
            $exception = 'PHPUnit_Framework_Error';
        }

        throw new $exception($errstr, $errno, $errfile, $errline);
    }

    /**
     * Registers an error handler and returns a function that will restore
     * the previous handler when invoked
     *
     * @param int $severity PHP predefined error constant
     *
     * @throws Exception if event of specified severity is emitted
     */
    public static function handleErrorOnce($severity = E_WARNING)
    {
        $terminator = function () {
            static $expired = false;
            if (!$expired) {
                $expired = true;
                // cleans temporary error handler
                return restore_error_handler();
            }
        };

        set_error_handler(function ($errno, $errstr) use ($severity) {
            if ($errno === $severity) {
                return;
            }

            return false;
        });

        return $terminator;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use SebastianBergmann\Environment\Runtime;

/**
 * Utility methods for PHP sub-processes.
 */
abstract class PHPUnit_Util_PHP
{
    /**
     * @var Runtime
     */
    protected $runtime;

    /**
     * @var bool
     */
    protected $stderrRedirection = false;

    /**
     * @var string
     */
    protected $stdin = '';

    /**
     * @var string
     */
    protected $args = '';

    /**
     * @var array
     */
    protected $env = [];

    /**
     * @var int
     */
    protected $timeout = 0;

    /**
     * Creates internal Runtime instance.
     */
    public function __construct()
    {
        $this->runtime = new Runtime();
    }

    /**
     * Defines if should use STDERR redirection or not.
     *
     * Then $stderrRedirection is TRUE, STDERR is redirected to STDOUT.
     *
     * @throws PHPUnit_Framework_Exception
     *
     * @param bool $stderrRedirection
     */
    public function setUseStderrRedirection($stderrRedirection)
    {
        if (!is_bool($stderrRedirection)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');
        }

        $this->stderrRedirection = $stderrRedirection;
    }

    /**
     * Returns TRUE if uses STDERR redirection or FALSE if not.
     *
     * @return bool
     */
    public function useStderrRedirection()
    {
        return $this->stderrRedirection;
    }

    /**
     * Sets the input string to be sent via STDIN
     *
     * @param string $stdin
     */
    public function setStdin($stdin)
    {
        $this->stdin = (string) $stdin;
    }

    /**
     * Returns the input string to be sent via STDIN
     *
     * @return string
     */
    public function getStdin()
    {
        return $this->stdin;
    }

    /**
     * Sets the string of arguments to pass to the php job
     *
     * @param string $args
     */
    public function setArgs($args)
    {
        $this->args = (string) $args;
    }

    /**
     * Returns the string of arguments to pass to the php job
     *
     * @retrun string
     */
    public function getArgs()
    {
        return $this->args;
    }

    /**
     * Sets the array of environment variables to start the child process with
     *
     * @param array $env
     */
    public function setEnv(array $env)
    {
        $this->env = $env;
    }

    /**
     * Returns the array of environment variables to start the child process with
     *
     * @return array
     */
    public function getEnv()
    {
        return $this->env;
    }

    /**
     * Sets the amount of seconds to wait before timing out
     *
     * @param int $timeout
     */
    public function setTimeout($timeout)
    {
        $this->timeout = (int) $timeout;
    }

    /**
     * Returns the amount of seconds to wait before timing out
     *
     * @return int
     */
    public function getTimeout()
    {
        return $this->timeout;
    }

    /**
     * @return PHPUnit_Util_PHP
     */
    public static function factory()
    {
        if (DIRECTORY_SEPARATOR == '\\') {
            return new PHPUnit_Util_PHP_Windows;
        }

        return new PHPUnit_Util_PHP_Default;
    }

    /**
     * Runs a single test in a separate PHP process.
     *
     * @param string                       $job
     * @param PHPUnit_Framework_Test       $test
     * @param PHPUnit_Framework_TestResult $result
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function runTestJob($job, PHPUnit_Framework_Test $test, PHPUnit_Framework_TestResult $result)
    {
        $result->startTest($test);

        $_result = $this->runJob($job);

        $this->processChildResult(
            $test,
            $result,
            $_result['stdout'],
            $_result['stderr']
        );
    }

    /**
     * Returns the command based into the configurations.
     *
     * @param array       $settings
     * @param string|null $file
     *
     * @return string
     */
    public function getCommand(array $settings, $file = null)
    {
        $command = $this->runtime->getBinary();
        $command .= $this->settingsToParameters($settings);

        if ('phpdbg' === PHP_SAPI) {
            $command .= ' -qrr ';

            if ($file) {
                $command .= '-e ' . escapeshellarg($file);
            } else {
                $command .= escapeshellarg(__DIR__ . '/PHP/eval-stdin.php');
            }
        } elseif ($file) {
            $command .= ' -f ' . escapeshellarg($file);
        }

        if ($this->args) {
            $command .= ' -- ' . $this->args;
        }

        if (true === $this->stderrRedirection) {
            $command .= ' 2>&1';
        }

        return $command;
    }

    /**
     * Runs a single job (PHP code) using a separate PHP process.
     *
     * @param string $job
     * @param array  $settings
     *
     * @return array
     *
     * @throws PHPUnit_Framework_Exception
     */
    abstract public function runJob($job, array $settings = []);

    /**
     * @param array $settings
     *
     * @return string
     */
    protected function settingsToParameters(array $settings)
    {
        $buffer = '';

        foreach ($settings as $setting) {
            $buffer .= ' -d ' . $setting;
        }

        return $buffer;
    }

    /**
     * Processes the TestResult object from an isolated process.
     *
     * @param PHPUnit_Framework_Test       $test
     * @param PHPUnit_Framework_TestResult $result
     * @param string                       $stdout
     * @param string                       $stderr
     */
    private function processChildResult(PHPUnit_Framework_Test $test, PHPUnit_Framework_TestResult $result, $stdout, $stderr)
    {
        $time = 0;

        if (!empty($stderr)) {
            $result->addError(
                $test,
                new PHPUnit_Framework_Exception(trim($stderr)),
                $time
            );
        } else {
            set_error_handler(function ($errno, $errstr, $errfile, $errline) {
                throw new ErrorException($errstr, $errno, $errno, $errfile, $errline);
            });
            try {
                if (strpos($stdout, "#!/usr/bin/env php\n") === 0) {
                    $stdout = substr($stdout, 19);
                }

                $childResult = unserialize(str_replace("#!/usr/bin/env php\n", '', $stdout));
                restore_error_handler();
            } catch (ErrorException $e) {
                restore_error_handler();
                $childResult = false;

                $result->addError(
                    $test,
                    new PHPUnit_Framework_Exception(trim($stdout), 0, $e),
                    $time
                );
            }

            if ($childResult !== false) {
                if (!empty($childResult['output'])) {
                    $output = $childResult['output'];
                }

                $test->setResult($childResult['testResult']);
                $test->addToAssertionCount($childResult['numAssertions']);

                $childResult = $childResult['result'];
                /* @var $childResult PHPUnit_Framework_TestResult */

                if ($result->getCollectCodeCoverageInformation()) {
                    $result->getCodeCoverage()->merge(
                        $childResult->getCodeCoverage()
                    );
                }

                $time           = $childResult->time();
                $notImplemented = $childResult->notImplemented();
                $risky          = $childResult->risky();
                $skipped        = $childResult->skipped();
                $errors         = $childResult->errors();
                $warnings       = $childResult->warnings();
                $failures       = $childResult->failures();

                if (!empty($notImplemented)) {
                    $result->addError(
                        $test,
                        $this->getException($notImplemented[0]),
                        $time
                    );
                } elseif (!empty($risky)) {
                    $result->addError(
                        $test,
                        $this->getException($risky[0]),
                        $time
                    );
                } elseif (!empty($skipped)) {
                    $result->addError(
                        $test,
                        $this->getException($skipped[0]),
                        $time
                    );
                } elseif (!empty($errors)) {
                    $result->addError(
                        $test,
                        $this->getException($errors[0]),
                        $time
                    );
                } elseif (!empty($warnings)) {
                    $result->addWarning(
                        $test,
                        $this->getException($warnings[0]),
                        $time
                    );
                } elseif (!empty($failures)) {
                    $result->addFailure(
                        $test,
                        $this->getException($failures[0]),
                        $time
                    );
                }
            }
        }

        $result->endTest($test, $time);

        if (!empty($output)) {
            print $output;
        }
    }

    /**
     * Gets the thrown exception from a PHPUnit_Framework_TestFailure.
     *
     * @param PHPUnit_Framework_TestFailure $error
     *
     * @return Exception
     *
     * @see    https://github.com/sebastianbergmann/phpunit/issues/74
     */
    private function getException(PHPUnit_Framework_TestFailure $error)
    {
        $exception = $error->thrownException();

        if ($exception instanceof __PHP_Incomplete_Class) {
            $exceptionArray = [];
            foreach ((array) $exception as $key => $value) {
                $key                  = substr($key, strrpos($key, "\0") + 1);
                $exceptionArray[$key] = $value;
            }

            $exception = new PHPUnit_Framework_SyntheticError(
                sprintf(
                    '%s: %s',
                    $exceptionArray['_PHP_Incomplete_Class_Name'],
                    $exceptionArray['message']
                ),
                $exceptionArray['code'],
                $exceptionArray['file'],
                $exceptionArray['line'],
                $exceptionArray['trace']
            );
        }

        return $exception;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Utility class that can print to STDOUT or write to a file.
 */
class PHPUnit_Util_Printer
{
    /**
     * If true, flush output after every write.
     *
     * @var bool
     */
    protected $autoFlush = false;

    /**
     * @var resource
     */
    protected $out;

    /**
     * @var string
     */
    protected $outTarget;

    /**
     * Constructor.
     *
     * @param mixed $out
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function __construct($out = null)
    {
        if ($out !== null) {
            if (is_string($out)) {
                if (strpos($out, 'socket://') === 0) {
                    $out = explode(':', str_replace('socket://', '', $out));

                    if (count($out) != 2) {
                        throw new PHPUnit_Framework_Exception;
                    }

                    $this->out = fsockopen($out[0], $out[1]);
                } else {
                    if (strpos($out, 'php://') === false &&
                        !is_dir(dirname($out))) {
                        mkdir(dirname($out), 0777, true);
                    }

                    $this->out = fopen($out, 'wt');
                }

                $this->outTarget = $out;
            } else {
                $this->out = $out;
            }
        }
    }

    /**
     * Flush buffer and close output if it's not to a PHP stream
     */
    public function flush()
    {
        if ($this->out && strncmp($this->outTarget, 'php://', 6) !== 0) {
            fclose($this->out);
        }
    }

    /**
     * Performs a safe, incremental flush.
     *
     * Do not confuse this function with the flush() function of this class,
     * since the flush() function may close the file being written to, rendering
     * the current object no longer usable.
     */
    public function incrementalFlush()
    {
        if ($this->out) {
            fflush($this->out);
        } else {
            flush();
        }
    }

    /**
     * @param string $buffer
     */
    public function write($buffer)
    {
        if ($this->out) {
            fwrite($this->out, $buffer);

            if ($this->autoFlush) {
                $this->incrementalFlush();
            }
        } else {
            if (PHP_SAPI != 'cli' && PHP_SAPI != 'phpdbg') {
                $buffer = htmlspecialchars($buffer, ENT_SUBSTITUTE);
            }

            print $buffer;

            if ($this->autoFlush) {
                $this->incrementalFlush();
            }
        }
    }

    /**
     * Check auto-flush mode.
     *
     * @return bool
     */
    public function getAutoFlush()
    {
        return $this->autoFlush;
    }

    /**
     * Set auto-flushing mode.
     *
     * If set, *incremental* flushes will be done after each write. This should
     * not be confused with the different effects of this class' flush() method.
     *
     * @param bool $autoFlush
     */
    public function setAutoFlush($autoFlush)
    {
        if (is_bool($autoFlush)) {
            $this->autoFlush = $autoFlush;
        } else {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');
        }
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A Listener for test progress.
 */
interface PHPUnit_Framework_TestListener
{
    /**
     * An error occurred.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addError(PHPUnit_Framework_Test $test, Exception $e, $time);

    /**
     * A warning occurred.
     *
     * @param PHPUnit_Framework_Test    $test
     * @param PHPUnit_Framework_Warning $e
     * @param float                     $time
     *
     * @todo  Uncomment in time for PHPUnit 6.0.0
     *
     * @see   https://github.com/sebastianbergmann/phpunit/pull/1840#issuecomment-162535997
     */
//  public function addWarning(PHPUnit_Framework_Test $test, PHPUnit_Framework_Warning $e, $time);

    /**
     * A failure occurred.
     *
     * @param PHPUnit_Framework_Test                 $test
     * @param PHPUnit_Framework_AssertionFailedError $e
     * @param float                                  $time
     */
    public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time);

    /**
     * Incomplete test.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time);

    /**
     * Risky test.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time);

    /**
     * Skipped test.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time);

    /**
     * A test suite started.
     *
     * @param PHPUnit_Framework_TestSuite $suite
     */
    public function startTestSuite(PHPUnit_Framework_TestSuite $suite);

    /**
     * A test suite ended.
     *
     * @param PHPUnit_Framework_TestSuite $suite
     */
    public function endTestSuite(PHPUnit_Framework_TestSuite $suite);

    /**
     * A test started.
     *
     * @param PHPUnit_Framework_Test $test
     */
    public function startTest(PHPUnit_Framework_Test $test);

    /**
     * A test ended.
     *
     * @param PHPUnit_Framework_Test $test
     * @param float                  $time
     */
    public function endTest(PHPUnit_Framework_Test $test, $time);
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Thrown when there is a warning.
 */
class PHPUnit_Framework_Warning extends PHPUnit_Framework_Exception implements PHPUnit_Framework_SelfDescribing
{
    /**
     * Wrapper for getMessage() which is declared as final.
     *
     * @return string
     */
    public function toString()
    {
        return $this->getMessage();
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Extension to PHPUnit_Framework_AssertionFailedError to mark the special
 * case of a test that unintentionally covers code.
 */
class PHPUnit_Framework_UnintentionallyCoveredCodeError extends PHPUnit_Framework_RiskyTestError
{
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Exception for expectations which failed their check.
 *
 * The exception contains the error message and optionally a
 * SebastianBergmann\Comparator\ComparisonFailure which is used to
 * generate diff output of the failed expectations.
 */
class PHPUnit_Framework_ExpectationFailedException extends PHPUnit_Framework_AssertionFailedError
{
    /**
     * @var SebastianBergmann\Comparator\ComparisonFailure
     */
    protected $comparisonFailure;

    public function __construct($message, SebastianBergmann\Comparator\ComparisonFailure $comparisonFailure = null, Exception $previous = null)
    {
        $this->comparisonFailure = $comparisonFailure;

        parent::__construct($message, 0, $previous);
    }

    /**
     * @return SebastianBergmann\Comparator\ComparisonFailure
     */
    public function getComparisonFailure()
    {
        return $this->comparisonFailure;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\Exception as CodeCoverageException;
use SebastianBergmann\CodeCoverage\CoveredCodeNotExecutedException;
use SebastianBergmann\CodeCoverage\MissingCoversAnnotationException;
use SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException;
use SebastianBergmann\ResourceOperations\ResourceOperations;

/**
 * A TestResult collects the results of executing a test case.
 */
class PHPUnit_Framework_TestResult implements Countable
{
    /**
     * @var array
     */
    protected $passed = [];

    /**
     * @var array
     */
    protected $errors = [];

    /**
     * @var array
     */
    protected $failures = [];

    /**
     * @var array
     */
    protected $warnings = [];

    /**
     * @var array
     */
    protected $notImplemented = [];

    /**
     * @var array
     */
    protected $risky = [];

    /**
     * @var array
     */
    protected $skipped = [];

    /**
     * @var array
     */
    protected $listeners = [];

    /**
     * @var int
     */
    protected $runTests = 0;

    /**
     * @var float
     */
    protected $time = 0;

    /**
     * @var PHPUnit_Framework_TestSuite
     */
    protected $topTestSuite = null;

    /**
     * Code Coverage information.
     *
     * @var CodeCoverage
     */
    protected $codeCoverage;

    /**
     * @var bool
     */
    protected $convertErrorsToExceptions = true;

    /**
     * @var bool
     */
    protected $stop = false;

    /**
     * @var bool
     */
    protected $stopOnError = false;

    /**
     * @var bool
     */
    protected $stopOnFailure = false;

    /**
     * @var bool
     */
    protected $stopOnWarning = false;

    /**
     * @var bool
     */
    protected $beStrictAboutTestsThatDoNotTestAnything = false;

    /**
     * @var bool
     */
    protected $beStrictAboutOutputDuringTests = false;

    /**
     * @var bool
     */
    protected $beStrictAboutTodoAnnotatedTests = false;

    /**
     * @var bool
     */
    protected $beStrictAboutResourceUsageDuringSmallTests = false;

    /**
     * @var bool
     */
    protected $enforceTimeLimit = false;

    /**
     * @var int
     */
    protected $timeoutForSmallTests = 1;

    /**
     * @var int
     */
    protected $timeoutForMediumTests = 10;

    /**
     * @var int
     */
    protected $timeoutForLargeTests = 60;

    /**
     * @var bool
     */
    protected $stopOnRisky = false;

    /**
     * @var bool
     */
    protected $stopOnIncomplete = false;

    /**
     * @var bool
     */
    protected $stopOnSkipped = false;

    /**
     * @var bool
     */
    protected $lastTestFailed = false;

    /**
     * @var bool
     */
    private $registerMockObjectsFromTestArgumentsRecursively = false;

    /**
     * Registers a TestListener.
     *
     * @param  PHPUnit_Framework_TestListener
     */
    public function addListener(PHPUnit_Framework_TestListener $listener)
    {
        $this->listeners[] = $listener;
    }

    /**
     * Unregisters a TestListener.
     *
     * @param PHPUnit_Framework_TestListener $listener
     */
    public function removeListener(PHPUnit_Framework_TestListener $listener)
    {
        foreach ($this->listeners as $key => $_listener) {
            if ($listener === $_listener) {
                unset($this->listeners[$key]);
            }
        }
    }

    /**
     * Flushes all flushable TestListeners.
     */
    public function flushListeners()
    {
        foreach ($this->listeners as $listener) {
            if ($listener instanceof PHPUnit_Util_Printer) {
                $listener->flush();
            }
        }
    }

    /**
     * Adds an error to the list of errors.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Throwable              $t
     * @param float                  $time
     */
    public function addError(PHPUnit_Framework_Test $test, $t, $time)
    {
        if ($t instanceof PHPUnit_Framework_RiskyTest) {
            $this->risky[] = new PHPUnit_Framework_TestFailure($test, $t);
            $notifyMethod  = 'addRiskyTest';

            if ($test instanceof PHPUnit_Framework_TestCase) {
                $test->markAsRisky();
            }

            if ($this->stopOnRisky) {
                $this->stop();
            }
        } elseif ($t instanceof PHPUnit_Framework_IncompleteTest) {
            $this->notImplemented[] = new PHPUnit_Framework_TestFailure($test, $t);
            $notifyMethod           = 'addIncompleteTest';

            if ($this->stopOnIncomplete) {
                $this->stop();
            }
        } elseif ($t instanceof PHPUnit_Framework_SkippedTest) {
            $this->skipped[] = new PHPUnit_Framework_TestFailure($test, $t);
            $notifyMethod    = 'addSkippedTest';

            if ($this->stopOnSkipped) {
                $this->stop();
            }
        } else {
            $this->errors[] = new PHPUnit_Framework_TestFailure($test, $t);
            $notifyMethod   = 'addError';

            if ($this->stopOnError || $this->stopOnFailure) {
                $this->stop();
            }
        }

        // @see https://github.com/sebastianbergmann/phpunit/issues/1953
        if ($t instanceof Error) {
            $t = new PHPUnit_Framework_ExceptionWrapper($t);
        }

        foreach ($this->listeners as $listener) {
            $listener->$notifyMethod($test, $t, $time);
        }

        $this->lastTestFailed = true;
        $this->time          += $time;
    }

    /**
     * Adds a warning to the list of warnings.
     * The passed in exception caused the warning.
     *
     * @param PHPUnit_Framework_Test    $test
     * @param PHPUnit_Framework_Warning $e
     * @param float                     $time
     */
    public function addWarning(PHPUnit_Framework_Test $test, PHPUnit_Framework_Warning $e, $time)
    {
        if ($this->stopOnWarning) {
            $this->stop();
        }

        $this->warnings[] = new PHPUnit_Framework_TestFailure($test, $e);

        foreach ($this->listeners as $listener) {
            // @todo Remove check for PHPUnit 6.0.0
            // @see  https://github.com/sebastianbergmann/phpunit/pull/1840#issuecomment-162535997
            if (method_exists($listener, 'addWarning')) {
                $listener->addWarning($test, $e, $time);
            }
        }

        $this->time += $time;
    }

    /**
     * Adds a failure to the list of failures.
     * The passed in exception caused the failure.
     *
     * @param PHPUnit_Framework_Test                 $test
     * @param PHPUnit_Framework_AssertionFailedError $e
     * @param float                                  $time
     */
    public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time)
    {
        if ($e instanceof PHPUnit_Framework_RiskyTest ||
            $e instanceof PHPUnit_Framework_OutputError) {
            $this->risky[] = new PHPUnit_Framework_TestFailure($test, $e);
            $notifyMethod  = 'addRiskyTest';

            if ($test instanceof PHPUnit_Framework_TestCase) {
                $test->markAsRisky();
            }

            if ($this->stopOnRisky) {
                $this->stop();
            }
        } elseif ($e instanceof PHPUnit_Framework_IncompleteTest) {
            $this->notImplemented[] = new PHPUnit_Framework_TestFailure($test, $e);
            $notifyMethod           = 'addIncompleteTest';

            if ($this->stopOnIncomplete) {
                $this->stop();
            }
        } elseif ($e instanceof PHPUnit_Framework_SkippedTest) {
            $this->skipped[] = new PHPUnit_Framework_TestFailure($test, $e);
            $notifyMethod    = 'addSkippedTest';

            if ($this->stopOnSkipped) {
                $this->stop();
            }
        } else {
            $this->failures[] = new PHPUnit_Framework_TestFailure($test, $e);
            $notifyMethod     = 'addFailure';

            if ($this->stopOnFailure) {
                $this->stop();
            }
        }

        foreach ($this->listeners as $listener) {
            $listener->$notifyMethod($test, $e, $time);
        }

        $this->lastTestFailed = true;
        $this->time          += $time;
    }

    /**
     * Informs the result that a testsuite will be started.
     *
     * @param PHPUnit_Framework_TestSuite $suite
     */
    public function startTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
        if ($this->topTestSuite === null) {
            $this->topTestSuite = $suite;
        }

        foreach ($this->listeners as $listener) {
            $listener->startTestSuite($suite);
        }
    }

    /**
     * Informs the result that a testsuite was completed.
     *
     * @param PHPUnit_Framework_TestSuite $suite
     */
    public function endTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
        foreach ($this->listeners as $listener) {
            $listener->endTestSuite($suite);
        }
    }

    /**
     * Informs the result that a test will be started.
     *
     * @param PHPUnit_Framework_Test $test
     */
    public function startTest(PHPUnit_Framework_Test $test)
    {
        $this->lastTestFailed = false;
        $this->runTests      += count($test);

        foreach ($this->listeners as $listener) {
            $listener->startTest($test);
        }
    }

    /**
     * Informs the result that a test was completed.
     *
     * @param PHPUnit_Framework_Test $test
     * @param float                  $time
     */
    public function endTest(PHPUnit_Framework_Test $test, $time)
    {
        foreach ($this->listeners as $listener) {
            $listener->endTest($test, $time);
        }

        if (!$this->lastTestFailed && $test instanceof PHPUnit_Framework_TestCase) {
            $class  = get_class($test);
            $key    = $class . '::' . $test->getName();

            $this->passed[$key] = [
                'result' => $test->getResult(),
                'size'   => PHPUnit_Util_Test::getSize(
                    $class,
                    $test->getName(false)
                )
            ];

            $this->time += $time;
        }
    }

    /**
     * Returns true if no risky test occurred.
     *
     * @return bool
     */
    public function allHarmless()
    {
        return $this->riskyCount() == 0;
    }

    /**
     * Gets the number of risky tests.
     *
     * @return int
     */
    public function riskyCount()
    {
        return count($this->risky);
    }

    /**
     * Returns true if no incomplete test occurred.
     *
     * @return bool
     */
    public function allCompletelyImplemented()
    {
        return $this->notImplementedCount() == 0;
    }

    /**
     * Gets the number of incomplete tests.
     *
     * @return int
     */
    public function notImplementedCount()
    {
        return count($this->notImplemented);
    }

    /**
     * Returns an Enumeration for the risky tests.
     *
     * @return array
     */
    public function risky()
    {
        return $this->risky;
    }

    /**
     * Returns an Enumeration for the incomplete tests.
     *
     * @return array
     */
    public function notImplemented()
    {
        return $this->notImplemented;
    }

    /**
     * Returns true if no test has been skipped.
     *
     * @return bool
     */
    public function noneSkipped()
    {
        return $this->skippedCount() == 0;
    }

    /**
     * Gets the number of skipped tests.
     *
     * @return int
     */
    public function skippedCount()
    {
        return count($this->skipped);
    }

    /**
     * Returns an Enumeration for the skipped tests.
     *
     * @return array
     */
    public function skipped()
    {
        return $this->skipped;
    }

    /**
     * Gets the number of detected errors.
     *
     * @return int
     */
    public function errorCount()
    {
        return count($this->errors);
    }

    /**
     * Returns an Enumeration for the errors.
     *
     * @return array
     */
    public function errors()
    {
        return $this->errors;
    }

    /**
     * Gets the number of detected failures.
     *
     * @return int
     */
    public function failureCount()
    {
        return count($this->failures);
    }

    /**
     * Returns an Enumeration for the failures.
     *
     * @return array
     */
    public function failures()
    {
        return $this->failures;
    }

    /**
     * Gets the number of detected warnings.
     *
     * @return int
     */
    public function warningCount()
    {
        return count($this->warnings);
    }

    /**
     * Returns an Enumeration for the warnings.
     *
     * @return array
     */
    public function warnings()
    {
        return $this->warnings;
    }

    /**
     * Returns the names of the tests that have passed.
     *
     * @return array
     */
    public function passed()
    {
        return $this->passed;
    }

    /**
     * Returns the (top) test suite.
     *
     * @return PHPUnit_Framework_TestSuite
     */
    public function topTestSuite()
    {
        return $this->topTestSuite;
    }

    /**
     * Returns whether code coverage information should be collected.
     *
     * @return bool If code coverage should be collected
     */
    public function getCollectCodeCoverageInformation()
    {
        return $this->codeCoverage !== null;
    }

    /**
     * Runs a TestCase.
     *
     * @param PHPUnit_Framework_Test $test
     */
    public function run(PHPUnit_Framework_Test $test)
    {
        PHPUnit_Framework_Assert::resetCount();

        $coversNothing = false;

        if ($test instanceof PHPUnit_Framework_TestCase) {
            $test->setRegisterMockObjectsFromTestArgumentsRecursively(
                $this->registerMockObjectsFromTestArgumentsRecursively
            );

            $annotations = $test->getAnnotations();

            if (isset($annotations['class']['coversNothing']) || isset($annotations['method']['coversNothing'])) {
                $coversNothing = true;
            }
        }

        $error      = false;
        $failure    = false;
        $warning    = false;
        $incomplete = false;
        $risky      = false;
        $skipped    = false;

        $this->startTest($test);

        $errorHandlerSet = false;

        if ($this->convertErrorsToExceptions) {
            $oldErrorHandler = set_error_handler(
                ['PHPUnit_Util_ErrorHandler', 'handleError'],
                E_ALL | E_STRICT
            );

            if ($oldErrorHandler === null) {
                $errorHandlerSet = true;
            } else {
                restore_error_handler();
            }
        }

        $collectCodeCoverage = $this->codeCoverage !== null &&
                               !$test instanceof PHPUnit_Framework_WarningTestCase &&
                               !$coversNothing;

        if ($collectCodeCoverage) {
            $this->codeCoverage->start($test);
        }

        $monitorFunctions = $this->beStrictAboutResourceUsageDuringSmallTests &&
                            !$test instanceof PHPUnit_Framework_WarningTestCase &&
                            $test->getSize() == PHPUnit_Util_Test::SMALL &&
                            function_exists('xdebug_start_function_monitor');

        if ($monitorFunctions) {
            xdebug_start_function_monitor(ResourceOperations::getFunctions());
        }

        PHP_Timer::start();

        try {
            if (!$test instanceof PHPUnit_Framework_WarningTestCase &&
                $test->getSize() != PHPUnit_Util_Test::UNKNOWN &&
                $this->enforceTimeLimit &&
                extension_loaded('pcntl') && class_exists('PHP_Invoker')) {
                switch ($test->getSize()) {
                    case PHPUnit_Util_Test::SMALL:
                        $_timeout = $this->timeoutForSmallTests;
                        break;

                    case PHPUnit_Util_Test::MEDIUM:
                        $_timeout = $this->timeoutForMediumTests;
                        break;

                    case PHPUnit_Util_Test::LARGE:
                        $_timeout = $this->timeoutForLargeTests;
                        break;
                }

                $invoker = new PHP_Invoker;
                $invoker->invoke([$test, 'runBare'], [], $_timeout);
            } else {
                $test->runBare();
            }
        } catch (PHP_Invoker_TimeoutException $e) {
            $this->addFailure(
                $test,
                new PHPUnit_Framework_RiskyTestError(
                    $e->getMessage()
                ),
                $_timeout
            );

            $risky = true;
        } catch (PHPUnit_Framework_MockObject_Exception $e) {
            $e = new PHPUnit_Framework_Warning(
                $e->getMessage()
            );

            $warning = true;
        } catch (PHPUnit_Framework_AssertionFailedError $e) {
            $failure = true;

            if ($e instanceof PHPUnit_Framework_RiskyTestError) {
                $risky = true;
            } elseif ($e instanceof PHPUnit_Framework_IncompleteTestError) {
                $incomplete = true;
            } elseif ($e instanceof PHPUnit_Framework_SkippedTestError) {
                $skipped = true;
            }
        } catch (PHPUnit_Framework_Warning $e) {
            $warning = true;
        } catch (PHPUnit_Framework_Exception $e) {
            $error = true;
        } catch (Throwable $e) {
            // @see https://github.com/sebastianbergmann/phpunit/issues/2394
            if (PHP_MAJOR_VERSION === 7 && $e instanceof \AssertionError) {
                $test->addToAssertionCount(1);

                $failure = true;
                $frame   = $e->getTrace()[0];

                $e = new PHPUnit_Framework_AssertionFailedError(
                    sprintf(
                        '%s in %s:%s',
                        $e->getMessage(),
                        $frame['file'],
                        $frame['line']
                    )
                );
            } else {
                $e     = new PHPUnit_Framework_ExceptionWrapper($e);
                $error = true;
            }
        } catch (Exception $e) {
            $e     = new PHPUnit_Framework_ExceptionWrapper($e);
            $error = true;
        }

        $time = PHP_Timer::stop();
        $test->addToAssertionCount(PHPUnit_Framework_Assert::getCount());

        if ($monitorFunctions) {
            $blacklist = new PHPUnit_Util_Blacklist;
            $functions = xdebug_get_monitored_functions();
            xdebug_stop_function_monitor();

            foreach ($functions as $function) {
                if (!$blacklist->isBlacklisted($function['filename'])) {
                    $this->addFailure(
                        $test,
                        new PHPUnit_Framework_RiskyTestError(
                            sprintf(
                                '%s() used in %s:%s',
                                $function['function'],
                                $function['filename'],
                                $function['lineno']
                            )
                        ),
                        $time
                    );
                }
            }
        }

        if ($this->beStrictAboutTestsThatDoNotTestAnything &&
            $test->getNumAssertions() == 0) {
            $risky = true;
        }

        if ($collectCodeCoverage) {
            $append           = !$risky && !$incomplete && !$skipped;
            $linesToBeCovered = [];
            $linesToBeUsed    = [];

            if ($append && $test instanceof PHPUnit_Framework_TestCase) {
                try {
                    $linesToBeCovered = PHPUnit_Util_Test::getLinesToBeCovered(
                        get_class($test),
                        $test->getName(false)
                    );

                    $linesToBeUsed = PHPUnit_Util_Test::getLinesToBeUsed(
                        get_class($test),
                        $test->getName(false)
                    );
                } catch (PHPUnit_Framework_InvalidCoversTargetException $cce) {
                    $this->addWarning(
                        $test,
                        new PHPUnit_Framework_Warning(
                            $cce->getMessage()
                        ),
                        $time
                    );
                }
            }

            try {
                $this->codeCoverage->stop(
                    $append,
                    $linesToBeCovered,
                    $linesToBeUsed
                );
            } catch (UnintentionallyCoveredCodeException $cce) {
                $this->addFailure(
                    $test,
                    new PHPUnit_Framework_UnintentionallyCoveredCodeError(
                        'This test executed code that is not listed as code to be covered or used:' .
                        PHP_EOL . $cce->getMessage()
                    ),
                    $time
                );
            } catch (CoveredCodeNotExecutedException $cce) {
                $this->addFailure(
                    $test,
                    new PHPUnit_Framework_CoveredCodeNotExecutedException(
                        'This test did not execute all the code that is listed as code to be covered:' .
                        PHP_EOL . $cce->getMessage()
                    ),
                    $time
                );
            } catch (MissingCoversAnnotationException $cce) {
                if ($linesToBeCovered !== false) {
                    $this->addFailure(
                        $test,
                        new PHPUnit_Framework_MissingCoversAnnotationException(
                            'This test does not have a @covers annotation but is expected to have one'
                        ),
                        $time
                    );
                }
            } catch (CodeCoverageException $cce) {
                $error = true;

                if (!isset($e)) {
                    $e = $cce;
                }
            }
        }

        if ($errorHandlerSet === true) {
            restore_error_handler();
        }

        if ($error === true) {
            $this->addError($test, $e, $time);
        } elseif ($failure === true) {
            $this->addFailure($test, $e, $time);
        } elseif ($warning === true) {
            $this->addWarning($test, $e, $time);
        } elseif ($this->beStrictAboutTestsThatDoNotTestAnything &&
                  !$test->doesNotPerformAssertions() &&
                  $test->getNumAssertions() == 0) {
            $this->addFailure(
                $test,
                new PHPUnit_Framework_RiskyTestError(
                    'This test did not perform any assertions'
                ),
                $time
            );
        } elseif ($this->beStrictAboutOutputDuringTests && $test->hasOutput()) {
            $this->addFailure(
                $test,
                new PHPUnit_Framework_OutputError(
                    sprintf(
                        'This test printed output: %s',
                        $test->getActualOutput()
                    )
                ),
                $time
            );
        } elseif ($this->beStrictAboutTodoAnnotatedTests && $test instanceof PHPUnit_Framework_TestCase) {
            $annotations = $test->getAnnotations();

            if (isset($annotations['method']['todo'])) {
                $this->addFailure(
                    $test,
                    new PHPUnit_Framework_RiskyTestError(
                        'Test method is annotated with @todo'
                    ),
                    $time
                );
            }
        }

        $this->endTest($test, $time);
    }

    /**
     * Gets the number of run tests.
     *
     * @return int
     */
    public function count()
    {
        return $this->runTests;
    }

    /**
     * Checks whether the test run should stop.
     *
     * @return bool
     */
    public function shouldStop()
    {
        return $this->stop;
    }

    /**
     * Marks that the test run should stop.
     */
    public function stop()
    {
        $this->stop = true;
    }

    /**
     * Returns the code coverage object.
     *
     * @return CodeCoverage
     */
    public function getCodeCoverage()
    {
        return $this->codeCoverage;
    }

    /**
     * Sets the code coverage object.
     *
     * @param CodeCoverage $codeCoverage
     */
    public function setCodeCoverage(CodeCoverage $codeCoverage)
    {
        $this->codeCoverage = $codeCoverage;
    }

    /**
     * Enables or disables the error-to-exception conversion.
     *
     * @param bool $flag
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function convertErrorsToExceptions($flag)
    {
        if (!is_bool($flag)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');
        }

        $this->convertErrorsToExceptions = $flag;
    }

    /**
     * Returns the error-to-exception conversion setting.
     *
     * @return bool
     */
    public function getConvertErrorsToExceptions()
    {
        return $this->convertErrorsToExceptions;
    }

    /**
     * Enables or disables the stopping when an error occurs.
     *
     * @param bool $flag
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function stopOnError($flag)
    {
        if (!is_bool($flag)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');
        }

        $this->stopOnError = $flag;
    }

    /**
     * Enables or disables the stopping when a failure occurs.
     *
     * @param bool $flag
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function stopOnFailure($flag)
    {
        if (!is_bool($flag)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');
        }

        $this->stopOnFailure = $flag;
    }

    /**
     * Enables or disables the stopping when a warning occurs.
     *
     * @param bool $flag
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function stopOnWarning($flag)
    {
        if (!is_bool($flag)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');
        }

        $this->stopOnWarning = $flag;
    }

    /**
     * @param bool $flag
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function beStrictAboutTestsThatDoNotTestAnything($flag)
    {
        if (!is_bool($flag)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');
        }

        $this->beStrictAboutTestsThatDoNotTestAnything = $flag;
    }

    /**
     * @return bool
     */
    public function isStrictAboutTestsThatDoNotTestAnything()
    {
        return $this->beStrictAboutTestsThatDoNotTestAnything;
    }

    /**
     * @param bool $flag
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function beStrictAboutOutputDuringTests($flag)
    {
        if (!is_bool($flag)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');
        }

        $this->beStrictAboutOutputDuringTests = $flag;
    }

    /**
     * @return bool
     */
    public function isStrictAboutOutputDuringTests()
    {
        return $this->beStrictAboutOutputDuringTests;
    }

    /**
     * @param bool $flag
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function beStrictAboutResourceUsageDuringSmallTests($flag)
    {
        if (!is_bool($flag)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');
        }

        $this->beStrictAboutResourceUsageDuringSmallTests = $flag;
    }

    /**
     * @return bool
     */
    public function isStrictAboutResourceUsageDuringSmallTests()
    {
        return $this->beStrictAboutResourceUsageDuringSmallTests;
    }

    /**
     * @param bool $flag
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function enforceTimeLimit($flag)
    {
        if (!is_bool($flag)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');
        }

        $this->enforceTimeLimit = $flag;
    }

    /**
     * @return bool
     */
    public function enforcesTimeLimit()
    {
        return $this->enforceTimeLimit;
    }

    /**
     * @param bool $flag
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function beStrictAboutTodoAnnotatedTests($flag)
    {
        if (!is_bool($flag)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');
        }

        $this->beStrictAboutTodoAnnotatedTests = $flag;
    }

    /**
     * @return bool
     */
    public function isStrictAboutTodoAnnotatedTests()
    {
        return $this->beStrictAboutTodoAnnotatedTests;
    }

    /**
     * Enables or disables the stopping for risky tests.
     *
     * @param bool $flag
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function stopOnRisky($flag)
    {
        if (!is_bool($flag)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');
        }

        $this->stopOnRisky = $flag;
    }

    /**
     * Enables or disables the stopping for incomplete tests.
     *
     * @param bool $flag
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function stopOnIncomplete($flag)
    {
        if (!is_bool($flag)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');
        }

        $this->stopOnIncomplete = $flag;
    }

    /**
     * Enables or disables the stopping for skipped tests.
     *
     * @param bool $flag
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function stopOnSkipped($flag)
    {
        if (!is_bool($flag)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');
        }

        $this->stopOnSkipped = $flag;
    }

    /**
     * Returns the time spent running the tests.
     *
     * @return float
     */
    public function time()
    {
        return $this->time;
    }

    /**
     * Returns whether the entire test was successful or not.
     *
     * @param bool $includeWarnings
     *
     * @return bool
     */
    public function wasSuccessful($includeWarnings = true)
    {
        if ($includeWarnings) {
            return empty($this->errors) && empty($this->failures) && empty($this->warnings);
        } else {
            return empty($this->errors) && empty($this->failures);
        }
    }

    /**
     * Sets the timeout for small tests.
     *
     * @param int $timeout
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function setTimeoutForSmallTests($timeout)
    {
        if (!is_int($timeout)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'integer');
        }

        $this->timeoutForSmallTests = $timeout;
    }

    /**
     * Sets the timeout for medium tests.
     *
     * @param int $timeout
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function setTimeoutForMediumTests($timeout)
    {
        if (!is_int($timeout)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'integer');
        }

        $this->timeoutForMediumTests = $timeout;
    }

    /**
     * Sets the timeout for large tests.
     *
     * @param int $timeout
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function setTimeoutForLargeTests($timeout)
    {
        if (!is_int($timeout)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'integer');
        }

        $this->timeoutForLargeTests = $timeout;
    }

    /**
     * Returns the set timeout for large tests.
     *
     * @return int
     */
    public function getTimeoutForLargeTests()
    {
        return $this->timeoutForLargeTests;
    }

    /**
     * @param bool $flag
     */
    public function setRegisterMockObjectsFromTestArgumentsRecursively($flag)
    {
        if (!is_bool($flag)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');
        }

        $this->registerMockObjectsFromTestArgumentsRecursively = $flag;
    }

    /**
     * Returns the class hierarchy for a given class.
     *
     * @param string $className
     * @param bool   $asReflectionObjects
     *
     * @return array
     */
    protected function getHierarchy($className, $asReflectionObjects = false)
    {
        if ($asReflectionObjects) {
            $classes = [new ReflectionClass($className)];
        } else {
            $classes = [$className];
        }

        $done = false;

        while (!$done) {
            if ($asReflectionObjects) {
                $class = new ReflectionClass(
                    $classes[count($classes) - 1]->getName()
                );
            } else {
                $class = new ReflectionClass($classes[count($classes) - 1]);
            }

            $parent = $class->getParentClass();

            if ($parent !== false) {
                if ($asReflectionObjects) {
                    $classes[] = $parent;
                } else {
                    $classes[] = $parent->getName();
                }
            } else {
                $done = true;
            }
        }

        return $classes;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Extension to PHPUnit_Framework_AssertionFailedError to mark the special
 * case of a skipped test suite.
 */
class PHPUnit_Framework_SkippedTestSuiteError extends PHPUnit_Framework_AssertionFailedError implements PHPUnit_Framework_SkippedTest
{
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * An empty Listener that can be extended to implement TestListener
 * with just a few lines of code.
 *
 * @see PHPUnit_Framework_TestListener for documentation on the API methods.
 */
abstract class PHPUnit_Framework_BaseTestListener implements PHPUnit_Framework_TestListener
{
    public function addError(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
    }

    public function addWarning(PHPUnit_Framework_Test $test, PHPUnit_Framework_Warning $e, $time)
    {
    }

    public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time)
    {
    }

    public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
    }

    public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
    }

    public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
    }

    public function startTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
    }

    public function endTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
    }

    public function startTest(PHPUnit_Framework_Test $test)
    {
    }

    public function endTest(PHPUnit_Framework_Test $test, $time)
    {
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A TestSuite is a composite of Tests. It runs a collection of test cases.
 */
class PHPUnit_Framework_TestSuite implements PHPUnit_Framework_Test, PHPUnit_Framework_SelfDescribing, IteratorAggregate
{
    /**
     * Last count of tests in this suite.
     *
     * @var int|null
     */
    private $cachedNumTests;

    /**
     * Enable or disable the backup and restoration of the $GLOBALS array.
     *
     * @var bool
     */
    protected $backupGlobals = null;

    /**
     * Enable or disable the backup and restoration of static attributes.
     *
     * @var bool
     */
    protected $backupStaticAttributes = null;

    /**
     * @var bool
     */
    private $beStrictAboutChangesToGlobalState = null;

    /**
     * @var bool
     */
    protected $runTestInSeparateProcess = false;

    /**
     * The name of the test suite.
     *
     * @var string
     */
    protected $name = '';

    /**
     * The test groups of the test suite.
     *
     * @var array
     */
    protected $groups = [];

    /**
     * The tests in the test suite.
     *
     * @var array
     */
    protected $tests = [];

    /**
     * The number of tests in the test suite.
     *
     * @var int
     */
    protected $numTests = -1;

    /**
     * @var bool
     */
    protected $testCase = false;

    /**
     * @var array
     */
    protected $foundClasses = [];

    /**
     * @var PHPUnit_Runner_Filter_Factory
     */
    private $iteratorFilter = null;

    /**
     * @var string[]
     */
    private $declaredClasses;

    /**
     * Constructs a new TestSuite:
     *
     *   - PHPUnit_Framework_TestSuite() constructs an empty TestSuite.
     *
     *   - PHPUnit_Framework_TestSuite(ReflectionClass) constructs a
     *     TestSuite from the given class.
     *
     *   - PHPUnit_Framework_TestSuite(ReflectionClass, String)
     *     constructs a TestSuite from the given class with the given
     *     name.
     *
     *   - PHPUnit_Framework_TestSuite(String) either constructs a
     *     TestSuite from the given class (if the passed string is the
     *     name of an existing class) or constructs an empty TestSuite
     *     with the given name.
     *
     * @param mixed  $theClass
     * @param string $name
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function __construct($theClass = '', $name = '')
    {
        $this->declaredClasses = get_declared_classes();

        $argumentsValid = false;

        if (is_object($theClass) &&
            $theClass instanceof ReflectionClass) {
            $argumentsValid = true;
        } elseif (is_string($theClass) &&
                 $theClass !== '' &&
                 class_exists($theClass, false)) {
            $argumentsValid = true;

            if ($name == '') {
                $name = $theClass;
            }

            $theClass = new ReflectionClass($theClass);
        } elseif (is_string($theClass)) {
            $this->setName($theClass);

            return;
        }

        if (!$argumentsValid) {
            throw new PHPUnit_Framework_Exception;
        }

        if (!$theClass->isSubclassOf('PHPUnit_Framework_TestCase')) {
            throw new PHPUnit_Framework_Exception(
                'Class "' . $theClass->name . '" does not extend PHPUnit_Framework_TestCase.'
            );
        }

        if ($name != '') {
            $this->setName($name);
        } else {
            $this->setName($theClass->getName());
        }

        $constructor = $theClass->getConstructor();

        if ($constructor !== null &&
            !$constructor->isPublic()) {
            $this->addTest(
                self::warning(
                    sprintf(
                        'Class "%s" has no public constructor.',
                        $theClass->getName()
                    )
                )
            );

            return;
        }

        foreach ($theClass->getMethods() as $method) {
            $this->addTestMethod($theClass, $method);
        }

        if (empty($this->tests)) {
            $this->addTest(
                self::warning(
                    sprintf(
                        'No tests found in class "%s".',
                        $theClass->getName()
                    )
                )
            );
        }

        $this->testCase = true;
    }

    /**
     * Returns a string representation of the test suite.
     *
     * @return string
     */
    public function toString()
    {
        return $this->getName();
    }

    /**
     * Adds a test to the suite.
     *
     * @param PHPUnit_Framework_Test $test
     * @param array                  $groups
     */
    public function addTest(PHPUnit_Framework_Test $test, $groups = [])
    {
        $class = new ReflectionClass($test);

        if (!$class->isAbstract()) {
            $this->tests[]  = $test;
            $this->numTests = -1;

            if ($test instanceof self &&
                empty($groups)) {
                $groups = $test->getGroups();
            }

            if (empty($groups)) {
                $groups = ['default'];
            }

            foreach ($groups as $group) {
                if (!isset($this->groups[$group])) {
                    $this->groups[$group] = [$test];
                } else {
                    $this->groups[$group][] = $test;
                }
            }

            if ($test instanceof PHPUnit_Framework_TestCase) {
                $test->setGroups($groups);
            }
        }
    }

    /**
     * Adds the tests from the given class to the suite.
     *
     * @param mixed $testClass
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function addTestSuite($testClass)
    {
        if (is_string($testClass) && class_exists($testClass)) {
            $testClass = new ReflectionClass($testClass);
        }

        if (!is_object($testClass)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(
                1,
                'class name or object'
            );
        }

        if ($testClass instanceof self) {
            $this->addTest($testClass);
        } elseif ($testClass instanceof ReflectionClass) {
            $suiteMethod = false;

            if (!$testClass->isAbstract()) {
                if ($testClass->hasMethod(PHPUnit_Runner_BaseTestRunner::SUITE_METHODNAME)) {
                    $method = $testClass->getMethod(
                        PHPUnit_Runner_BaseTestRunner::SUITE_METHODNAME
                    );

                    if ($method->isStatic()) {
                        $this->addTest(
                            $method->invoke(null, $testClass->getName())
                        );

                        $suiteMethod = true;
                    }
                }
            }

            if (!$suiteMethod && !$testClass->isAbstract() && $testClass->isSubclassOf(PHPUnit_Framework_TestCase::class)) {
                $this->addTest(new self($testClass));
            }
        } else {
            throw new PHPUnit_Framework_Exception;
        }
    }

    /**
     * Wraps both <code>addTest()</code> and <code>addTestSuite</code>
     * as well as the separate import statements for the user's convenience.
     *
     * If the named file cannot be read or there are no new tests that can be
     * added, a <code>PHPUnit_Framework_WarningTestCase</code> will be created instead,
     * leaving the current test run untouched.
     *
     * @param string $filename
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function addTestFile($filename)
    {
        if (!is_string($filename)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        if (file_exists($filename) && substr($filename, -5) == '.phpt') {
            $this->addTest(
                new PHPUnit_Extensions_PhptTestCase($filename)
            );

            return;
        }

        // The given file may contain further stub classes in addition to the
        // test class itself. Figure out the actual test class.
        $filename   = PHPUnit_Util_Fileloader::checkAndLoad($filename);
        $newClasses = array_diff(get_declared_classes(), $this->declaredClasses);

        // The diff is empty in case a parent class (with test methods) is added
        // AFTER a child class that inherited from it. To account for that case,
        // cumulate all discovered classes, so the parent class may be found in
        // a later invocation.
        if (!empty($newClasses)) {
            // On the assumption that test classes are defined first in files,
            // process discovered classes in approximate LIFO order, so as to
            // avoid unnecessary reflection.
            $this->foundClasses = array_merge($newClasses, $this->foundClasses);
            $this->declaredClasses = get_declared_classes();
        }

        // The test class's name must match the filename, either in full, or as
        // a PEAR/PSR-0 prefixed shortname ('NameSpace_ShortName'), or as a
        // PSR-1 local shortname ('NameSpace\ShortName'). The comparison must be
        // anchored to prevent false-positive matches (e.g., 'OtherShortName').
        $shortname      = basename($filename, '.php');
        $shortnameRegEx = '/(?:^|_|\\\\)' . preg_quote($shortname, '/') . '$/';

        foreach ($this->foundClasses as $i => $className) {
            if (preg_match($shortnameRegEx, $className)) {
                $class = new ReflectionClass($className);

                if ($class->getFileName() == $filename) {
                    $newClasses = [$className];
                    unset($this->foundClasses[$i]);
                    break;
                }
            }
        }

        foreach ($newClasses as $className) {
            if (strpos($className, 'PHPUnit_Framework') === 0) {
                continue;
            }

            $class = new ReflectionClass($className);

            if (!$class->isAbstract()) {
                if ($class->hasMethod(PHPUnit_Runner_BaseTestRunner::SUITE_METHODNAME)) {
                    $method = $class->getMethod(
                        PHPUnit_Runner_BaseTestRunner::SUITE_METHODNAME
                    );

                    if ($method->isStatic()) {
                        $this->addTest($method->invoke(null, $className));
                    }
                } elseif ($class->implementsInterface('PHPUnit_Framework_Test')) {
                    $this->addTestSuite($class);
                }
            }
        }

        $this->numTests = -1;
    }

    /**
     * Wrapper for addTestFile() that adds multiple test files.
     *
     * @param array|Iterator $filenames
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function addTestFiles($filenames)
    {
        if (!(is_array($filenames) ||
             (is_object($filenames) && $filenames instanceof Iterator))) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(
                1,
                'array or iterator'
            );
        }

        foreach ($filenames as $filename) {
            $this->addTestFile((string) $filename);
        }
    }

    /**
     * Counts the number of test cases that will be run by this test.
     *
     * @param bool $preferCache Indicates if cache is preferred.
     *
     * @return int
     */
    public function count($preferCache = false)
    {
        if ($preferCache && $this->cachedNumTests !== null) {
            $numTests = $this->cachedNumTests;
        } else {
            $numTests = 0;

            foreach ($this as $test) {
                $numTests += count($test);
            }

            $this->cachedNumTests = $numTests;
        }

        return $numTests;
    }

    /**
     * @param ReflectionClass $theClass
     * @param string          $name
     *
     * @return PHPUnit_Framework_Test
     *
     * @throws PHPUnit_Framework_Exception
     */
    public static function createTest(ReflectionClass $theClass, $name)
    {
        $className = $theClass->getName();

        if (!$theClass->isInstantiable()) {
            return self::warning(
                sprintf('Cannot instantiate class "%s".', $className)
            );
        }

        $backupSettings = PHPUnit_Util_Test::getBackupSettings(
            $className,
            $name
        );

        $preserveGlobalState = PHPUnit_Util_Test::getPreserveGlobalStateSettings(
            $className,
            $name
        );

        $runTestInSeparateProcess = PHPUnit_Util_Test::getProcessIsolationSettings(
            $className,
            $name
        );

        $constructor = $theClass->getConstructor();

        if ($constructor !== null) {
            $parameters = $constructor->getParameters();

            // TestCase() or TestCase($name)
            if (count($parameters) < 2) {
                $test = new $className;
            } // TestCase($name, $data)
            else {
                try {
                    $data = PHPUnit_Util_Test::getProvidedData(
                        $className,
                        $name
                    );
                } catch (PHPUnit_Framework_IncompleteTestError $e) {
                    $message = sprintf(
                        'Test for %s::%s marked incomplete by data provider',
                        $className,
                        $name
                    );

                    $_message = $e->getMessage();

                    if (!empty($_message)) {
                        $message .= "\n" . $_message;
                    }

                    $data = self::incompleteTest($className, $name, $message);
                } catch (PHPUnit_Framework_SkippedTestError $e) {
                    $message = sprintf(
                        'Test for %s::%s skipped by data provider',
                        $className,
                        $name
                    );

                    $_message = $e->getMessage();

                    if (!empty($_message)) {
                        $message .= "\n" . $_message;
                    }

                    $data = self::skipTest($className, $name, $message);
                } catch (Throwable $_t) {
                    $t = $_t;
                } catch (Exception $_t) {
                    $t = $_t;
                }

                if (isset($t)) {
                    $message = sprintf(
                        'The data provider specified for %s::%s is invalid.',
                        $className,
                        $name
                    );

                    $_message = $t->getMessage();

                    if (!empty($_message)) {
                        $message .= "\n" . $_message;
                    }

                    $data = self::warning($message);
                }

                // Test method with @dataProvider.
                if (isset($data)) {
                    $test = new PHPUnit_Framework_TestSuite_DataProvider(
                        $className . '::' . $name
                    );

                    if (empty($data)) {
                        $data = self::warning(
                            sprintf(
                                'No tests found in suite "%s".',
                                $test->getName()
                            )
                        );
                    }

                    $groups = PHPUnit_Util_Test::getGroups($className, $name);

                    if ($data instanceof PHPUnit_Framework_WarningTestCase ||
                        $data instanceof PHPUnit_Framework_SkippedTestCase ||
                        $data instanceof PHPUnit_Framework_IncompleteTestCase) {
                        $test->addTest($data, $groups);
                    } else {
                        foreach ($data as $_dataName => $_data) {
                            $_test = new $className($name, $_data, $_dataName);

                            if ($runTestInSeparateProcess) {
                                $_test->setRunTestInSeparateProcess(true);

                                if ($preserveGlobalState !== null) {
                                    $_test->setPreserveGlobalState($preserveGlobalState);
                                }
                            }

                            if ($backupSettings['backupGlobals'] !== null) {
                                $_test->setBackupGlobals(
                                    $backupSettings['backupGlobals']
                                );
                            }

                            if ($backupSettings['backupStaticAttributes'] !== null) {
                                $_test->setBackupStaticAttributes(
                                    $backupSettings['backupStaticAttributes']
                                );
                            }

                            $test->addTest($_test, $groups);
                        }
                    }
                } else {
                    $test = new $className;
                }
            }
        }

        if (!isset($test)) {
            throw new PHPUnit_Framework_Exception('No valid test provided.');
        }

        if ($test instanceof PHPUnit_Framework_TestCase) {
            $test->setName($name);

            if ($runTestInSeparateProcess) {
                $test->setRunTestInSeparateProcess(true);

                if ($preserveGlobalState !== null) {
                    $test->setPreserveGlobalState($preserveGlobalState);
                }
            }

            if ($backupSettings['backupGlobals'] !== null) {
                $test->setBackupGlobals($backupSettings['backupGlobals']);
            }

            if ($backupSettings['backupStaticAttributes'] !== null) {
                $test->setBackupStaticAttributes(
                    $backupSettings['backupStaticAttributes']
                );
            }
        }

        return $test;
    }

    /**
     * Creates a default TestResult object.
     *
     * @return PHPUnit_Framework_TestResult
     */
    protected function createResult()
    {
        return new PHPUnit_Framework_TestResult;
    }

    /**
     * Returns the name of the suite.
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Returns the test groups of the suite.
     *
     * @return array
     */
    public function getGroups()
    {
        return array_keys($this->groups);
    }

    public function getGroupDetails()
    {
        return $this->groups;
    }

    /**
     * Set tests groups of the test case
     *
     * @param array $groups
     */
    public function setGroupDetails(array $groups)
    {
        $this->groups = $groups;
    }

    /**
     * Runs the tests and collects their result in a TestResult.
     *
     * @param PHPUnit_Framework_TestResult $result
     *
     * @return PHPUnit_Framework_TestResult
     */
    public function run(PHPUnit_Framework_TestResult $result = null)
    {
        if ($result === null) {
            $result = $this->createResult();
        }

        if (count($this) == 0) {
            return $result;
        }

        $hookMethods = PHPUnit_Util_Test::getHookMethods($this->name);

        $result->startTestSuite($this);

        try {
            $this->setUp();

            foreach ($hookMethods['beforeClass'] as $beforeClassMethod) {
                if ($this->testCase === true &&
                    class_exists($this->name, false) &&
                    method_exists($this->name, $beforeClassMethod)) {
                    if ($missingRequirements = PHPUnit_Util_Test::getMissingRequirements($this->name, $beforeClassMethod)) {
                        $this->markTestSuiteSkipped(implode(PHP_EOL, $missingRequirements));
                    }

                    call_user_func([$this->name, $beforeClassMethod]);
                }
            }
        } catch (PHPUnit_Framework_SkippedTestSuiteError $e) {
            $numTests = count($this);

            for ($i = 0; $i < $numTests; $i++) {
                $result->startTest($this);
                $result->addFailure($this, $e, 0);
                $result->endTest($this, 0);
            }

            $this->tearDown();
            $result->endTestSuite($this);

            return $result;
        } catch (Throwable $_t) {
            $t = $_t;
        } catch (Exception $_t) {
            $t = $_t;
        }

        if (isset($t)) {
            $numTests = count($this);

            for ($i = 0; $i < $numTests; $i++) {
                if ($result->shouldStop()) {
                    break;
                }

                $result->startTest($this);
                $result->addError($this, $t, 0);
                $result->endTest($this, 0);
            }

            $this->tearDown();
            $result->endTestSuite($this);

            return $result;
        }

        foreach ($this as $test) {
            if ($result->shouldStop()) {
                break;
            }

            if ($test instanceof PHPUnit_Framework_TestCase ||
                $test instanceof self) {
                $test->setBeStrictAboutChangesToGlobalState($this->beStrictAboutChangesToGlobalState);
                $test->setBackupGlobals($this->backupGlobals);
                $test->setBackupStaticAttributes($this->backupStaticAttributes);
                $test->setRunTestInSeparateProcess($this->runTestInSeparateProcess);
            }

            $test->run($result);
        }

        foreach ($hookMethods['afterClass'] as $afterClassMethod) {
            if ($this->testCase === true && class_exists($this->name, false) && method_exists($this->name, $afterClassMethod)) {
                call_user_func([$this->name, $afterClassMethod]);
            }
        }

        $this->tearDown();

        $result->endTestSuite($this);

        return $result;
    }

    /**
     * @param bool $runTestInSeparateProcess
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function setRunTestInSeparateProcess($runTestInSeparateProcess)
    {
        if (is_bool($runTestInSeparateProcess)) {
            $this->runTestInSeparateProcess = $runTestInSeparateProcess;
        } else {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');
        }
    }

    /**
     * Runs a test.
     *
     * @deprecated
     *
     * @param PHPUnit_Framework_Test       $test
     * @param PHPUnit_Framework_TestResult $result
     */
    public function runTest(PHPUnit_Framework_Test $test, PHPUnit_Framework_TestResult $result)
    {
        $test->run($result);
    }

    /**
     * Sets the name of the suite.
     *
     * @param  string
     */
    public function setName($name)
    {
        $this->name = $name;
    }

    /**
     * Returns the test at the given index.
     *
     * @param  int|false
     *
     * @return PHPUnit_Framework_Test
     */
    public function testAt($index)
    {
        if (isset($this->tests[$index])) {
            return $this->tests[$index];
        } else {
            return false;
        }
    }

    /**
     * Returns the tests as an enumeration.
     *
     * @return array
     */
    public function tests()
    {
        return $this->tests;
    }

    /**
     * Set tests of the test suite
     *
     * @param array $tests
     */
    public function setTests(array $tests)
    {
        $this->tests = $tests;
    }

    /**
     * Mark the test suite as skipped.
     *
     * @param string $message
     *
     * @throws PHPUnit_Framework_SkippedTestSuiteError
     */
    public function markTestSuiteSkipped($message = '')
    {
        throw new PHPUnit_Framework_SkippedTestSuiteError($message);
    }

    /**
     * @param ReflectionClass  $class
     * @param ReflectionMethod $method
     */
    protected function addTestMethod(ReflectionClass $class, ReflectionMethod $method)
    {
        if (!$this->isTestMethod($method)) {
            return;
        }

        $name = $method->getName();

        if (!$method->isPublic()) {
            $this->addTest(
                self::warning(
                    sprintf(
                        'Test method "%s" in test class "%s" is not public.',
                        $name,
                        $class->getName()
                    )
                )
            );

            return;
        }

        $test = self::createTest($class, $name);

        if ($test instanceof PHPUnit_Framework_TestCase ||
            $test instanceof PHPUnit_Framework_TestSuite_DataProvider) {
            $test->setDependencies(
                PHPUnit_Util_Test::getDependencies($class->getName(), $name)
            );
        }

        $this->addTest(
            $test,
            PHPUnit_Util_Test::getGroups($class->getName(), $name)
        );
    }

    /**
     * @param ReflectionMethod $method
     *
     * @return bool
     */
    public static function isTestMethod(ReflectionMethod $method)
    {
        if (strpos($method->name, 'test') === 0) {
            return true;
        }

        // @scenario on TestCase::testMethod()
        // @test     on TestCase::testMethod()
        $docComment = $method->getDocComment();

        return strpos($docComment, '@test') !== false ||
               strpos($docComment, '@scenario') !== false;
    }

    /**
     * @param string $message
     *
     * @return PHPUnit_Framework_WarningTestCase
     */
    protected static function warning($message)
    {
        return new PHPUnit_Framework_WarningTestCase($message);
    }

    /**
     * @param string $class
     * @param string $methodName
     * @param string $message
     *
     * @return PHPUnit_Framework_SkippedTestCase
     */
    protected static function skipTest($class, $methodName, $message)
    {
        return new PHPUnit_Framework_SkippedTestCase($class, $methodName, $message);
    }

    /**
     * @param string $class
     * @param string $methodName
     * @param string $message
     *
     * @return PHPUnit_Framework_IncompleteTestCase
     */
    protected static function incompleteTest($class, $methodName, $message)
    {
        return new PHPUnit_Framework_IncompleteTestCase($class, $methodName, $message);
    }

    /**
     * @param bool $beStrictAboutChangesToGlobalState
     */
    public function setBeStrictAboutChangesToGlobalState($beStrictAboutChangesToGlobalState)
    {
        if (is_null($this->beStrictAboutChangesToGlobalState) && is_bool($beStrictAboutChangesToGlobalState)) {
            $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState;
        }
    }

    /**
     * @param bool $backupGlobals
     */
    public function setBackupGlobals($backupGlobals)
    {
        if (is_null($this->backupGlobals) && is_bool($backupGlobals)) {
            $this->backupGlobals = $backupGlobals;
        }
    }

    /**
     * @param bool $backupStaticAttributes
     */
    public function setBackupStaticAttributes($backupStaticAttributes)
    {
        if (is_null($this->backupStaticAttributes) &&
            is_bool($backupStaticAttributes)) {
            $this->backupStaticAttributes = $backupStaticAttributes;
        }
    }

    /**
     * Returns an iterator for this test suite.
     *
     * @return RecursiveIteratorIterator
     */
    public function getIterator()
    {
        $iterator = new PHPUnit_Util_TestSuiteIterator($this);

        if ($this->iteratorFilter !== null) {
            $iterator = $this->iteratorFilter->factory($iterator, $this);
        }

        return $iterator;
    }

    public function injectFilter(PHPUnit_Runner_Filter_Factory $filter)
    {
        $this->iteratorFilter = $filter;
        foreach ($this as $test) {
            if ($test instanceof self) {
                $test->injectFilter($filter);
            }
        }
    }

    /**
     * Template Method that is called before the tests
     * of this test suite are run.
     */
    protected function setUp()
    {
    }

    /**
     * Template Method that is called after the tests
     * of this test suite have finished running.
     */
    protected function tearDown()
    {
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Extension to PHPUnit_Framework_AssertionFailedError to mark the special
 * case of a test that printed output.
 */
class PHPUnit_Framework_OutputError extends PHPUnit_Framework_AssertionFailedError
{
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Extension to PHPUnit_Framework_AssertionFailedError to mark the special
 * case of an incomplete test.
 */
class PHPUnit_Framework_IncompleteTestError extends PHPUnit_Framework_AssertionFailedError implements PHPUnit_Framework_IncompleteTest
{
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Interface for classes that can return a description of itself.
 */
interface PHPUnit_Framework_SelfDescribing
{
    /**
     * Returns a string representation of the object.
     *
     * @return string
     */
    public function toString();
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

class PHPUnit_Framework_TestSuite_DataProvider extends PHPUnit_Framework_TestSuite
{
    /**
     * Sets the dependencies of a TestCase.
     *
     * @param array $dependencies
     */
    public function setDependencies(array $dependencies)
    {
        foreach ($this->tests as $test) {
            $test->setDependencies($dependencies);
        }
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Extension to PHPUnit_Framework_AssertionFailedError to mark the special
 * case of a risky test.
 */
class PHPUnit_Framework_RiskyTestError extends PHPUnit_Framework_AssertionFailedError implements PHPUnit_Framework_RiskyTest
{
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A marker interface for marking any exception/error as result of an unit
 * test as incomplete implementation or currently not implemented.
 */
interface PHPUnit_Framework_IncompleteTest
{
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Extension to PHPUnit_Framework_AssertionFailedError to mark the special
 * case of a skipped test.
 */
class PHPUnit_Framework_SkippedTestError extends PHPUnit_Framework_AssertionFailedError implements PHPUnit_Framework_SkippedTest
{
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Creates a synthetic failed assertion.
 */
class PHPUnit_Framework_SyntheticError extends PHPUnit_Framework_AssertionFailedError
{
    /**
     * The synthetic file.
     *
     * @var string
     */
    protected $syntheticFile = '';

    /**
     * The synthetic line number.
     *
     * @var int
     */
    protected $syntheticLine = 0;

    /**
     * The synthetic trace.
     *
     * @var array
     */
    protected $syntheticTrace = [];

    /**
     * Constructor.
     *
     * @param string $message
     * @param int    $code
     * @param string $file
     * @param int    $line
     * @param array  $trace
     */
    public function __construct($message, $code, $file, $line, $trace)
    {
        parent::__construct($message, $code);

        $this->syntheticFile  = $file;
        $this->syntheticLine  = $line;
        $this->syntheticTrace = $trace;
    }

    /**
     * @return string
     */
    public function getSyntheticFile()
    {
        return $this->syntheticFile;
    }

    /**
     * @return int
     */
    public function getSyntheticLine()
    {
        return $this->syntheticLine;
    }

    /**
     * @return array
     */
    public function getSyntheticTrace()
    {
        return $this->syntheticTrace;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

class PHPUnit_Framework_CodeCoverageException extends PHPUnit_Framework_Exception
{
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Wraps Exceptions thrown by code under test.
 *
 * Re-instantiates Exceptions thrown by user-space code to retain their original
 * class names, properties, and stack traces (but without arguments).
 *
 * Unlike PHPUnit_Framework_Exception, the complete stack of previous Exceptions
 * is processed.
 */
class PHPUnit_Framework_ExceptionWrapper extends PHPUnit_Framework_Exception
{
    /**
     * @var string
     */
    protected $className;

    /**
     * @var PHPUnit_Framework_ExceptionWrapper|null
     */
    protected $previous;

    /**
     * @param Throwable|Exception $e
     */
    public function __construct($e)
    {
        // PDOException::getCode() is a string.
        // @see http://php.net/manual/en/class.pdoexception.php#95812
        parent::__construct($e->getMessage(), (int) $e->getCode());

        $this->className = get_class($e);
        $this->file      = $e->getFile();
        $this->line      = $e->getLine();

        $this->serializableTrace = $e->getTrace();

        foreach ($this->serializableTrace as $i => $call) {
            unset($this->serializableTrace[$i]['args']);
        }

        if ($e->getPrevious()) {
            $this->previous = new self($e->getPrevious());
        }
    }

    /**
     * @return string
     */
    public function getClassName()
    {
        return $this->className;
    }

    /**
     * @return PHPUnit_Framework_ExceptionWrapper
     */
    public function getPreviousWrapped()
    {
        return $this->previous;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        $string = PHPUnit_Framework_TestFailure::exceptionToString($this);

        if ($trace = PHPUnit_Util_Filter::getFilteredStacktrace($this)) {
            $string .= "\n" . $trace;
        }

        if ($this->previous) {
            $string .= "\nCaused by\n" . $this->previous;
        }

        return $string;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Extension to PHPUnit_Framework_AssertionFailedError to mark the special
 * case of a test that does not execute the code it wants to cover.
 */
class PHPUnit_Framework_CoveredCodeNotExecutedException extends PHPUnit_Framework_RiskyTestError
{
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Returns a matcher that matches when the method is executed
 * zero or more times.
 *
 * @return PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount
 */
function any()
{
    return call_user_func_array(
        'PHPUnit_Framework_TestCase::any',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_IsAnything matcher object.
 *
 * @return PHPUnit_Framework_Constraint_IsAnything
 */
function anything()
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::anything',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_ArrayHasKey matcher object.
 *
 * @param mixed $key
 *
 * @return PHPUnit_Framework_Constraint_ArrayHasKey
 */
function arrayHasKey($key)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::arrayHasKey',
        func_get_args()
    );
}

/**
 * Asserts that an array has a specified key.
 *
 * @param mixed             $key
 * @param array|ArrayAccess $array
 * @param string            $message
 */
function assertArrayHasKey($key, $array, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertArrayHasKey',
        func_get_args()
    );
}

/**
 * Asserts that an array has a specified subset.
 *
 * @param array|ArrayAccess $subset
 * @param array|ArrayAccess $array
 * @param bool              $strict  Check for object identity
 * @param string            $message
 */
function assertArraySubset($subset, $array, $strict = false, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertArraySubset',
        func_get_args()
    );
}

/**
 * Asserts that an array does not have a specified key.
 *
 * @param mixed             $key
 * @param array|ArrayAccess $array
 * @param string            $message
 */
function assertArrayNotHasKey($key, $array, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertArrayNotHasKey',
        func_get_args()
    );
}

/**
 * Asserts that a haystack that is stored in a static attribute of a class
 * or an attribute of an object contains a needle.
 *
 * @param mixed  $needle
 * @param string $haystackAttributeName
 * @param mixed  $haystackClassOrObject
 * @param string $message
 * @param bool   $ignoreCase
 * @param bool   $checkForObjectIdentity
 * @param bool   $checkForNonObjectIdentity
 */
function assertAttributeContains($needle, $haystackAttributeName, $haystackClassOrObject, $message = '', $ignoreCase = false, $checkForObjectIdentity = true, $checkForNonObjectIdentity = false)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertAttributeContains',
        func_get_args()
    );
}

/**
 * Asserts that a haystack that is stored in a static attribute of a class
 * or an attribute of an object contains only values of a given type.
 *
 * @param string $type
 * @param string $haystackAttributeName
 * @param mixed  $haystackClassOrObject
 * @param bool   $isNativeType
 * @param string $message
 */
function assertAttributeContainsOnly($type, $haystackAttributeName, $haystackClassOrObject, $isNativeType = null, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertAttributeContainsOnly',
        func_get_args()
    );
}

/**
 * Asserts the number of elements of an array, Countable or Traversable
 * that is stored in an attribute.
 *
 * @param int    $expectedCount
 * @param string $haystackAttributeName
 * @param mixed  $haystackClassOrObject
 * @param string $message
 */
function assertAttributeCount($expectedCount, $haystackAttributeName, $haystackClassOrObject, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertAttributeCount',
        func_get_args()
    );
}

/**
 * Asserts that a static attribute of a class or an attribute of an object
 * is empty.
 *
 * @param string $haystackAttributeName
 * @param mixed  $haystackClassOrObject
 * @param string $message
 */
function assertAttributeEmpty($haystackAttributeName, $haystackClassOrObject, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertAttributeEmpty',
        func_get_args()
    );
}

/**
 * Asserts that a variable is equal to an attribute of an object.
 *
 * @param mixed  $expected
 * @param string $actualAttributeName
 * @param string $actualClassOrObject
 * @param string $message
 * @param float  $delta
 * @param int    $maxDepth
 * @param bool   $canonicalize
 * @param bool   $ignoreCase
 */
function assertAttributeEquals($expected, $actualAttributeName, $actualClassOrObject, $message = '', $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertAttributeEquals',
        func_get_args()
    );
}

/**
 * Asserts that an attribute is greater than another value.
 *
 * @param mixed  $expected
 * @param string $actualAttributeName
 * @param string $actualClassOrObject
 * @param string $message
 */
function assertAttributeGreaterThan($expected, $actualAttributeName, $actualClassOrObject, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertAttributeGreaterThan',
        func_get_args()
    );
}

/**
 * Asserts that an attribute is greater than or equal to another value.
 *
 * @param mixed  $expected
 * @param string $actualAttributeName
 * @param string $actualClassOrObject
 * @param string $message
 */
function assertAttributeGreaterThanOrEqual($expected, $actualAttributeName, $actualClassOrObject, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertAttributeGreaterThanOrEqual',
        func_get_args()
    );
}

/**
 * Asserts that an attribute is of a given type.
 *
 * @param string $expected
 * @param string $attributeName
 * @param mixed  $classOrObject
 * @param string $message
 */
function assertAttributeInstanceOf($expected, $attributeName, $classOrObject, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertAttributeInstanceOf',
        func_get_args()
    );
}

/**
 * Asserts that an attribute is of a given type.
 *
 * @param string $expected
 * @param string $attributeName
 * @param mixed  $classOrObject
 * @param string $message
 */
function assertAttributeInternalType($expected, $attributeName, $classOrObject, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertAttributeInternalType',
        func_get_args()
    );
}

/**
 * Asserts that an attribute is smaller than another value.
 *
 * @param mixed  $expected
 * @param string $actualAttributeName
 * @param string $actualClassOrObject
 * @param string $message
 */
function assertAttributeLessThan($expected, $actualAttributeName, $actualClassOrObject, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertAttributeLessThan',
        func_get_args()
    );
}

/**
 * Asserts that an attribute is smaller than or equal to another value.
 *
 * @param mixed  $expected
 * @param string $actualAttributeName
 * @param string $actualClassOrObject
 * @param string $message
 */
function assertAttributeLessThanOrEqual($expected, $actualAttributeName, $actualClassOrObject, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertAttributeLessThanOrEqual',
        func_get_args()
    );
}

/**
 * Asserts that a haystack that is stored in a static attribute of a class
 * or an attribute of an object does not contain a needle.
 *
 * @param mixed  $needle
 * @param string $haystackAttributeName
 * @param mixed  $haystackClassOrObject
 * @param string $message
 * @param bool   $ignoreCase
 * @param bool   $checkForObjectIdentity
 * @param bool   $checkForNonObjectIdentity
 */
function assertAttributeNotContains($needle, $haystackAttributeName, $haystackClassOrObject, $message = '', $ignoreCase = false, $checkForObjectIdentity = true, $checkForNonObjectIdentity = false)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertAttributeNotContains',
        func_get_args()
    );
}

/**
 * Asserts that a haystack that is stored in a static attribute of a class
 * or an attribute of an object does not contain only values of a given
 * type.
 *
 * @param string $type
 * @param string $haystackAttributeName
 * @param mixed  $haystackClassOrObject
 * @param bool   $isNativeType
 * @param string $message
 */
function assertAttributeNotContainsOnly($type, $haystackAttributeName, $haystackClassOrObject, $isNativeType = null, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertAttributeNotContainsOnly',
        func_get_args()
    );
}

/**
 * Asserts the number of elements of an array, Countable or Traversable
 * that is stored in an attribute.
 *
 * @param int    $expectedCount
 * @param string $haystackAttributeName
 * @param mixed  $haystackClassOrObject
 * @param string $message
 */
function assertAttributeNotCount($expectedCount, $haystackAttributeName, $haystackClassOrObject, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertAttributeNotCount',
        func_get_args()
    );
}

/**
 * Asserts that a static attribute of a class or an attribute of an object
 * is not empty.
 *
 * @param string $haystackAttributeName
 * @param mixed  $haystackClassOrObject
 * @param string $message
 */
function assertAttributeNotEmpty($haystackAttributeName, $haystackClassOrObject, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertAttributeNotEmpty',
        func_get_args()
    );
}

/**
 * Asserts that a variable is not equal to an attribute of an object.
 *
 * @param mixed  $expected
 * @param string $actualAttributeName
 * @param string $actualClassOrObject
 * @param string $message
 * @param float  $delta
 * @param int    $maxDepth
 * @param bool   $canonicalize
 * @param bool   $ignoreCase
 */
function assertAttributeNotEquals($expected, $actualAttributeName, $actualClassOrObject, $message = '', $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertAttributeNotEquals',
        func_get_args()
    );
}

/**
 * Asserts that an attribute is of a given type.
 *
 * @param string $expected
 * @param string $attributeName
 * @param mixed  $classOrObject
 * @param string $message
 */
function assertAttributeNotInstanceOf($expected, $attributeName, $classOrObject, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertAttributeNotInstanceOf',
        func_get_args()
    );
}

/**
 * Asserts that an attribute is of a given type.
 *
 * @param string $expected
 * @param string $attributeName
 * @param mixed  $classOrObject
 * @param string $message
 */
function assertAttributeNotInternalType($expected, $attributeName, $classOrObject, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertAttributeNotInternalType',
        func_get_args()
    );
}

/**
 * Asserts that a variable and an attribute of an object do not have the
 * same type and value.
 *
 * @param mixed  $expected
 * @param string $actualAttributeName
 * @param object $actualClassOrObject
 * @param string $message
 */
function assertAttributeNotSame($expected, $actualAttributeName, $actualClassOrObject, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertAttributeNotSame',
        func_get_args()
    );
}

/**
 * Asserts that a variable and an attribute of an object have the same type
 * and value.
 *
 * @param mixed  $expected
 * @param string $actualAttributeName
 * @param object $actualClassOrObject
 * @param string $message
 */
function assertAttributeSame($expected, $actualAttributeName, $actualClassOrObject, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertAttributeSame',
        func_get_args()
    );
}

/**
 * Asserts that a class has a specified attribute.
 *
 * @param string $attributeName
 * @param string $className
 * @param string $message
 */
function assertClassHasAttribute($attributeName, $className, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertClassHasAttribute',
        func_get_args()
    );
}

/**
 * Asserts that a class has a specified static attribute.
 *
 * @param string $attributeName
 * @param string $className
 * @param string $message
 */
function assertClassHasStaticAttribute($attributeName, $className, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertClassHasStaticAttribute',
        func_get_args()
    );
}

/**
 * Asserts that a class does not have a specified attribute.
 *
 * @param string $attributeName
 * @param string $className
 * @param string $message
 */
function assertClassNotHasAttribute($attributeName, $className, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertClassNotHasAttribute',
        func_get_args()
    );
}

/**
 * Asserts that a class does not have a specified static attribute.
 *
 * @param string $attributeName
 * @param string $className
 * @param string $message
 */
function assertClassNotHasStaticAttribute($attributeName, $className, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertClassNotHasStaticAttribute',
        func_get_args()
    );
}

/**
 * Asserts that a haystack contains a needle.
 *
 * @param mixed  $needle
 * @param mixed  $haystack
 * @param string $message
 * @param bool   $ignoreCase
 * @param bool   $checkForObjectIdentity
 * @param bool   $checkForNonObjectIdentity
 */
function assertContains($needle, $haystack, $message = '', $ignoreCase = false, $checkForObjectIdentity = true, $checkForNonObjectIdentity = false)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertContains',
        func_get_args()
    );
}

/**
 * Asserts that a haystack contains only values of a given type.
 *
 * @param string $type
 * @param mixed  $haystack
 * @param bool   $isNativeType
 * @param string $message
 */
function assertContainsOnly($type, $haystack, $isNativeType = null, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertContainsOnly',
        func_get_args()
    );
}

/**
 * Asserts that a haystack contains only instances of a given classname
 *
 * @param string            $classname
 * @param array|Traversable $haystack
 * @param string            $message
 */
function assertContainsOnlyInstancesOf($classname, $haystack, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertContainsOnlyInstancesOf',
        func_get_args()
    );
}

/**
 * Asserts the number of elements of an array, Countable or Traversable.
 *
 * @param int    $expectedCount
 * @param mixed  $haystack
 * @param string $message
 */
function assertCount($expectedCount, $haystack, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertCount',
        func_get_args()
    );
}

/**
 * Asserts that a variable is empty.
 *
 * @param mixed  $actual
 * @param string $message
 *
 * @throws PHPUnit_Framework_AssertionFailedError
 */
function assertEmpty($actual, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertEmpty',
        func_get_args()
    );
}

/**
 * Asserts that a hierarchy of DOMElements matches.
 *
 * @param DOMElement $expectedElement
 * @param DOMElement $actualElement
 * @param bool       $checkAttributes
 * @param string     $message
 */
function assertEqualXMLStructure(DOMElement $expectedElement, DOMElement $actualElement, $checkAttributes = false, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertEqualXMLStructure',
        func_get_args()
    );
}

/**
 * Asserts that two variables are equal.
 *
 * @param mixed  $expected
 * @param mixed  $actual
 * @param string $message
 * @param float  $delta
 * @param int    $maxDepth
 * @param bool   $canonicalize
 * @param bool   $ignoreCase
 */
function assertEquals($expected, $actual, $message = '', $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertEquals',
        func_get_args()
    );
}

/**
 * Asserts that a condition is not true.
 *
 * @param bool   $condition
 * @param string $message
 *
 * @throws PHPUnit_Framework_AssertionFailedError
 */
function assertNotTrue($condition, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertNotTrue',
        func_get_args()
    );
}

/**
 * Asserts that a condition is false.
 *
 * @param bool   $condition
 * @param string $message
 *
 * @throws PHPUnit_Framework_AssertionFailedError
 */
function assertFalse($condition, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertFalse',
        func_get_args()
    );
}

/**
 * Asserts that the contents of one file is equal to the contents of another
 * file.
 *
 * @param string $expected
 * @param string $actual
 * @param string $message
 * @param bool   $canonicalize
 * @param bool   $ignoreCase
 */
function assertFileEquals($expected, $actual, $message = '', $canonicalize = false, $ignoreCase = false)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertFileEquals',
        func_get_args()
    );
}

/**
 * Asserts that a file exists.
 *
 * @param string $filename
 * @param string $message
 */
function assertFileExists($filename, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertFileExists',
        func_get_args()
    );
}

/**
 * Asserts that the contents of one file is not equal to the contents of
 * another file.
 *
 * @param string $expected
 * @param string $actual
 * @param string $message
 * @param bool   $canonicalize
 * @param bool   $ignoreCase
 */
function assertFileNotEquals($expected, $actual, $message = '', $canonicalize = false, $ignoreCase = false)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertFileNotEquals',
        func_get_args()
    );
}

/**
 * Asserts that a file does not exist.
 *
 * @param string $filename
 * @param string $message
 */
function assertFileNotExists($filename, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertFileNotExists',
        func_get_args()
    );
}

/**
 * Asserts that a value is greater than another value.
 *
 * @param mixed  $expected
 * @param mixed  $actual
 * @param string $message
 */
function assertGreaterThan($expected, $actual, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertGreaterThan',
        func_get_args()
    );
}

/**
 * Asserts that a value is greater than or equal to another value.
 *
 * @param mixed  $expected
 * @param mixed  $actual
 * @param string $message
 */
function assertGreaterThanOrEqual($expected, $actual, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertGreaterThanOrEqual',
        func_get_args()
    );
}

/**
 * Asserts that a variable is of a given type.
 *
 * @param string $expected
 * @param mixed  $actual
 * @param string $message
 */
function assertInstanceOf($expected, $actual, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertInstanceOf',
        func_get_args()
    );
}

/**
 * Asserts that a variable is of a given type.
 *
 * @param string $expected
 * @param mixed  $actual
 * @param string $message
 */
function assertInternalType($expected, $actual, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertInternalType',
        func_get_args()
    );
}

/**
 * Asserts that a string is a valid JSON string.
 *
 * @param string $actualJson
 * @param string $message
 */
function assertJson($actualJson, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertJson',
        func_get_args()
    );
}

/**
 * Asserts that two JSON files are equal.
 *
 * @param string $expectedFile
 * @param string $actualFile
 * @param string $message
 */
function assertJsonFileEqualsJsonFile($expectedFile, $actualFile, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertJsonFileEqualsJsonFile',
        func_get_args()
    );
}

/**
 * Asserts that two JSON files are not equal.
 *
 * @param string $expectedFile
 * @param string $actualFile
 * @param string $message
 */
function assertJsonFileNotEqualsJsonFile($expectedFile, $actualFile, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertJsonFileNotEqualsJsonFile',
        func_get_args()
    );
}

/**
 * Asserts that the generated JSON encoded object and the content of the given file are equal.
 *
 * @param string $expectedFile
 * @param string $actualJson
 * @param string $message
 */
function assertJsonStringEqualsJsonFile($expectedFile, $actualJson, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertJsonStringEqualsJsonFile',
        func_get_args()
    );
}

/**
 * Asserts that two given JSON encoded objects or arrays are equal.
 *
 * @param string $expectedJson
 * @param string $actualJson
 * @param string $message
 */
function assertJsonStringEqualsJsonString($expectedJson, $actualJson, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertJsonStringEqualsJsonString',
        func_get_args()
    );
}

/**
 * Asserts that the generated JSON encoded object and the content of the given file are not equal.
 *
 * @param string $expectedFile
 * @param string $actualJson
 * @param string $message
 */
function assertJsonStringNotEqualsJsonFile($expectedFile, $actualJson, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertJsonStringNotEqualsJsonFile',
        func_get_args()
    );
}

/**
 * Asserts that two given JSON encoded objects or arrays are not equal.
 *
 * @param string $expectedJson
 * @param string $actualJson
 * @param string $message
 */
function assertJsonStringNotEqualsJsonString($expectedJson, $actualJson, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertJsonStringNotEqualsJsonString',
        func_get_args()
    );
}

/**
 * Asserts that a value is smaller than another value.
 *
 * @param mixed  $expected
 * @param mixed  $actual
 * @param string $message
 */
function assertLessThan($expected, $actual, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertLessThan',
        func_get_args()
    );
}

/**
 * Asserts that a value is smaller than or equal to another value.
 *
 * @param mixed  $expected
 * @param mixed  $actual
 * @param string $message
 */
function assertLessThanOrEqual($expected, $actual, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertLessThanOrEqual',
        func_get_args()
    );
}

/**
 * Asserts that a variable is finite.
 *
 * @param mixed  $actual
 * @param string $message
 */
function assertFinite($actual, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertFinite',
        func_get_args()
    );
}

/**
 * Asserts that a variable is infinite.
 *
 * @param mixed  $actual
 * @param string $message
 */
function assertInfinite($actual, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertInfinite',
        func_get_args()
    );
}

/**
 * Asserts that a variable is nan.
 *
 * @param mixed  $actual
 * @param string $message
 */
function assertNan($actual, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertNan',
        func_get_args()
    );
}

/**
 * Asserts that a haystack does not contain a needle.
 *
 * @param mixed  $needle
 * @param mixed  $haystack
 * @param string $message
 * @param bool   $ignoreCase
 * @param bool   $checkForObjectIdentity
 * @param bool   $checkForNonObjectIdentity
 */
function assertNotContains($needle, $haystack, $message = '', $ignoreCase = false, $checkForObjectIdentity = true, $checkForNonObjectIdentity = false)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertNotContains',
        func_get_args()
    );
}

/**
 * Asserts that a haystack does not contain only values of a given type.
 *
 * @param string $type
 * @param mixed  $haystack
 * @param bool   $isNativeType
 * @param string $message
 */
function assertNotContainsOnly($type, $haystack, $isNativeType = null, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertNotContainsOnly',
        func_get_args()
    );
}

/**
 * Asserts the number of elements of an array, Countable or Traversable.
 *
 * @param int    $expectedCount
 * @param mixed  $haystack
 * @param string $message
 */
function assertNotCount($expectedCount, $haystack, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertNotCount',
        func_get_args()
    );
}

/**
 * Asserts that a variable is not empty.
 *
 * @param mixed  $actual
 * @param string $message
 *
 * @throws PHPUnit_Framework_AssertionFailedError
 */
function assertNotEmpty($actual, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertNotEmpty',
        func_get_args()
    );
}

/**
 * Asserts that two variables are not equal.
 *
 * @param mixed  $expected
 * @param mixed  $actual
 * @param string $message
 * @param float  $delta
 * @param int    $maxDepth
 * @param bool   $canonicalize
 * @param bool   $ignoreCase
 */
function assertNotEquals($expected, $actual, $message = '', $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertNotEquals',
        func_get_args()
    );
}

/**
 * Asserts that a variable is not of a given type.
 *
 * @param string $expected
 * @param mixed  $actual
 * @param string $message
 */
function assertNotInstanceOf($expected, $actual, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertNotInstanceOf',
        func_get_args()
    );
}

/**
 * Asserts that a variable is not of a given type.
 *
 * @param string $expected
 * @param mixed  $actual
 * @param string $message
 */
function assertNotInternalType($expected, $actual, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertNotInternalType',
        func_get_args()
    );
}

/**
 * Asserts that a condition is not false.
 *
 * @param bool   $condition
 * @param string $message
 *
 * @throws PHPUnit_Framework_AssertionFailedError
 */
function assertNotFalse($condition, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertNotFalse',
        func_get_args()
    );
}

/**
 * Asserts that a variable is not null.
 *
 * @param mixed  $actual
 * @param string $message
 */
function assertNotNull($actual, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertNotNull',
        func_get_args()
    );
}

/**
 * Asserts that a string does not match a given regular expression.
 *
 * @param string $pattern
 * @param string $string
 * @param string $message
 */
function assertNotRegExp($pattern, $string, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertNotRegExp',
        func_get_args()
    );
}

/**
 * Asserts that two variables do not have the same type and value.
 * Used on objects, it asserts that two variables do not reference
 * the same object.
 *
 * @param mixed  $expected
 * @param mixed  $actual
 * @param string $message
 */
function assertNotSame($expected, $actual, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertNotSame',
        func_get_args()
    );
}

/**
 * Assert that the size of two arrays (or `Countable` or `Traversable` objects)
 * is not the same.
 *
 * @param array|Countable|Traversable $expected
 * @param array|Countable|Traversable $actual
 * @param string                      $message
 */
function assertNotSameSize($expected, $actual, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertNotSameSize',
        func_get_args()
    );
}

/**
 * Asserts that a variable is null.
 *
 * @param mixed  $actual
 * @param string $message
 */
function assertNull($actual, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertNull',
        func_get_args()
    );
}

/**
 * Asserts that an object has a specified attribute.
 *
 * @param string $attributeName
 * @param object $object
 * @param string $message
 */
function assertObjectHasAttribute($attributeName, $object, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertObjectHasAttribute',
        func_get_args()
    );
}

/**
 * Asserts that an object does not have a specified attribute.
 *
 * @param string $attributeName
 * @param object $object
 * @param string $message
 */
function assertObjectNotHasAttribute($attributeName, $object, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertObjectNotHasAttribute',
        func_get_args()
    );
}

/**
 * Asserts that a string matches a given regular expression.
 *
 * @param string $pattern
 * @param string $string
 * @param string $message
 */
function assertRegExp($pattern, $string, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertRegExp',
        func_get_args()
    );
}

/**
 * Asserts that two variables have the same type and value.
 * Used on objects, it asserts that two variables reference
 * the same object.
 *
 * @param mixed  $expected
 * @param mixed  $actual
 * @param string $message
 */
function assertSame($expected, $actual, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertSame',
        func_get_args()
    );
}

/**
 * Assert that the size of two arrays (or `Countable` or `Traversable` objects)
 * is the same.
 *
 * @param array|Countable|Traversable $expected
 * @param array|Countable|Traversable $actual
 * @param string                      $message
 */
function assertSameSize($expected, $actual, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertSameSize',
        func_get_args()
    );
}

/**
 * Asserts that a string ends not with a given prefix.
 *
 * @param string $suffix
 * @param string $string
 * @param string $message
 */
function assertStringEndsNotWith($suffix, $string, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertStringEndsNotWith',
        func_get_args()
    );
}

/**
 * Asserts that a string ends with a given prefix.
 *
 * @param string $suffix
 * @param string $string
 * @param string $message
 */
function assertStringEndsWith($suffix, $string, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertStringEndsWith',
        func_get_args()
    );
}

/**
 * Asserts that the contents of a string is equal
 * to the contents of a file.
 *
 * @param string $expectedFile
 * @param string $actualString
 * @param string $message
 * @param bool   $canonicalize
 * @param bool   $ignoreCase
 */
function assertStringEqualsFile($expectedFile, $actualString, $message = '', $canonicalize = false, $ignoreCase = false)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertStringEqualsFile',
        func_get_args()
    );
}

/**
 * Asserts that a string matches a given format string.
 *
 * @param string $format
 * @param string $string
 * @param string $message
 */
function assertStringMatchesFormat($format, $string, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertStringMatchesFormat',
        func_get_args()
    );
}

/**
 * Asserts that a string matches a given format file.
 *
 * @param string $formatFile
 * @param string $string
 * @param string $message
 */
function assertStringMatchesFormatFile($formatFile, $string, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertStringMatchesFormatFile',
        func_get_args()
    );
}

/**
 * Asserts that the contents of a string is not equal
 * to the contents of a file.
 *
 * @param string $expectedFile
 * @param string $actualString
 * @param string $message
 * @param bool   $canonicalize
 * @param bool   $ignoreCase
 */
function assertStringNotEqualsFile($expectedFile, $actualString, $message = '', $canonicalize = false, $ignoreCase = false)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertStringNotEqualsFile',
        func_get_args()
    );
}

/**
 * Asserts that a string does not match a given format string.
 *
 * @param string $format
 * @param string $string
 * @param string $message
 */
function assertStringNotMatchesFormat($format, $string, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertStringNotMatchesFormat',
        func_get_args()
    );
}

/**
 * Asserts that a string does not match a given format string.
 *
 * @param string $formatFile
 * @param string $string
 * @param string $message
 */
function assertStringNotMatchesFormatFile($formatFile, $string, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertStringNotMatchesFormatFile',
        func_get_args()
    );
}

/**
 * Asserts that a string starts not with a given prefix.
 *
 * @param string $prefix
 * @param string $string
 * @param string $message
 */
function assertStringStartsNotWith($prefix, $string, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertStringStartsNotWith',
        func_get_args()
    );
}

/**
 * Asserts that a string starts with a given prefix.
 *
 * @param string $prefix
 * @param string $string
 * @param string $message
 */
function assertStringStartsWith($prefix, $string, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertStringStartsWith',
        func_get_args()
    );
}

/**
 * Evaluates a PHPUnit_Framework_Constraint matcher object.
 *
 * @param mixed                        $value
 * @param PHPUnit_Framework_Constraint $constraint
 * @param string                       $message
 */
function assertThat($value, PHPUnit_Framework_Constraint $constraint, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertThat',
        func_get_args()
    );
}

/**
 * Asserts that a condition is true.
 *
 * @param bool   $condition
 * @param string $message
 *
 * @throws PHPUnit_Framework_AssertionFailedError
 */
function assertTrue($condition, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertTrue',
        func_get_args()
    );
}

/**
 * Asserts that two XML files are equal.
 *
 * @param string $expectedFile
 * @param string $actualFile
 * @param string $message
 */
function assertXmlFileEqualsXmlFile($expectedFile, $actualFile, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertXmlFileEqualsXmlFile',
        func_get_args()
    );
}

/**
 * Asserts that two XML files are not equal.
 *
 * @param string $expectedFile
 * @param string $actualFile
 * @param string $message
 */
function assertXmlFileNotEqualsXmlFile($expectedFile, $actualFile, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertXmlFileNotEqualsXmlFile',
        func_get_args()
    );
}

/**
 * Asserts that two XML documents are equal.
 *
 * @param string $expectedFile
 * @param string $actualXml
 * @param string $message
 */
function assertXmlStringEqualsXmlFile($expectedFile, $actualXml, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertXmlStringEqualsXmlFile',
        func_get_args()
    );
}

/**
 * Asserts that two XML documents are equal.
 *
 * @param string $expectedXml
 * @param string $actualXml
 * @param string $message
 */
function assertXmlStringEqualsXmlString($expectedXml, $actualXml, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertXmlStringEqualsXmlString',
        func_get_args()
    );
}

/**
 * Asserts that two XML documents are not equal.
 *
 * @param string $expectedFile
 * @param string $actualXml
 * @param string $message
 */
function assertXmlStringNotEqualsXmlFile($expectedFile, $actualXml, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertXmlStringNotEqualsXmlFile',
        func_get_args()
    );
}

/**
 * Asserts that two XML documents are not equal.
 *
 * @param string $expectedXml
 * @param string $actualXml
 * @param string $message
 */
function assertXmlStringNotEqualsXmlString($expectedXml, $actualXml, $message = '')
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::assertXmlStringNotEqualsXmlString',
        func_get_args()
    );
}

/**
 * Returns a matcher that matches when the method is executed
 * at the given $index.
 *
 * @param int $index
 *
 * @return PHPUnit_Framework_MockObject_Matcher_InvokedAtIndex
 */
function at($index)
{
    return call_user_func_array(
        'PHPUnit_Framework_TestCase::at',
        func_get_args()
    );
}

/**
 * Returns a matcher that matches when the method is executed at least once.
 *
 * @return PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastOnce
 */
function atLeastOnce()
{
    return call_user_func_array(
        'PHPUnit_Framework_TestCase::atLeastOnce',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_Attribute matcher object.
 *
 * @param PHPUnit_Framework_Constraint $constraint
 * @param string                       $attributeName
 *
 * @return PHPUnit_Framework_Constraint_Attribute
 */
function attribute(PHPUnit_Framework_Constraint $constraint, $attributeName)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::attribute',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_IsEqual matcher object
 * that is wrapped in a PHPUnit_Framework_Constraint_Attribute matcher
 * object.
 *
 * @param string $attributeName
 * @param mixed  $value
 * @param float  $delta
 * @param int    $maxDepth
 * @param bool   $canonicalize
 * @param bool   $ignoreCase
 *
 * @return PHPUnit_Framework_Constraint_Attribute
 */
function attributeEqualTo($attributeName, $value, $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::attributeEqualTo',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_Callback matcher object.
 *
 * @param callable $callback
 *
 * @return PHPUnit_Framework_Constraint_Callback
 */
function callback($callback)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::callback',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_ClassHasAttribute matcher object.
 *
 * @param string $attributeName
 *
 * @return PHPUnit_Framework_Constraint_ClassHasAttribute
 */
function classHasAttribute($attributeName)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::classHasAttribute',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_ClassHasStaticAttribute matcher
 * object.
 *
 * @param string $attributeName
 *
 * @return PHPUnit_Framework_Constraint_ClassHasStaticAttribute
 */
function classHasStaticAttribute($attributeName)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::classHasStaticAttribute',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_TraversableContains matcher
 * object.
 *
 * @param mixed $value
 * @param bool  $checkForObjectIdentity
 * @param bool  $checkForNonObjectIdentity
 *
 * @return PHPUnit_Framework_Constraint_TraversableContains
 */
function contains($value, $checkForObjectIdentity = true, $checkForNonObjectIdentity = false)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::contains',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_TraversableContainsOnly matcher
 * object.
 *
 * @param string $type
 *
 * @return PHPUnit_Framework_Constraint_TraversableContainsOnly
 */
function containsOnly($type)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::containsOnly',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_TraversableContainsOnly matcher
 * object.
 *
 * @param string $classname
 *
 * @return PHPUnit_Framework_Constraint_TraversableContainsOnly
 */
function containsOnlyInstancesOf($classname)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::containsOnlyInstancesOf',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_Count matcher object.
 *
 * @param int $count
 *
 * @return Count
 */
function countOf($count)
{
    return call_user_func_array(
        'PHPUnit\Framework\Assert::countOf',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_IsEqual matcher object.
 *
 * @param mixed $value
 * @param float $delta
 * @param int   $maxDepth
 * @param bool  $canonicalize
 * @param bool  $ignoreCase
 *
 * @return PHPUnit_Framework_Constraint_IsEqual
 */
function equalTo($value, $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::equalTo',
        func_get_args()
    );
}

/**
 * Returns a matcher that matches when the method is executed
 * exactly $count times.
 *
 * @param int $count
 *
 * @return PHPUnit_Framework_MockObject_Matcher_InvokedCount
 */
function exactly($count)
{
    return call_user_func_array(
        'PHPUnit_Framework_TestCase::exactly',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_FileExists matcher object.
 *
 * @return PHPUnit_Framework_Constraint_FileExists
 */
function fileExists()
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::fileExists',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_GreaterThan matcher object.
 *
 * @param mixed $value
 *
 * @return PHPUnit_Framework_Constraint_GreaterThan
 */
function greaterThan($value)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::greaterThan',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_Or matcher object that wraps
 * a PHPUnit_Framework_Constraint_IsEqual and a
 * PHPUnit_Framework_Constraint_GreaterThan matcher object.
 *
 * @param mixed $value
 *
 * @return PHPUnit_Framework_Constraint_Or
 */
function greaterThanOrEqual($value)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::greaterThanOrEqual',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_IsIdentical matcher object.
 *
 * @param mixed $value
 *
 * @return PHPUnit_Framework_Constraint_IsIdentical
 */
function identicalTo($value)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::identicalTo',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_IsEmpty matcher object.
 *
 * @return PHPUnit_Framework_Constraint_IsEmpty
 */
function isEmpty()
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::isEmpty',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_IsFalse matcher object.
 *
 * @return PHPUnit_Framework_Constraint_IsFalse
 */
function isFalse()
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::isFalse',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_IsInstanceOf matcher object.
 *
 * @param string $className
 *
 * @return PHPUnit_Framework_Constraint_IsInstanceOf
 */
function isInstanceOf($className)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::isInstanceOf',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_IsJson matcher object.
 *
 * @return PHPUnit_Framework_Constraint_IsJson
 */
function isJson()
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::isJson',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_IsNull matcher object.
 *
 * @return PHPUnit_Framework_Constraint_IsNull
 */
function isNull()
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::isNull',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_IsTrue matcher object.
 *
 * @return PHPUnit_Framework_Constraint_IsTrue
 */
function isTrue()
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::isTrue',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_IsType matcher object.
 *
 * @param string $type
 *
 * @return PHPUnit_Framework_Constraint_IsType
 */
function isType($type)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::isType',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_LessThan matcher object.
 *
 * @param mixed $value
 *
 * @return PHPUnit_Framework_Constraint_LessThan
 */
function lessThan($value)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::lessThan',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_Or matcher object that wraps
 * a PHPUnit_Framework_Constraint_IsEqual and a
 * PHPUnit_Framework_Constraint_LessThan matcher object.
 *
 * @param mixed $value
 *
 * @return PHPUnit_Framework_Constraint_Or
 */
function lessThanOrEqual($value)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::lessThanOrEqual',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_And matcher object.
 *
 * @return PHPUnit_Framework_Constraint_And
 */
function logicalAnd()
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::logicalAnd',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_Not matcher object.
 *
 * @param PHPUnit_Framework_Constraint $constraint
 *
 * @return PHPUnit_Framework_Constraint_Not
 */
function logicalNot(PHPUnit_Framework_Constraint $constraint)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::logicalNot',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_Or matcher object.
 *
 * @return PHPUnit_Framework_Constraint_Or
 */
function logicalOr()
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::logicalOr',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_Xor matcher object.
 *
 * @return PHPUnit_Framework_Constraint_Xor
 */
function logicalXor()
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::logicalXor',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_StringMatches matcher object.
 *
 * @param string $string
 *
 * @return PHPUnit_Framework_Constraint_StringMatches
 */
function matches($string)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::matches',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_PCREMatch matcher object.
 *
 * @param string $pattern
 *
 * @return PHPUnit_Framework_Constraint_PCREMatch
 */
function matchesRegularExpression($pattern)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::matchesRegularExpression',
        func_get_args()
    );
}

/**
 * Returns a matcher that matches when the method is never executed.
 *
 * @return PHPUnit_Framework_MockObject_Matcher_InvokedCount
 */
function never()
{
    return call_user_func_array(
        'PHPUnit_Framework_TestCase::never',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_ObjectHasAttribute matcher object.
 *
 * @param string $attributeName
 *
 * @return PHPUnit_Framework_Constraint_ObjectHasAttribute
 */
function objectHasAttribute($attributeName)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::objectHasAttribute',
        func_get_args()
    );
}

/**
 * @param mixed $value, ...
 *
 * @return PHPUnit_Framework_MockObject_Stub_ConsecutiveCalls
 */
function onConsecutiveCalls()
{
    return call_user_func_array(
        'PHPUnit_Framework_TestCase::onConsecutiveCalls',
        func_get_args()
    );
}

/**
 * Returns a matcher that matches when the method is executed exactly once.
 *
 * @return PHPUnit_Framework_MockObject_Matcher_InvokedCount
 */
function once()
{
    return call_user_func_array(
        'PHPUnit_Framework_TestCase::once',
        func_get_args()
    );
}

/**
 * @param int $argumentIndex
 *
 * @return PHPUnit_Framework_MockObject_Stub_ReturnArgument
 */
function returnArgument($argumentIndex)
{
    return call_user_func_array(
        'PHPUnit_Framework_TestCase::returnArgument',
        func_get_args()
    );
}

/**
 * @param mixed $callback
 *
 * @return PHPUnit_Framework_MockObject_Stub_ReturnCallback
 */
function returnCallback($callback)
{
    return call_user_func_array(
        'PHPUnit_Framework_TestCase::returnCallback',
        func_get_args()
    );
}

/**
 * Returns the current object.
 *
 * This method is useful when mocking a fluent interface.
 *
 * @return PHPUnit_Framework_MockObject_Stub_ReturnSelf
 */
function returnSelf()
{
    return call_user_func_array(
        'PHPUnit_Framework_TestCase::returnSelf',
        func_get_args()
    );
}

/**
 * @param mixed $value
 *
 * @return PHPUnit_Framework_MockObject_Stub_Return
 */
function returnValue($value)
{
    return call_user_func_array(
        'PHPUnit_Framework_TestCase::returnValue',
        func_get_args()
    );
}

/**
 * @param array $valueMap
 *
 * @return PHPUnit_Framework_MockObject_Stub_ReturnValueMap
 */
function returnValueMap(array $valueMap)
{
    return call_user_func_array(
        'PHPUnit_Framework_TestCase::returnValueMap',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_StringContains matcher object.
 *
 * @param string $string
 * @param bool   $case
 *
 * @return PHPUnit_Framework_Constraint_StringContains
 */
function stringContains($string, $case = true)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::stringContains',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_StringEndsWith matcher object.
 *
 * @param mixed $suffix
 *
 * @return PHPUnit_Framework_Constraint_StringEndsWith
 */
function stringEndsWith($suffix)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::stringEndsWith',
        func_get_args()
    );
}

/**
 * Returns a PHPUnit_Framework_Constraint_StringStartsWith matcher object.
 *
 * @param mixed $prefix
 *
 * @return PHPUnit_Framework_Constraint_StringStartsWith
 */
function stringStartsWith($prefix)
{
    return call_user_func_array(
        'PHPUnit_Framework_Assert::stringStartsWith',
        func_get_args()
    );
}

/**
 * @param Exception $exception
 *
 * @return PHPUnit_Framework_MockObject_Stub_Exception
 */
function throwException(Exception $exception)
{
    return call_user_func_array(
        'PHPUnit_Framework_TestCase::throwException',
        func_get_args()
    );
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A set of assertion methods.
 */
abstract class PHPUnit_Framework_Assert
{
    /**
     * @var int
     */
    private static $count = 0;

    /**
     * Asserts that an array has a specified key.
     *
     * @param mixed             $key
     * @param array|ArrayAccess $array
     * @param string            $message
     */
    public static function assertArrayHasKey($key, $array, $message = '')
    {
        if (!(is_int($key) || is_string($key))) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(
                1,
                'integer or string'
            );
        }

        if (!(is_array($array) || $array instanceof ArrayAccess)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(
                2,
                'array or ArrayAccess'
            );
        }

        $constraint = new PHPUnit_Framework_Constraint_ArrayHasKey($key);

        static::assertThat($array, $constraint, $message);
    }

    /**
     * Asserts that an array has a specified subset.
     *
     * @param array|ArrayAccess $subset
     * @param array|ArrayAccess $array
     * @param bool              $strict  Check for object identity
     * @param string            $message
     */
    public static function assertArraySubset($subset, $array, $strict = false, $message = '')
    {
        if (!(is_array($subset) || $subset instanceof ArrayAccess)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(
                1,
                'array or ArrayAccess'
            );
        }

        if (!(is_array($array) || $array instanceof ArrayAccess)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(
                2,
                'array or ArrayAccess'
            );
        }

        $constraint = new PHPUnit_Framework_Constraint_ArraySubset($subset, $strict);

        static::assertThat($array, $constraint, $message);
    }

    /**
     * Asserts that an array does not have a specified key.
     *
     * @param mixed             $key
     * @param array|ArrayAccess $array
     * @param string            $message
     */
    public static function assertArrayNotHasKey($key, $array, $message = '')
    {
        if (!(is_int($key) || is_string($key))) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(
                1,
                'integer or string'
            );
        }

        if (!(is_array($array) || $array instanceof ArrayAccess)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(
                2,
                'array or ArrayAccess'
            );
        }

        $constraint = new PHPUnit_Framework_Constraint_Not(
            new PHPUnit_Framework_Constraint_ArrayHasKey($key)
        );

        static::assertThat($array, $constraint, $message);
    }

    /**
     * Asserts that a haystack contains a needle.
     *
     * @param mixed  $needle
     * @param mixed  $haystack
     * @param string $message
     * @param bool   $ignoreCase
     * @param bool   $checkForObjectIdentity
     * @param bool   $checkForNonObjectIdentity
     */
    public static function assertContains($needle, $haystack, $message = '', $ignoreCase = false, $checkForObjectIdentity = true, $checkForNonObjectIdentity = false)
    {
        if (is_array($haystack) ||
            is_object($haystack) && $haystack instanceof Traversable) {
            $constraint = new PHPUnit_Framework_Constraint_TraversableContains(
                $needle,
                $checkForObjectIdentity,
                $checkForNonObjectIdentity
            );
        } elseif (is_string($haystack)) {
            if (!is_string($needle)) {
                throw PHPUnit_Util_InvalidArgumentHelper::factory(
                    1,
                    'string'
                );
            }

            $constraint = new PHPUnit_Framework_Constraint_StringContains(
                $needle,
                $ignoreCase
            );
        } else {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(
                2,
                'array, traversable or string'
            );
        }

        static::assertThat($haystack, $constraint, $message);
    }

    /**
     * Asserts that a haystack that is stored in a static attribute of a class
     * or an attribute of an object contains a needle.
     *
     * @param mixed         $needle
     * @param string        $haystackAttributeName
     * @param string|object $haystackClassOrObject
     * @param string        $message
     * @param bool          $ignoreCase
     * @param bool          $checkForObjectIdentity
     * @param bool          $checkForNonObjectIdentity
     */
    public static function assertAttributeContains($needle, $haystackAttributeName, $haystackClassOrObject, $message = '', $ignoreCase = false, $checkForObjectIdentity = true, $checkForNonObjectIdentity = false)
    {
        static::assertContains(
            $needle,
            static::readAttribute($haystackClassOrObject, $haystackAttributeName),
            $message,
            $ignoreCase,
            $checkForObjectIdentity,
            $checkForNonObjectIdentity
        );
    }

    /**
     * Asserts that a haystack does not contain a needle.
     *
     * @param mixed  $needle
     * @param mixed  $haystack
     * @param string $message
     * @param bool   $ignoreCase
     * @param bool   $checkForObjectIdentity
     * @param bool   $checkForNonObjectIdentity
     */
    public static function assertNotContains($needle, $haystack, $message = '', $ignoreCase = false, $checkForObjectIdentity = true, $checkForNonObjectIdentity = false)
    {
        if (is_array($haystack) ||
            is_object($haystack) && $haystack instanceof Traversable) {
            $constraint = new PHPUnit_Framework_Constraint_Not(
                new PHPUnit_Framework_Constraint_TraversableContains(
                    $needle,
                    $checkForObjectIdentity,
                    $checkForNonObjectIdentity
                )
            );
        } elseif (is_string($haystack)) {
            if (!is_string($needle)) {
                throw PHPUnit_Util_InvalidArgumentHelper::factory(
                    1,
                    'string'
                );
            }

            $constraint = new PHPUnit_Framework_Constraint_Not(
                new PHPUnit_Framework_Constraint_StringContains(
                    $needle,
                    $ignoreCase
                )
            );
        } else {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(
                2,
                'array, traversable or string'
            );
        }

        static::assertThat($haystack, $constraint, $message);
    }

    /**
     * Asserts that a haystack that is stored in a static attribute of a class
     * or an attribute of an object does not contain a needle.
     *
     * @param mixed         $needle
     * @param string        $haystackAttributeName
     * @param string|object $haystackClassOrObject
     * @param string        $message
     * @param bool          $ignoreCase
     * @param bool          $checkForObjectIdentity
     * @param bool          $checkForNonObjectIdentity
     */
    public static function assertAttributeNotContains($needle, $haystackAttributeName, $haystackClassOrObject, $message = '', $ignoreCase = false, $checkForObjectIdentity = true, $checkForNonObjectIdentity = false)
    {
        static::assertNotContains(
            $needle,
            static::readAttribute($haystackClassOrObject, $haystackAttributeName),
            $message,
            $ignoreCase,
            $checkForObjectIdentity,
            $checkForNonObjectIdentity
        );
    }

    /**
     * Asserts that a haystack contains only values of a given type.
     *
     * @param string $type
     * @param mixed  $haystack
     * @param bool   $isNativeType
     * @param string $message
     */
    public static function assertContainsOnly($type, $haystack, $isNativeType = null, $message = '')
    {
        if (!(is_array($haystack) ||
            is_object($haystack) && $haystack instanceof Traversable)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(
                2,
                'array or traversable'
            );
        }

        if ($isNativeType == null) {
            $isNativeType = PHPUnit_Util_Type::isType($type);
        }

        static::assertThat(
            $haystack,
            new PHPUnit_Framework_Constraint_TraversableContainsOnly(
                $type,
                $isNativeType
            ),
            $message
        );
    }

    /**
     * Asserts that a haystack contains only instances of a given classname
     *
     * @param string            $classname
     * @param array|Traversable $haystack
     * @param string            $message
     */
    public static function assertContainsOnlyInstancesOf($classname, $haystack, $message = '')
    {
        if (!(is_array($haystack) ||
            is_object($haystack) && $haystack instanceof Traversable)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(
                2,
                'array or traversable'
            );
        }

        static::assertThat(
            $haystack,
            new PHPUnit_Framework_Constraint_TraversableContainsOnly(
                $classname,
                false
            ),
            $message
        );
    }

    /**
     * Asserts that a haystack that is stored in a static attribute of a class
     * or an attribute of an object contains only values of a given type.
     *
     * @param string        $type
     * @param string        $haystackAttributeName
     * @param string|object $haystackClassOrObject
     * @param bool          $isNativeType
     * @param string        $message
     */
    public static function assertAttributeContainsOnly($type, $haystackAttributeName, $haystackClassOrObject, $isNativeType = null, $message = '')
    {
        static::assertContainsOnly(
            $type,
            static::readAttribute($haystackClassOrObject, $haystackAttributeName),
            $isNativeType,
            $message
        );
    }

    /**
     * Asserts that a haystack does not contain only values of a given type.
     *
     * @param string $type
     * @param mixed  $haystack
     * @param bool   $isNativeType
     * @param string $message
     */
    public static function assertNotContainsOnly($type, $haystack, $isNativeType = null, $message = '')
    {
        if (!(is_array($haystack) ||
            is_object($haystack) && $haystack instanceof Traversable)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(
                2,
                'array or traversable'
            );
        }

        if ($isNativeType == null) {
            $isNativeType = PHPUnit_Util_Type::isType($type);
        }

        static::assertThat(
            $haystack,
            new PHPUnit_Framework_Constraint_Not(
                new PHPUnit_Framework_Constraint_TraversableContainsOnly(
                    $type,
                    $isNativeType
                )
            ),
            $message
        );
    }

    /**
     * Asserts that a haystack that is stored in a static attribute of a class
     * or an attribute of an object does not contain only values of a given
     * type.
     *
     * @param string        $type
     * @param string        $haystackAttributeName
     * @param string|object $haystackClassOrObject
     * @param bool          $isNativeType
     * @param string        $message
     */
    public static function assertAttributeNotContainsOnly($type, $haystackAttributeName, $haystackClassOrObject, $isNativeType = null, $message = '')
    {
        static::assertNotContainsOnly(
            $type,
            static::readAttribute($haystackClassOrObject, $haystackAttributeName),
            $isNativeType,
            $message
        );
    }

    /**
     * Asserts the number of elements of an array, Countable or Traversable.
     *
     * @param int    $expectedCount
     * @param mixed  $haystack
     * @param string $message
     */
    public static function assertCount($expectedCount, $haystack, $message = '')
    {
        if (!is_int($expectedCount)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'integer');
        }

        if (!$haystack instanceof Countable &&
            !$haystack instanceof Traversable &&
            !is_array($haystack)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'countable or traversable');
        }

        static::assertThat(
            $haystack,
            new PHPUnit_Framework_Constraint_Count($expectedCount),
            $message
        );
    }

    /**
     * Asserts the number of elements of an array, Countable or Traversable
     * that is stored in an attribute.
     *
     * @param int           $expectedCount
     * @param string        $haystackAttributeName
     * @param string|object $haystackClassOrObject
     * @param string        $message
     */
    public static function assertAttributeCount($expectedCount, $haystackAttributeName, $haystackClassOrObject, $message = '')
    {
        static::assertCount(
            $expectedCount,
            static::readAttribute($haystackClassOrObject, $haystackAttributeName),
            $message
        );
    }

    /**
     * Asserts the number of elements of an array, Countable or Traversable.
     *
     * @param int    $expectedCount
     * @param mixed  $haystack
     * @param string $message
     */
    public static function assertNotCount($expectedCount, $haystack, $message = '')
    {
        if (!is_int($expectedCount)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'integer');
        }

        if (!$haystack instanceof Countable &&
            !$haystack instanceof Traversable &&
            !is_array($haystack)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'countable or traversable');
        }

        $constraint = new PHPUnit_Framework_Constraint_Not(
            new PHPUnit_Framework_Constraint_Count($expectedCount)
        );

        static::assertThat($haystack, $constraint, $message);
    }

    /**
     * Asserts the number of elements of an array, Countable or Traversable
     * that is stored in an attribute.
     *
     * @param int           $expectedCount
     * @param string        $haystackAttributeName
     * @param string|object $haystackClassOrObject
     * @param string        $message
     */
    public static function assertAttributeNotCount($expectedCount, $haystackAttributeName, $haystackClassOrObject, $message = '')
    {
        static::assertNotCount(
            $expectedCount,
            static::readAttribute($haystackClassOrObject, $haystackAttributeName),
            $message
        );
    }

    /**
     * Asserts that two variables are equal.
     *
     * @param mixed  $expected
     * @param mixed  $actual
     * @param string $message
     * @param float  $delta
     * @param int    $maxDepth
     * @param bool   $canonicalize
     * @param bool   $ignoreCase
     */
    public static function assertEquals($expected, $actual, $message = '', $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false)
    {
        $constraint = new PHPUnit_Framework_Constraint_IsEqual(
            $expected,
            $delta,
            $maxDepth,
            $canonicalize,
            $ignoreCase
        );

        static::assertThat($actual, $constraint, $message);
    }

    /**
     * Asserts that a variable is equal to an attribute of an object.
     *
     * @param mixed         $expected
     * @param string        $actualAttributeName
     * @param string|object $actualClassOrObject
     * @param string        $message
     * @param float         $delta
     * @param int           $maxDepth
     * @param bool          $canonicalize
     * @param bool          $ignoreCase
     */
    public static function assertAttributeEquals($expected, $actualAttributeName, $actualClassOrObject, $message = '', $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false)
    {
        static::assertEquals(
            $expected,
            static::readAttribute($actualClassOrObject, $actualAttributeName),
            $message,
            $delta,
            $maxDepth,
            $canonicalize,
            $ignoreCase
        );
    }

    /**
     * Asserts that two variables are not equal.
     *
     * @param mixed  $expected
     * @param mixed  $actual
     * @param string $message
     * @param float  $delta
     * @param int    $maxDepth
     * @param bool   $canonicalize
     * @param bool   $ignoreCase
     */
    public static function assertNotEquals($expected, $actual, $message = '', $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false)
    {
        $constraint = new PHPUnit_Framework_Constraint_Not(
            new PHPUnit_Framework_Constraint_IsEqual(
                $expected,
                $delta,
                $maxDepth,
                $canonicalize,
                $ignoreCase
            )
        );

        static::assertThat($actual, $constraint, $message);
    }

    /**
     * Asserts that a variable is not equal to an attribute of an object.
     *
     * @param mixed         $expected
     * @param string        $actualAttributeName
     * @param string|object $actualClassOrObject
     * @param string        $message
     * @param float         $delta
     * @param int           $maxDepth
     * @param bool          $canonicalize
     * @param bool          $ignoreCase
     */
    public static function assertAttributeNotEquals($expected, $actualAttributeName, $actualClassOrObject, $message = '', $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false)
    {
        static::assertNotEquals(
            $expected,
            static::readAttribute($actualClassOrObject, $actualAttributeName),
            $message,
            $delta,
            $maxDepth,
            $canonicalize,
            $ignoreCase
        );
    }

    /**
     * Asserts that a variable is empty.
     *
     * @param mixed  $actual
     * @param string $message
     *
     * @throws PHPUnit_Framework_AssertionFailedError
     */
    public static function assertEmpty($actual, $message = '')
    {
        static::assertThat($actual, static::isEmpty(), $message);
    }

    /**
     * Asserts that a static attribute of a class or an attribute of an object
     * is empty.
     *
     * @param string        $haystackAttributeName
     * @param string|object $haystackClassOrObject
     * @param string        $message
     */
    public static function assertAttributeEmpty($haystackAttributeName, $haystackClassOrObject, $message = '')
    {
        static::assertEmpty(
            static::readAttribute($haystackClassOrObject, $haystackAttributeName),
            $message
        );
    }

    /**
     * Asserts that a variable is not empty.
     *
     * @param mixed  $actual
     * @param string $message
     *
     * @throws PHPUnit_Framework_AssertionFailedError
     */
    public static function assertNotEmpty($actual, $message = '')
    {
        static::assertThat($actual, static::logicalNot(static::isEmpty()), $message);
    }

    /**
     * Asserts that a static attribute of a class or an attribute of an object
     * is not empty.
     *
     * @param string        $haystackAttributeName
     * @param string|object $haystackClassOrObject
     * @param string        $message
     */
    public static function assertAttributeNotEmpty($haystackAttributeName, $haystackClassOrObject, $message = '')
    {
        static::assertNotEmpty(
            static::readAttribute($haystackClassOrObject, $haystackAttributeName),
            $message
        );
    }

    /**
     * Asserts that a value is greater than another value.
     *
     * @param mixed  $expected
     * @param mixed  $actual
     * @param string $message
     */
    public static function assertGreaterThan($expected, $actual, $message = '')
    {
        static::assertThat($actual, static::greaterThan($expected), $message);
    }

    /**
     * Asserts that an attribute is greater than another value.
     *
     * @param mixed         $expected
     * @param string        $actualAttributeName
     * @param string|object $actualClassOrObject
     * @param string        $message
     */
    public static function assertAttributeGreaterThan($expected, $actualAttributeName, $actualClassOrObject, $message = '')
    {
        static::assertGreaterThan(
            $expected,
            static::readAttribute($actualClassOrObject, $actualAttributeName),
            $message
        );
    }

    /**
     * Asserts that a value is greater than or equal to another value.
     *
     * @param mixed  $expected
     * @param mixed  $actual
     * @param string $message
     */
    public static function assertGreaterThanOrEqual($expected, $actual, $message = '')
    {
        static::assertThat(
            $actual,
            static::greaterThanOrEqual($expected),
            $message
        );
    }

    /**
     * Asserts that an attribute is greater than or equal to another value.
     *
     * @param mixed         $expected
     * @param string        $actualAttributeName
     * @param string|object $actualClassOrObject
     * @param string        $message
     */
    public static function assertAttributeGreaterThanOrEqual($expected, $actualAttributeName, $actualClassOrObject, $message = '')
    {
        static::assertGreaterThanOrEqual(
            $expected,
            static::readAttribute($actualClassOrObject, $actualAttributeName),
            $message
        );
    }

    /**
     * Asserts that a value is smaller than another value.
     *
     * @param mixed  $expected
     * @param mixed  $actual
     * @param string $message
     */
    public static function assertLessThan($expected, $actual, $message = '')
    {
        static::assertThat($actual, static::lessThan($expected), $message);
    }

    /**
     * Asserts that an attribute is smaller than another value.
     *
     * @param mixed         $expected
     * @param string        $actualAttributeName
     * @param string|object $actualClassOrObject
     * @param string        $message
     */
    public static function assertAttributeLessThan($expected, $actualAttributeName, $actualClassOrObject, $message = '')
    {
        static::assertLessThan(
            $expected,
            static::readAttribute($actualClassOrObject, $actualAttributeName),
            $message
        );
    }

    /**
     * Asserts that a value is smaller than or equal to another value.
     *
     * @param mixed  $expected
     * @param mixed  $actual
     * @param string $message
     */
    public static function assertLessThanOrEqual($expected, $actual, $message = '')
    {
        static::assertThat($actual, static::lessThanOrEqual($expected), $message);
    }

    /**
     * Asserts that an attribute is smaller than or equal to another value.
     *
     * @param mixed         $expected
     * @param string        $actualAttributeName
     * @param string|object $actualClassOrObject
     * @param string        $message
     */
    public static function assertAttributeLessThanOrEqual($expected, $actualAttributeName, $actualClassOrObject, $message = '')
    {
        static::assertLessThanOrEqual(
            $expected,
            static::readAttribute($actualClassOrObject, $actualAttributeName),
            $message
        );
    }

    /**
     * Asserts that the contents of one file is equal to the contents of another
     * file.
     *
     * @param string $expected
     * @param string $actual
     * @param string $message
     * @param bool   $canonicalize
     * @param bool   $ignoreCase
     */
    public static function assertFileEquals($expected, $actual, $message = '', $canonicalize = false, $ignoreCase = false)
    {
        static::assertFileExists($expected, $message);
        static::assertFileExists($actual, $message);

        static::assertEquals(
            file_get_contents($expected),
            file_get_contents($actual),
            $message,
            0,
            10,
            $canonicalize,
            $ignoreCase
        );
    }

    /**
     * Asserts that the contents of one file is not equal to the contents of
     * another file.
     *
     * @param string $expected
     * @param string $actual
     * @param string $message
     * @param bool   $canonicalize
     * @param bool   $ignoreCase
     */
    public static function assertFileNotEquals($expected, $actual, $message = '', $canonicalize = false, $ignoreCase = false)
    {
        static::assertFileExists($expected, $message);
        static::assertFileExists($actual, $message);

        static::assertNotEquals(
            file_get_contents($expected),
            file_get_contents($actual),
            $message,
            0,
            10,
            $canonicalize,
            $ignoreCase
        );
    }

    /**
     * Asserts that the contents of a string is equal
     * to the contents of a file.
     *
     * @param string $expectedFile
     * @param string $actualString
     * @param string $message
     * @param bool   $canonicalize
     * @param bool   $ignoreCase
     */
    public static function assertStringEqualsFile($expectedFile, $actualString, $message = '', $canonicalize = false, $ignoreCase = false)
    {
        static::assertFileExists($expectedFile, $message);

        static::assertEquals(
            file_get_contents($expectedFile),
            $actualString,
            $message,
            0,
            10,
            $canonicalize,
            $ignoreCase
        );
    }

    /**
     * Asserts that the contents of a string is not equal
     * to the contents of a file.
     *
     * @param string $expectedFile
     * @param string $actualString
     * @param string $message
     * @param bool   $canonicalize
     * @param bool   $ignoreCase
     */
    public static function assertStringNotEqualsFile($expectedFile, $actualString, $message = '', $canonicalize = false, $ignoreCase = false)
    {
        static::assertFileExists($expectedFile, $message);

        static::assertNotEquals(
            file_get_contents($expectedFile),
            $actualString,
            $message,
            0,
            10,
            $canonicalize,
            $ignoreCase
        );
    }

    /**
     * Asserts that a file/dir is readable.
     *
     * @param string $filename
     * @param string $message
     */
    public static function assertIsReadable($filename, $message = '')
    {
        if (!is_string($filename)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        $constraint = new PHPUnit_Framework_Constraint_IsReadable;

        static::assertThat($filename, $constraint, $message);
    }

    /**
     * Asserts that a file/dir exists and is not readable.
     *
     * @param string $filename
     * @param string $message
     */
    public static function assertNotIsReadable($filename, $message = '')
    {
        if (!is_string($filename)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        $constraint = new PHPUnit_Framework_Constraint_Not(
            new PHPUnit_Framework_Constraint_IsReadable
        );

        static::assertThat($filename, $constraint, $message);
    }

    /**
     * Asserts that a file/dir exists and is writable.
     *
     * @param string $filename
     * @param string $message
     */
    public static function assertIsWritable($filename, $message = '')
    {
        if (!is_string($filename)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        $constraint = new PHPUnit_Framework_Constraint_IsWritable;

        static::assertThat($filename, $constraint, $message);
    }

    /**
     * Asserts that a file/dir exists and is not writable.
     *
     * @param string $filename
     * @param string $message
     */
    public static function assertNotIsWritable($filename, $message = '')
    {
        if (!is_string($filename)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        $constraint = new PHPUnit_Framework_Constraint_Not(
            new PHPUnit_Framework_Constraint_IsWritable
        );

        static::assertThat($filename, $constraint, $message);
    }

    /**
     * Asserts that a directory exists.
     *
     * @param string $directory
     * @param string $message
     */
    public static function assertDirectoryExists($directory, $message = '')
    {
        if (!is_string($directory)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        $constraint = new PHPUnit_Framework_Constraint_DirectoryExists;

        static::assertThat($directory, $constraint, $message);
    }

    /**
     * Asserts that a directory does not exist.
     *
     * @param string $directory
     * @param string $message
     */
    public static function assertDirectoryNotExists($directory, $message = '')
    {
        if (!is_string($directory)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        $constraint = new PHPUnit_Framework_Constraint_Not(
            new PHPUnit_Framework_Constraint_DirectoryExists
        );

        static::assertThat($directory, $constraint, $message);
    }

    /**
     * Asserts that a directory exists and is readable.
     *
     * @param string $directory
     * @param string $message
     */
    public static function assertDirectoryIsReadable($directory, $message = '')
    {
        self::assertDirectoryExists($directory, $message);
        self::assertIsReadable($directory, $message);
    }

    /**
     * Asserts that a directory exists and is not readable.
     *
     * @param string $directory
     * @param string $message
     */
    public static function assertDirectoryNotIsReadable($directory, $message = '')
    {
        self::assertDirectoryExists($directory, $message);
        self::assertNotIsReadable($directory, $message);
    }

    /**
     * Asserts that a directory exists and is writable.
     *
     * @param string $directory
     * @param string $message
     */
    public static function assertDirectoryIsWritable($directory, $message = '')
    {
        self::assertDirectoryExists($directory, $message);
        self::assertIsWritable($directory, $message);
    }

    /**
     * Asserts that a directory exists and is not writable.
     *
     * @param string $directory
     * @param string $message
     */
    public static function assertDirectoryNotIsWritable($directory, $message = '')
    {
        self::assertDirectoryExists($directory, $message);
        self::assertNotIsWritable($directory, $message);
    }

    /**
     * Asserts that a file exists.
     *
     * @param string $filename
     * @param string $message
     */
    public static function assertFileExists($filename, $message = '')
    {
        if (!is_string($filename)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        $constraint = new PHPUnit_Framework_Constraint_FileExists;

        static::assertThat($filename, $constraint, $message);
    }

    /**
     * Asserts that a file does not exist.
     *
     * @param string $filename
     * @param string $message
     */
    public static function assertFileNotExists($filename, $message = '')
    {
        if (!is_string($filename)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        $constraint = new PHPUnit_Framework_Constraint_Not(
            new PHPUnit_Framework_Constraint_FileExists
        );

        static::assertThat($filename, $constraint, $message);
    }

    /**
     * Asserts that a file exists and is readable.
     *
     * @param string $file
     * @param string $message
     */
    public static function assertFileIsReadable($file, $message = '')
    {
        self::assertFileExists($file, $message);
        self::assertIsReadable($file, $message);
    }

    /**
     * Asserts that a file exists and is not readable.
     *
     * @param string $file
     * @param string $message
     */
    public static function assertFileNotIsReadable($file, $message = '')
    {
        self::assertFileExists($file, $message);
        self::assertNotIsReadable($file, $message);
    }

    /**
     * Asserts that a file exists and is writable.
     *
     * @param string $file
     * @param string $message
     */
    public static function assertFileIsWritable($file, $message = '')
    {
        self::assertFileExists($file, $message);
        self::assertIsWritable($file, $message);
    }

    /**
     * Asserts that a file exists and is not writable.
     *
     * @param string $file
     * @param string $message
     */
    public static function assertFileNotIsWritable($file, $message = '')
    {
        self::assertFileExists($file, $message);
        self::assertNotIsWritable($file, $message);
    }

    /**
     * Asserts that a condition is true.
     *
     * @param bool   $condition
     * @param string $message
     *
     * @throws PHPUnit_Framework_AssertionFailedError
     */
    public static function assertTrue($condition, $message = '')
    {
        static::assertThat($condition, static::isTrue(), $message);
    }

    /**
     * Asserts that a condition is not true.
     *
     * @param bool   $condition
     * @param string $message
     *
     * @throws PHPUnit_Framework_AssertionFailedError
     */
    public static function assertNotTrue($condition, $message = '')
    {
        static::assertThat($condition, static::logicalNot(static::isTrue()), $message);
    }

    /**
     * Asserts that a condition is false.
     *
     * @param bool   $condition
     * @param string $message
     *
     * @throws PHPUnit_Framework_AssertionFailedError
     */
    public static function assertFalse($condition, $message = '')
    {
        static::assertThat($condition, static::isFalse(), $message);
    }

    /**
     * Asserts that a condition is not false.
     *
     * @param bool   $condition
     * @param string $message
     *
     * @throws PHPUnit_Framework_AssertionFailedError
     */
    public static function assertNotFalse($condition, $message = '')
    {
        static::assertThat($condition, static::logicalNot(static::isFalse()), $message);
    }

    /**
     * Asserts that a variable is null.
     *
     * @param mixed  $actual
     * @param string $message
     */
    public static function assertNull($actual, $message = '')
    {
        static::assertThat($actual, static::isNull(), $message);
    }

    /**
     * Asserts that a variable is not null.
     *
     * @param mixed  $actual
     * @param string $message
     */
    public static function assertNotNull($actual, $message = '')
    {
        static::assertThat($actual, static::logicalNot(static::isNull()), $message);
    }

    /**
     * Asserts that a variable is finite.
     *
     * @param mixed  $actual
     * @param string $message
     */
    public static function assertFinite($actual, $message = '')
    {
        static::assertThat($actual, static::isFinite(), $message);
    }

    /**
     * Asserts that a variable is infinite.
     *
     * @param mixed  $actual
     * @param string $message
     */
    public static function assertInfinite($actual, $message = '')
    {
        static::assertThat($actual, static::isInfinite(), $message);
    }

    /**
     * Asserts that a variable is nan.
     *
     * @param mixed  $actual
     * @param string $message
     */
    public static function assertNan($actual, $message = '')
    {
        static::assertThat($actual, static::isNan(), $message);
    }

    /**
     * Asserts that a class has a specified attribute.
     *
     * @param string $attributeName
     * @param string $className
     * @param string $message
     */
    public static function assertClassHasAttribute($attributeName, $className, $message = '')
    {
        if (!is_string($attributeName)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        if (!preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $attributeName)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'valid attribute name');
        }

        if (!is_string($className) || !class_exists($className)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'class name', $className);
        }

        $constraint = new PHPUnit_Framework_Constraint_ClassHasAttribute(
            $attributeName
        );

        static::assertThat($className, $constraint, $message);
    }

    /**
     * Asserts that a class does not have a specified attribute.
     *
     * @param string $attributeName
     * @param string $className
     * @param string $message
     */
    public static function assertClassNotHasAttribute($attributeName, $className, $message = '')
    {
        if (!is_string($attributeName)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        if (!preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $attributeName)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'valid attribute name');
        }

        if (!is_string($className) || !class_exists($className)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'class name', $className);
        }

        $constraint = new PHPUnit_Framework_Constraint_Not(
            new PHPUnit_Framework_Constraint_ClassHasAttribute($attributeName)
        );

        static::assertThat($className, $constraint, $message);
    }

    /**
     * Asserts that a class has a specified static attribute.
     *
     * @param string $attributeName
     * @param string $className
     * @param string $message
     */
    public static function assertClassHasStaticAttribute($attributeName, $className, $message = '')
    {
        if (!is_string($attributeName)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        if (!preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $attributeName)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'valid attribute name');
        }

        if (!is_string($className) || !class_exists($className)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'class name', $className);
        }

        $constraint = new PHPUnit_Framework_Constraint_ClassHasStaticAttribute(
            $attributeName
        );

        static::assertThat($className, $constraint, $message);
    }

    /**
     * Asserts that a class does not have a specified static attribute.
     *
     * @param string $attributeName
     * @param string $className
     * @param string $message
     */
    public static function assertClassNotHasStaticAttribute($attributeName, $className, $message = '')
    {
        if (!is_string($attributeName)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        if (!preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $attributeName)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'valid attribute name');
        }

        if (!is_string($className) || !class_exists($className)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'class name', $className);
        }

        $constraint = new PHPUnit_Framework_Constraint_Not(
            new PHPUnit_Framework_Constraint_ClassHasStaticAttribute(
                $attributeName
            )
        );

        static::assertThat($className, $constraint, $message);
    }

    /**
     * Asserts that an object has a specified attribute.
     *
     * @param string $attributeName
     * @param object $object
     * @param string $message
     */
    public static function assertObjectHasAttribute($attributeName, $object, $message = '')
    {
        if (!is_string($attributeName)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        if (!preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $attributeName)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'valid attribute name');
        }

        if (!is_object($object)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'object');
        }

        $constraint = new PHPUnit_Framework_Constraint_ObjectHasAttribute(
            $attributeName
        );

        static::assertThat($object, $constraint, $message);
    }

    /**
     * Asserts that an object does not have a specified attribute.
     *
     * @param string $attributeName
     * @param object $object
     * @param string $message
     */
    public static function assertObjectNotHasAttribute($attributeName, $object, $message = '')
    {
        if (!is_string($attributeName)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        if (!preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $attributeName)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'valid attribute name');
        }

        if (!is_object($object)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'object');
        }

        $constraint = new PHPUnit_Framework_Constraint_Not(
            new PHPUnit_Framework_Constraint_ObjectHasAttribute($attributeName)
        );

        static::assertThat($object, $constraint, $message);
    }

    /**
     * Asserts that two variables have the same type and value.
     * Used on objects, it asserts that two variables reference
     * the same object.
     *
     * @param mixed  $expected
     * @param mixed  $actual
     * @param string $message
     */
    public static function assertSame($expected, $actual, $message = '')
    {
        if (is_bool($expected) && is_bool($actual)) {
            static::assertEquals($expected, $actual, $message);
        } else {
            $constraint = new PHPUnit_Framework_Constraint_IsIdentical(
                $expected
            );

            static::assertThat($actual, $constraint, $message);
        }
    }

    /**
     * Asserts that a variable and an attribute of an object have the same type
     * and value.
     *
     * @param mixed         $expected
     * @param string        $actualAttributeName
     * @param string|object $actualClassOrObject
     * @param string        $message
     */
    public static function assertAttributeSame($expected, $actualAttributeName, $actualClassOrObject, $message = '')
    {
        static::assertSame(
            $expected,
            static::readAttribute($actualClassOrObject, $actualAttributeName),
            $message
        );
    }

    /**
     * Asserts that two variables do not have the same type and value.
     * Used on objects, it asserts that two variables do not reference
     * the same object.
     *
     * @param mixed  $expected
     * @param mixed  $actual
     * @param string $message
     */
    public static function assertNotSame($expected, $actual, $message = '')
    {
        if (is_bool($expected) && is_bool($actual)) {
            static::assertNotEquals($expected, $actual, $message);
        } else {
            $constraint = new PHPUnit_Framework_Constraint_Not(
                new PHPUnit_Framework_Constraint_IsIdentical($expected)
            );

            static::assertThat($actual, $constraint, $message);
        }
    }

    /**
     * Asserts that a variable and an attribute of an object do not have the
     * same type and value.
     *
     * @param mixed         $expected
     * @param string        $actualAttributeName
     * @param string|object $actualClassOrObject
     * @param string        $message
     */
    public static function assertAttributeNotSame($expected, $actualAttributeName, $actualClassOrObject, $message = '')
    {
        static::assertNotSame(
            $expected,
            static::readAttribute($actualClassOrObject, $actualAttributeName),
            $message
        );
    }

    /**
     * Asserts that a variable is of a given type.
     *
     * @param string $expected
     * @param mixed  $actual
     * @param string $message
     */
    public static function assertInstanceOf($expected, $actual, $message = '')
    {
        if (!(is_string($expected) && (class_exists($expected) || interface_exists($expected)))) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'class or interface name');
        }

        $constraint = new PHPUnit_Framework_Constraint_IsInstanceOf(
            $expected
        );

        static::assertThat($actual, $constraint, $message);
    }

    /**
     * Asserts that an attribute is of a given type.
     *
     * @param string        $expected
     * @param string        $attributeName
     * @param string|object $classOrObject
     * @param string        $message
     */
    public static function assertAttributeInstanceOf($expected, $attributeName, $classOrObject, $message = '')
    {
        static::assertInstanceOf(
            $expected,
            static::readAttribute($classOrObject, $attributeName),
            $message
        );
    }

    /**
     * Asserts that a variable is not of a given type.
     *
     * @param string $expected
     * @param mixed  $actual
     * @param string $message
     */
    public static function assertNotInstanceOf($expected, $actual, $message = '')
    {
        if (!(is_string($expected) && (class_exists($expected) || interface_exists($expected)))) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'class or interface name');
        }

        $constraint = new PHPUnit_Framework_Constraint_Not(
            new PHPUnit_Framework_Constraint_IsInstanceOf($expected)
        );

        static::assertThat($actual, $constraint, $message);
    }

    /**
     * Asserts that an attribute is of a given type.
     *
     * @param string        $expected
     * @param string        $attributeName
     * @param string|object $classOrObject
     * @param string        $message
     */
    public static function assertAttributeNotInstanceOf($expected, $attributeName, $classOrObject, $message = '')
    {
        static::assertNotInstanceOf(
            $expected,
            static::readAttribute($classOrObject, $attributeName),
            $message
        );
    }

    /**
     * Asserts that a variable is of a given type.
     *
     * @param string $expected
     * @param mixed  $actual
     * @param string $message
     */
    public static function assertInternalType($expected, $actual, $message = '')
    {
        if (!is_string($expected)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        $constraint = new PHPUnit_Framework_Constraint_IsType(
            $expected
        );

        static::assertThat($actual, $constraint, $message);
    }

    /**
     * Asserts that an attribute is of a given type.
     *
     * @param string        $expected
     * @param string        $attributeName
     * @param string|object $classOrObject
     * @param string        $message
     */
    public static function assertAttributeInternalType($expected, $attributeName, $classOrObject, $message = '')
    {
        static::assertInternalType(
            $expected,
            static::readAttribute($classOrObject, $attributeName),
            $message
        );
    }

    /**
     * Asserts that a variable is not of a given type.
     *
     * @param string $expected
     * @param mixed  $actual
     * @param string $message
     */
    public static function assertNotInternalType($expected, $actual, $message = '')
    {
        if (!is_string($expected)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        $constraint = new PHPUnit_Framework_Constraint_Not(
            new PHPUnit_Framework_Constraint_IsType($expected)
        );

        static::assertThat($actual, $constraint, $message);
    }

    /**
     * Asserts that an attribute is of a given type.
     *
     * @param string        $expected
     * @param string        $attributeName
     * @param string|object $classOrObject
     * @param string        $message
     */
    public static function assertAttributeNotInternalType($expected, $attributeName, $classOrObject, $message = '')
    {
        static::assertNotInternalType(
            $expected,
            static::readAttribute($classOrObject, $attributeName),
            $message
        );
    }

    /**
     * Asserts that a string matches a given regular expression.
     *
     * @param string $pattern
     * @param string $string
     * @param string $message
     */
    public static function assertRegExp($pattern, $string, $message = '')
    {
        if (!is_string($pattern)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        if (!is_string($string)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'string');
        }

        $constraint = new PHPUnit_Framework_Constraint_PCREMatch($pattern);

        static::assertThat($string, $constraint, $message);
    }

    /**
     * Asserts that a string does not match a given regular expression.
     *
     * @param string $pattern
     * @param string $string
     * @param string $message
     */
    public static function assertNotRegExp($pattern, $string, $message = '')
    {
        if (!is_string($pattern)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        if (!is_string($string)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'string');
        }

        $constraint = new PHPUnit_Framework_Constraint_Not(
            new PHPUnit_Framework_Constraint_PCREMatch($pattern)
        );

        static::assertThat($string, $constraint, $message);
    }

    /**
     * Assert that the size of two arrays (or `Countable` or `Traversable` objects)
     * is the same.
     *
     * @param array|Countable|Traversable $expected
     * @param array|Countable|Traversable $actual
     * @param string                      $message
     */
    public static function assertSameSize($expected, $actual, $message = '')
    {
        if (!$expected instanceof Countable &&
            !$expected instanceof Traversable &&
            !is_array($expected)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'countable or traversable');
        }

        if (!$actual instanceof Countable &&
            !$actual instanceof Traversable &&
            !is_array($actual)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'countable or traversable');
        }

        static::assertThat(
            $actual,
            new PHPUnit_Framework_Constraint_SameSize($expected),
            $message
        );
    }

    /**
     * Assert that the size of two arrays (or `Countable` or `Traversable` objects)
     * is not the same.
     *
     * @param array|Countable|Traversable $expected
     * @param array|Countable|Traversable $actual
     * @param string                      $message
     */
    public static function assertNotSameSize($expected, $actual, $message = '')
    {
        if (!$expected instanceof Countable &&
            !$expected instanceof Traversable &&
            !is_array($expected)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'countable or traversable');
        }

        if (!$actual instanceof Countable &&
            !$actual instanceof Traversable &&
            !is_array($actual)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'countable or traversable');
        }

        $constraint = new PHPUnit_Framework_Constraint_Not(
            new PHPUnit_Framework_Constraint_SameSize($expected)
        );

        static::assertThat($actual, $constraint, $message);
    }

    /**
     * Asserts that a string matches a given format string.
     *
     * @param string $format
     * @param string $string
     * @param string $message
     */
    public static function assertStringMatchesFormat($format, $string, $message = '')
    {
        if (!is_string($format)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        if (!is_string($string)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'string');
        }

        $constraint = new PHPUnit_Framework_Constraint_StringMatches($format);

        static::assertThat($string, $constraint, $message);
    }

    /**
     * Asserts that a string does not match a given format string.
     *
     * @param string $format
     * @param string $string
     * @param string $message
     */
    public static function assertStringNotMatchesFormat($format, $string, $message = '')
    {
        if (!is_string($format)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        if (!is_string($string)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'string');
        }

        $constraint = new PHPUnit_Framework_Constraint_Not(
            new PHPUnit_Framework_Constraint_StringMatches($format)
        );

        static::assertThat($string, $constraint, $message);
    }

    /**
     * Asserts that a string matches a given format file.
     *
     * @param string $formatFile
     * @param string $string
     * @param string $message
     */
    public static function assertStringMatchesFormatFile($formatFile, $string, $message = '')
    {
        static::assertFileExists($formatFile, $message);

        if (!is_string($string)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'string');
        }

        $constraint = new PHPUnit_Framework_Constraint_StringMatches(
            file_get_contents($formatFile)
        );

        static::assertThat($string, $constraint, $message);
    }

    /**
     * Asserts that a string does not match a given format string.
     *
     * @param string $formatFile
     * @param string $string
     * @param string $message
     */
    public static function assertStringNotMatchesFormatFile($formatFile, $string, $message = '')
    {
        static::assertFileExists($formatFile, $message);

        if (!is_string($string)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'string');
        }

        $constraint = new PHPUnit_Framework_Constraint_Not(
            new PHPUnit_Framework_Constraint_StringMatches(
                file_get_contents($formatFile)
            )
        );

        static::assertThat($string, $constraint, $message);
    }

    /**
     * Asserts that a string starts with a given prefix.
     *
     * @param string $prefix
     * @param string $string
     * @param string $message
     */
    public static function assertStringStartsWith($prefix, $string, $message = '')
    {
        if (!is_string($prefix)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        if (!is_string($string)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'string');
        }

        $constraint = new PHPUnit_Framework_Constraint_StringStartsWith(
            $prefix
        );

        static::assertThat($string, $constraint, $message);
    }

    /**
     * Asserts that a string starts not with a given prefix.
     *
     * @param string $prefix
     * @param string $string
     * @param string $message
     */
    public static function assertStringStartsNotWith($prefix, $string, $message = '')
    {
        if (!is_string($prefix)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        if (!is_string($string)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'string');
        }

        $constraint = new PHPUnit_Framework_Constraint_Not(
            new PHPUnit_Framework_Constraint_StringStartsWith($prefix)
        );

        static::assertThat($string, $constraint, $message);
    }

    /**
     * Asserts that a string ends with a given suffix.
     *
     * @param string $suffix
     * @param string $string
     * @param string $message
     */
    public static function assertStringEndsWith($suffix, $string, $message = '')
    {
        if (!is_string($suffix)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        if (!is_string($string)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'string');
        }

        $constraint = new PHPUnit_Framework_Constraint_StringEndsWith($suffix);

        static::assertThat($string, $constraint, $message);
    }

    /**
     * Asserts that a string ends not with a given suffix.
     *
     * @param string $suffix
     * @param string $string
     * @param string $message
     */
    public static function assertStringEndsNotWith($suffix, $string, $message = '')
    {
        if (!is_string($suffix)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        if (!is_string($string)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'string');
        }

        $constraint = new PHPUnit_Framework_Constraint_Not(
            new PHPUnit_Framework_Constraint_StringEndsWith($suffix)
        );

        static::assertThat($string, $constraint, $message);
    }

    /**
     * Asserts that two XML files are equal.
     *
     * @param string $expectedFile
     * @param string $actualFile
     * @param string $message
     */
    public static function assertXmlFileEqualsXmlFile($expectedFile, $actualFile, $message = '')
    {
        $expected = PHPUnit_Util_XML::loadFile($expectedFile);
        $actual   = PHPUnit_Util_XML::loadFile($actualFile);

        static::assertEquals($expected, $actual, $message);
    }

    /**
     * Asserts that two XML files are not equal.
     *
     * @param string $expectedFile
     * @param string $actualFile
     * @param string $message
     */
    public static function assertXmlFileNotEqualsXmlFile($expectedFile, $actualFile, $message = '')
    {
        $expected = PHPUnit_Util_XML::loadFile($expectedFile);
        $actual   = PHPUnit_Util_XML::loadFile($actualFile);

        static::assertNotEquals($expected, $actual, $message);
    }

    /**
     * Asserts that two XML documents are equal.
     *
     * @param string $expectedFile
     * @param string $actualXml
     * @param string $message
     */
    public static function assertXmlStringEqualsXmlFile($expectedFile, $actualXml, $message = '')
    {
        $expected = PHPUnit_Util_XML::loadFile($expectedFile);
        $actual   = PHPUnit_Util_XML::load($actualXml);

        static::assertEquals($expected, $actual, $message);
    }

    /**
     * Asserts that two XML documents are not equal.
     *
     * @param string $expectedFile
     * @param string $actualXml
     * @param string $message
     */
    public static function assertXmlStringNotEqualsXmlFile($expectedFile, $actualXml, $message = '')
    {
        $expected = PHPUnit_Util_XML::loadFile($expectedFile);
        $actual   = PHPUnit_Util_XML::load($actualXml);

        static::assertNotEquals($expected, $actual, $message);
    }

    /**
     * Asserts that two XML documents are equal.
     *
     * @param string $expectedXml
     * @param string $actualXml
     * @param string $message
     */
    public static function assertXmlStringEqualsXmlString($expectedXml, $actualXml, $message = '')
    {
        $expected = PHPUnit_Util_XML::load($expectedXml);
        $actual   = PHPUnit_Util_XML::load($actualXml);

        static::assertEquals($expected, $actual, $message);
    }

    /**
     * Asserts that two XML documents are not equal.
     *
     * @param string $expectedXml
     * @param string $actualXml
     * @param string $message
     */
    public static function assertXmlStringNotEqualsXmlString($expectedXml, $actualXml, $message = '')
    {
        $expected = PHPUnit_Util_XML::load($expectedXml);
        $actual   = PHPUnit_Util_XML::load($actualXml);

        static::assertNotEquals($expected, $actual, $message);
    }

    /**
     * Asserts that a hierarchy of DOMElements matches.
     *
     * @param DOMElement $expectedElement
     * @param DOMElement $actualElement
     * @param bool       $checkAttributes
     * @param string     $message
     */
    public static function assertEqualXMLStructure(DOMElement $expectedElement, DOMElement $actualElement, $checkAttributes = false, $message = '')
    {
        $tmp             = new DOMDocument;
        $expectedElement = $tmp->importNode($expectedElement, true);

        $tmp           = new DOMDocument;
        $actualElement = $tmp->importNode($actualElement, true);

        unset($tmp);

        static::assertEquals(
            $expectedElement->tagName,
            $actualElement->tagName,
            $message
        );

        if ($checkAttributes) {
            static::assertEquals(
                $expectedElement->attributes->length,
                $actualElement->attributes->length,
                sprintf(
                    '%s%sNumber of attributes on node "%s" does not match',
                    $message,
                    !empty($message) ? "\n" : '',
                    $expectedElement->tagName
                )
            );

            for ($i = 0; $i < $expectedElement->attributes->length; $i++) {
                $expectedAttribute = $expectedElement->attributes->item($i);
                $actualAttribute   = $actualElement->attributes->getNamedItem(
                    $expectedAttribute->name
                );

                if (!$actualAttribute) {
                    static::fail(
                        sprintf(
                            '%s%sCould not find attribute "%s" on node "%s"',
                            $message,
                            !empty($message) ? "\n" : '',
                            $expectedAttribute->name,
                            $expectedElement->tagName
                        )
                    );
                }
            }
        }

        PHPUnit_Util_XML::removeCharacterDataNodes($expectedElement);
        PHPUnit_Util_XML::removeCharacterDataNodes($actualElement);

        static::assertEquals(
            $expectedElement->childNodes->length,
            $actualElement->childNodes->length,
            sprintf(
                '%s%sNumber of child nodes of "%s" differs',
                $message,
                !empty($message) ? "\n" : '',
                $expectedElement->tagName
            )
        );

        for ($i = 0; $i < $expectedElement->childNodes->length; $i++) {
            static::assertEqualXMLStructure(
                $expectedElement->childNodes->item($i),
                $actualElement->childNodes->item($i),
                $checkAttributes,
                $message
            );
        }
    }

    /**
     * Evaluates a PHPUnit_Framework_Constraint matcher object.
     *
     * @param mixed                        $value
     * @param PHPUnit_Framework_Constraint $constraint
     * @param string                       $message
     */
    public static function assertThat($value, PHPUnit_Framework_Constraint $constraint, $message = '')
    {
        self::$count += count($constraint);

        $constraint->evaluate($value, $message);
    }

    /**
     * Asserts that a string is a valid JSON string.
     *
     * @param string $actualJson
     * @param string $message
     */
    public static function assertJson($actualJson, $message = '')
    {
        if (!is_string($actualJson)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        static::assertThat($actualJson, static::isJson(), $message);
    }

    /**
     * Asserts that two given JSON encoded objects or arrays are equal.
     *
     * @param string $expectedJson
     * @param string $actualJson
     * @param string $message
     */
    public static function assertJsonStringEqualsJsonString($expectedJson, $actualJson, $message = '')
    {
        static::assertJson($expectedJson, $message);
        static::assertJson($actualJson, $message);

        $expected = json_decode($expectedJson);
        $actual   = json_decode($actualJson);

        static::assertEquals($expected, $actual, $message);
    }

    /**
     * Asserts that two given JSON encoded objects or arrays are not equal.
     *
     * @param string $expectedJson
     * @param string $actualJson
     * @param string $message
     */
    public static function assertJsonStringNotEqualsJsonString($expectedJson, $actualJson, $message = '')
    {
        static::assertJson($expectedJson, $message);
        static::assertJson($actualJson, $message);

        $expected = json_decode($expectedJson);
        $actual   = json_decode($actualJson);

        static::assertNotEquals($expected, $actual, $message);
    }

    /**
     * Asserts that the generated JSON encoded object and the content of the given file are equal.
     *
     * @param string $expectedFile
     * @param string $actualJson
     * @param string $message
     */
    public static function assertJsonStringEqualsJsonFile($expectedFile, $actualJson, $message = '')
    {
        static::assertFileExists($expectedFile, $message);
        $expectedJson = file_get_contents($expectedFile);

        static::assertJson($expectedJson, $message);
        static::assertJson($actualJson, $message);

        // call constraint
        $constraint = new PHPUnit_Framework_Constraint_JsonMatches(
            $expectedJson
        );

        static::assertThat($actualJson, $constraint, $message);
    }

    /**
     * Asserts that the generated JSON encoded object and the content of the given file are not equal.
     *
     * @param string $expectedFile
     * @param string $actualJson
     * @param string $message
     */
    public static function assertJsonStringNotEqualsJsonFile($expectedFile, $actualJson, $message = '')
    {
        static::assertFileExists($expectedFile, $message);
        $expectedJson = file_get_contents($expectedFile);

        static::assertJson($expectedJson, $message);
        static::assertJson($actualJson, $message);

        // call constraint
        $constraint = new PHPUnit_Framework_Constraint_JsonMatches(
            $expectedJson
        );

        static::assertThat($actualJson, new PHPUnit_Framework_Constraint_Not($constraint), $message);
    }

    /**
     * Asserts that two JSON files are equal.
     *
     * @param string $expectedFile
     * @param string $actualFile
     * @param string $message
     */
    public static function assertJsonFileEqualsJsonFile($expectedFile, $actualFile, $message = '')
    {
        static::assertFileExists($expectedFile, $message);
        static::assertFileExists($actualFile, $message);

        $actualJson   = file_get_contents($actualFile);
        $expectedJson = file_get_contents($expectedFile);

        static::assertJson($expectedJson, $message);
        static::assertJson($actualJson, $message);

        // call constraint
        $constraintExpected = new PHPUnit_Framework_Constraint_JsonMatches(
            $expectedJson
        );

        $constraintActual = new PHPUnit_Framework_Constraint_JsonMatches($actualJson);

        static::assertThat($expectedJson, $constraintActual, $message);
        static::assertThat($actualJson, $constraintExpected, $message);
    }

    /**
     * Asserts that two JSON files are not equal.
     *
     * @param string $expectedFile
     * @param string $actualFile
     * @param string $message
     */
    public static function assertJsonFileNotEqualsJsonFile($expectedFile, $actualFile, $message = '')
    {
        static::assertFileExists($expectedFile, $message);
        static::assertFileExists($actualFile, $message);

        $actualJson   = file_get_contents($actualFile);
        $expectedJson = file_get_contents($expectedFile);

        static::assertJson($expectedJson, $message);
        static::assertJson($actualJson, $message);

        // call constraint
        $constraintExpected = new PHPUnit_Framework_Constraint_JsonMatches(
            $expectedJson
        );

        $constraintActual = new PHPUnit_Framework_Constraint_JsonMatches($actualJson);

        static::assertThat($expectedJson, new PHPUnit_Framework_Constraint_Not($constraintActual), $message);
        static::assertThat($actualJson, new PHPUnit_Framework_Constraint_Not($constraintExpected), $message);
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_And matcher object.
     *
     * @return PHPUnit_Framework_Constraint_And
     */
    public static function logicalAnd()
    {
        $constraints = func_get_args();

        $constraint = new PHPUnit_Framework_Constraint_And;
        $constraint->setConstraints($constraints);

        return $constraint;
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_Or matcher object.
     *
     * @return PHPUnit_Framework_Constraint_Or
     */
    public static function logicalOr()
    {
        $constraints = func_get_args();

        $constraint = new PHPUnit_Framework_Constraint_Or;
        $constraint->setConstraints($constraints);

        return $constraint;
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_Not matcher object.
     *
     * @param PHPUnit_Framework_Constraint $constraint
     *
     * @return PHPUnit_Framework_Constraint_Not
     */
    public static function logicalNot(PHPUnit_Framework_Constraint $constraint)
    {
        return new PHPUnit_Framework_Constraint_Not($constraint);
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_Xor matcher object.
     *
     * @return PHPUnit_Framework_Constraint_Xor
     */
    public static function logicalXor()
    {
        $constraints = func_get_args();

        $constraint = new PHPUnit_Framework_Constraint_Xor;
        $constraint->setConstraints($constraints);

        return $constraint;
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_IsAnything matcher object.
     *
     * @return PHPUnit_Framework_Constraint_IsAnything
     */
    public static function anything()
    {
        return new PHPUnit_Framework_Constraint_IsAnything;
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_IsTrue matcher object.
     *
     * @return PHPUnit_Framework_Constraint_IsTrue
     */
    public static function isTrue()
    {
        return new PHPUnit_Framework_Constraint_IsTrue;
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_Callback matcher object.
     *
     * @param callable $callback
     *
     * @return PHPUnit_Framework_Constraint_Callback
     */
    public static function callback($callback)
    {
        return new PHPUnit_Framework_Constraint_Callback($callback);
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_IsFalse matcher object.
     *
     * @return PHPUnit_Framework_Constraint_IsFalse
     */
    public static function isFalse()
    {
        return new PHPUnit_Framework_Constraint_IsFalse;
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_IsJson matcher object.
     *
     * @return PHPUnit_Framework_Constraint_IsJson
     */
    public static function isJson()
    {
        return new PHPUnit_Framework_Constraint_IsJson;
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_IsNull matcher object.
     *
     * @return PHPUnit_Framework_Constraint_IsNull
     */
    public static function isNull()
    {
        return new PHPUnit_Framework_Constraint_IsNull;
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_IsFinite matcher object.
     *
     * @return PHPUnit_Framework_Constraint_IsFinite
     */
    public static function isFinite()
    {
        return new PHPUnit_Framework_Constraint_IsFinite;
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_IsInfinite matcher object.
     *
     * @return PHPUnit_Framework_Constraint_IsInfinite
     */
    public static function isInfinite()
    {
        return new PHPUnit_Framework_Constraint_IsInfinite;
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_IsNan matcher object.
     *
     * @return PHPUnit_Framework_Constraint_IsNan
     */
    public static function isNan()
    {
        return new PHPUnit_Framework_Constraint_IsNan;
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_Attribute matcher object.
     *
     * @param PHPUnit_Framework_Constraint $constraint
     * @param string                       $attributeName
     *
     * @return PHPUnit_Framework_Constraint_Attribute
     */
    public static function attribute(PHPUnit_Framework_Constraint $constraint, $attributeName)
    {
        return new PHPUnit_Framework_Constraint_Attribute(
            $constraint,
            $attributeName
        );
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_TraversableContains matcher
     * object.
     *
     * @param mixed $value
     * @param bool  $checkForObjectIdentity
     * @param bool  $checkForNonObjectIdentity
     *
     * @return PHPUnit_Framework_Constraint_TraversableContains
     */
    public static function contains($value, $checkForObjectIdentity = true, $checkForNonObjectIdentity = false)
    {
        return new PHPUnit_Framework_Constraint_TraversableContains($value, $checkForObjectIdentity, $checkForNonObjectIdentity);
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_TraversableContainsOnly matcher
     * object.
     *
     * @param string $type
     *
     * @return PHPUnit_Framework_Constraint_TraversableContainsOnly
     */
    public static function containsOnly($type)
    {
        return new PHPUnit_Framework_Constraint_TraversableContainsOnly($type);
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_TraversableContainsOnly matcher
     * object.
     *
     * @param string $classname
     *
     * @return PHPUnit_Framework_Constraint_TraversableContainsOnly
     */
    public static function containsOnlyInstancesOf($classname)
    {
        return new PHPUnit_Framework_Constraint_TraversableContainsOnly($classname, false);
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_ArrayHasKey matcher object.
     *
     * @param mixed $key
     *
     * @return PHPUnit_Framework_Constraint_ArrayHasKey
     */
    public static function arrayHasKey($key)
    {
        return new PHPUnit_Framework_Constraint_ArrayHasKey($key);
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_IsEqual matcher object.
     *
     * @param mixed $value
     * @param float $delta
     * @param int   $maxDepth
     * @param bool  $canonicalize
     * @param bool  $ignoreCase
     *
     * @return PHPUnit_Framework_Constraint_IsEqual
     */
    public static function equalTo($value, $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false)
    {
        return new PHPUnit_Framework_Constraint_IsEqual(
            $value,
            $delta,
            $maxDepth,
            $canonicalize,
            $ignoreCase
        );
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_IsEqual matcher object
     * that is wrapped in a PHPUnit_Framework_Constraint_Attribute matcher
     * object.
     *
     * @param string $attributeName
     * @param mixed  $value
     * @param float  $delta
     * @param int    $maxDepth
     * @param bool   $canonicalize
     * @param bool   $ignoreCase
     *
     * @return PHPUnit_Framework_Constraint_Attribute
     */
    public static function attributeEqualTo($attributeName, $value, $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false)
    {
        return static::attribute(
            static::equalTo(
                $value,
                $delta,
                $maxDepth,
                $canonicalize,
                $ignoreCase
            ),
            $attributeName
        );
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_IsEmpty matcher object.
     *
     * @return PHPUnit_Framework_Constraint_IsEmpty
     */
    public static function isEmpty()
    {
        return new PHPUnit_Framework_Constraint_IsEmpty;
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_IsWritable matcher object.
     *
     * @return PHPUnit_Framework_Constraint_IsWritable
     */
    public static function isWritable()
    {
        return new PHPUnit_Framework_Constraint_IsWritable;
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_IsReadable matcher object.
     *
     * @return PHPUnit_Framework_Constraint_IsReadable
     */
    public static function isReadable()
    {
        return new PHPUnit_Framework_Constraint_IsReadable;
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_DirectoryExists matcher object.
     *
     * @return PHPUnit_Framework_Constraint_DirectoryExists
     */
    public static function directoryExists()
    {
        return new PHPUnit_Framework_Constraint_DirectoryExists;
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_FileExists matcher object.
     *
     * @return PHPUnit_Framework_Constraint_FileExists
     */
    public static function fileExists()
    {
        return new PHPUnit_Framework_Constraint_FileExists;
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_GreaterThan matcher object.
     *
     * @param mixed $value
     *
     * @return PHPUnit_Framework_Constraint_GreaterThan
     */
    public static function greaterThan($value)
    {
        return new PHPUnit_Framework_Constraint_GreaterThan($value);
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_Or matcher object that wraps
     * a PHPUnit_Framework_Constraint_IsEqual and a
     * PHPUnit_Framework_Constraint_GreaterThan matcher object.
     *
     * @param mixed $value
     *
     * @return PHPUnit_Framework_Constraint_Or
     */
    public static function greaterThanOrEqual($value)
    {
        return static::logicalOr(
            new PHPUnit_Framework_Constraint_IsEqual($value),
            new PHPUnit_Framework_Constraint_GreaterThan($value)
        );
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_ClassHasAttribute matcher object.
     *
     * @param string $attributeName
     *
     * @return PHPUnit_Framework_Constraint_ClassHasAttribute
     */
    public static function classHasAttribute($attributeName)
    {
        return new PHPUnit_Framework_Constraint_ClassHasAttribute(
            $attributeName
        );
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_ClassHasStaticAttribute matcher
     * object.
     *
     * @param string $attributeName
     *
     * @return PHPUnit_Framework_Constraint_ClassHasStaticAttribute
     */
    public static function classHasStaticAttribute($attributeName)
    {
        return new PHPUnit_Framework_Constraint_ClassHasStaticAttribute(
            $attributeName
        );
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_ObjectHasAttribute matcher object.
     *
     * @param string $attributeName
     *
     * @return PHPUnit_Framework_Constraint_ObjectHasAttribute
     */
    public static function objectHasAttribute($attributeName)
    {
        return new PHPUnit_Framework_Constraint_ObjectHasAttribute(
            $attributeName
        );
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_IsIdentical matcher object.
     *
     * @param mixed $value
     *
     * @return PHPUnit_Framework_Constraint_IsIdentical
     */
    public static function identicalTo($value)
    {
        return new PHPUnit_Framework_Constraint_IsIdentical($value);
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_IsInstanceOf matcher object.
     *
     * @param string $className
     *
     * @return PHPUnit_Framework_Constraint_IsInstanceOf
     */
    public static function isInstanceOf($className)
    {
        return new PHPUnit_Framework_Constraint_IsInstanceOf($className);
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_IsType matcher object.
     *
     * @param string $type
     *
     * @return PHPUnit_Framework_Constraint_IsType
     */
    public static function isType($type)
    {
        return new PHPUnit_Framework_Constraint_IsType($type);
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_LessThan matcher object.
     *
     * @param mixed $value
     *
     * @return PHPUnit_Framework_Constraint_LessThan
     */
    public static function lessThan($value)
    {
        return new PHPUnit_Framework_Constraint_LessThan($value);
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_Or matcher object that wraps
     * a PHPUnit_Framework_Constraint_IsEqual and a
     * PHPUnit_Framework_Constraint_LessThan matcher object.
     *
     * @param mixed $value
     *
     * @return PHPUnit_Framework_Constraint_Or
     */
    public static function lessThanOrEqual($value)
    {
        return static::logicalOr(
            new PHPUnit_Framework_Constraint_IsEqual($value),
            new PHPUnit_Framework_Constraint_LessThan($value)
        );
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_PCREMatch matcher object.
     *
     * @param string $pattern
     *
     * @return PHPUnit_Framework_Constraint_PCREMatch
     */
    public static function matchesRegularExpression($pattern)
    {
        return new PHPUnit_Framework_Constraint_PCREMatch($pattern);
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_StringMatches matcher object.
     *
     * @param string $string
     *
     * @return PHPUnit_Framework_Constraint_StringMatches
     */
    public static function matches($string)
    {
        return new PHPUnit_Framework_Constraint_StringMatches($string);
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_StringStartsWith matcher object.
     *
     * @param mixed $prefix
     *
     * @return PHPUnit_Framework_Constraint_StringStartsWith
     */
    public static function stringStartsWith($prefix)
    {
        return new PHPUnit_Framework_Constraint_StringStartsWith($prefix);
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_StringContains matcher object.
     *
     * @param string $string
     * @param bool   $case
     *
     * @return PHPUnit_Framework_Constraint_StringContains
     */
    public static function stringContains($string, $case = true)
    {
        return new PHPUnit_Framework_Constraint_StringContains($string, $case);
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_StringEndsWith matcher object.
     *
     * @param mixed $suffix
     *
     * @return PHPUnit_Framework_Constraint_StringEndsWith
     */
    public static function stringEndsWith($suffix)
    {
        return new PHPUnit_Framework_Constraint_StringEndsWith($suffix);
    }

    /**
     * Returns a PHPUnit_Framework_Constraint_Count matcher object.
     *
     * @param int $count
     *
     * @return PHPUnit_Framework_Constraint_Count
     */
    public static function countOf($count)
    {
        return new PHPUnit_Framework_Constraint_Count($count);
    }
    /**
     * Fails a test with the given message.
     *
     * @param string $message
     *
     * @throws PHPUnit_Framework_AssertionFailedError
     */
    public static function fail($message = '')
    {
        throw new PHPUnit_Framework_AssertionFailedError($message);
    }

    /**
     * Returns the value of an attribute of a class or an object.
     * This also works for attributes that are declared protected or private.
     *
     * @param string|object $classOrObject
     * @param string        $attributeName
     *
     * @return mixed
     *
     * @throws PHPUnit_Framework_Exception
     */
    public static function readAttribute($classOrObject, $attributeName)
    {
        if (!is_string($attributeName)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'string');
        }

        if (!preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $attributeName)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'valid attribute name');
        }

        if (is_string($classOrObject)) {
            if (!class_exists($classOrObject)) {
                throw PHPUnit_Util_InvalidArgumentHelper::factory(
                    1,
                    'class name'
                );
            }

            return static::getStaticAttribute(
                $classOrObject,
                $attributeName
            );
        } elseif (is_object($classOrObject)) {
            return static::getObjectAttribute(
                $classOrObject,
                $attributeName
            );
        } else {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(
                1,
                'class name or object'
            );
        }
    }

    /**
     * Returns the value of a static attribute.
     * This also works for attributes that are declared protected or private.
     *
     * @param string $className
     * @param string $attributeName
     *
     * @return mixed
     *
     * @throws PHPUnit_Framework_Exception
     */
    public static function getStaticAttribute($className, $attributeName)
    {
        if (!is_string($className)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        if (!class_exists($className)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'class name');
        }

        if (!is_string($attributeName)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'string');
        }

        if (!preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $attributeName)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'valid attribute name');
        }

        $class = new ReflectionClass($className);

        while ($class) {
            $attributes = $class->getStaticProperties();

            if (array_key_exists($attributeName, $attributes)) {
                return $attributes[$attributeName];
            }

            $class = $class->getParentClass();
        }

        throw new PHPUnit_Framework_Exception(
            sprintf(
                'Attribute "%s" not found in class.',
                $attributeName
            )
        );
    }

    /**
     * Returns the value of an object's attribute.
     * This also works for attributes that are declared protected or private.
     *
     * @param object $object
     * @param string $attributeName
     *
     * @return mixed
     *
     * @throws PHPUnit_Framework_Exception
     */
    public static function getObjectAttribute($object, $attributeName)
    {
        if (!is_object($object)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'object');
        }

        if (!is_string($attributeName)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'string');
        }

        if (!preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $attributeName)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'valid attribute name');
        }

        try {
            $attribute = new ReflectionProperty($object, $attributeName);
        } catch (ReflectionException $e) {
            $reflector = new ReflectionObject($object);

            while ($reflector = $reflector->getParentClass()) {
                try {
                    $attribute = $reflector->getProperty($attributeName);
                    break;
                } catch (ReflectionException $e) {
                }
            }
        }

        if (isset($attribute)) {
            if (!$attribute || $attribute->isPublic()) {
                return $object->$attributeName;
            }

            $attribute->setAccessible(true);
            $value = $attribute->getValue($object);
            $attribute->setAccessible(false);

            return $value;
        }

        throw new PHPUnit_Framework_Exception(
            sprintf(
                'Attribute "%s" not found in object.',
                $attributeName
            )
        );
    }

    /**
     * Mark the test as incomplete.
     *
     * @param string $message
     *
     * @throws PHPUnit_Framework_IncompleteTestError
     */
    public static function markTestIncomplete($message = '')
    {
        throw new PHPUnit_Framework_IncompleteTestError($message);
    }

    /**
     * Mark the test as skipped.
     *
     * @param string $message
     *
     * @throws PHPUnit_Framework_SkippedTestError
     */
    public static function markTestSkipped($message = '')
    {
        throw new PHPUnit_Framework_SkippedTestError($message);
    }

    /**
     * Return the current assertion count.
     *
     * @return int
     */
    public static function getCount()
    {
        return self::$count;
    }

    /**
     * Reset the assertion counter.
     */
    public static function resetCount()
    {
        self::$count = 0;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A marker interface for marking a unit test as being skipped.
 */
interface PHPUnit_Framework_SkippedTest
{
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A Test can be run and collect its results.
 */
interface PHPUnit_Framework_Test extends Countable
{
    /**
     * Runs a test and collects its result in a TestResult instance.
     *
     * @param PHPUnit_Framework_TestResult $result
     *
     * @return PHPUnit_Framework_TestResult
     */
    public function run(PHPUnit_Framework_TestResult $result = null);
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A marker interface for marking any exception/error as result of an unit
 * test as risky.
 */
interface PHPUnit_Framework_RiskyTest
{
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Wrapper for PHP errors.
 */
class PHPUnit_Framework_Error extends PHPUnit_Framework_Exception
{
    /**
     * Constructor.
     *
     * @param string    $message
     * @param int       $code
     * @param string    $file
     * @param int       $line
     * @param Exception $previous
     */
    public function __construct($message, $code, $file, $line, Exception $previous = null)
    {
        parent::__construct($message, $code, $previous);

        $this->file  = $file;
        $this->line  = $line;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use SebastianBergmann\Exporter\Exporter;

/**
 * Abstract base class for constraints which can be applied to any value.
 */
abstract class PHPUnit_Framework_Constraint implements Countable, PHPUnit_Framework_SelfDescribing
{
    protected $exporter;

    public function __construct()
    {
        $this->exporter = new Exporter;
    }

    /**
     * Evaluates the constraint for parameter $other
     *
     * If $returnResult is set to false (the default), an exception is thrown
     * in case of a failure. null is returned otherwise.
     *
     * If $returnResult is true, the result of the evaluation is returned as
     * a boolean value instead: true in case of success, false in case of a
     * failure.
     *
     * @param mixed  $other        Value or object to evaluate.
     * @param string $description  Additional information about the test
     * @param bool   $returnResult Whether to return a result or throw an exception
     *
     * @return mixed
     *
     * @throws PHPUnit_Framework_ExpectationFailedException
     */
    public function evaluate($other, $description = '', $returnResult = false)
    {
        $success = false;

        if ($this->matches($other)) {
            $success = true;
        }

        if ($returnResult) {
            return $success;
        }

        if (!$success) {
            $this->fail($other, $description);
        }
    }

    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * This method can be overridden to implement the evaluation algorithm.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        return false;
    }

    /**
     * Counts the number of constraint elements.
     *
     * @return int
     */
    public function count()
    {
        return 1;
    }

    /**
     * Throws an exception for the given compared value and test description
     *
     * @param mixed                                          $other             Evaluated value or object.
     * @param string                                         $description       Additional information about the test
     * @param SebastianBergmann\Comparator\ComparisonFailure $comparisonFailure
     *
     * @throws PHPUnit_Framework_ExpectationFailedException
     */
    protected function fail($other, $description, SebastianBergmann\Comparator\ComparisonFailure $comparisonFailure = null)
    {
        $failureDescription = sprintf(
            'Failed asserting that %s.',
            $this->failureDescription($other)
        );

        $additionalFailureDescription = $this->additionalFailureDescription($other);

        if ($additionalFailureDescription) {
            $failureDescription .= "\n" . $additionalFailureDescription;
        }

        if (!empty($description)) {
            $failureDescription = $description . "\n" . $failureDescription;
        }

        throw new PHPUnit_Framework_ExpectationFailedException(
            $failureDescription,
            $comparisonFailure
        );
    }

    /**
     * Return additional failure description where needed
     *
     * The function can be overridden to provide additional failure
     * information like a diff
     *
     * @param mixed $other Evaluated value or object.
     *
     * @return string
     */
    protected function additionalFailureDescription($other)
    {
        return '';
    }

    /**
     * Returns the description of the failure
     *
     * The beginning of failure messages is "Failed asserting that" in most
     * cases. This method should return the second part of that sentence.
     *
     * To provide additional failure information additionalFailureDescription
     * can be used.
     *
     * @param mixed $other Evaluated value or object.
     *
     * @return string
     */
    protected function failureDescription($other)
    {
        return $this->exporter->export($other) . ' ' . $this->toString();
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

class PHPUnit_Framework_InvalidCoversTargetException extends PHPUnit_Framework_CodeCoverageException
{
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Extension to PHPUnit_Framework_AssertionFailedError to mark a test as risky
 * when it does not have a @covers annotation but is expected to have one.
 */
class PHPUnit_Framework_MissingCoversAnnotationException extends PHPUnit_Framework_RiskyTestError
{
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Base class for all PHPUnit Framework exceptions.
 *
 * Ensures that exceptions thrown during a test run do not leave stray
 * references behind.
 *
 * Every Exception contains a stack trace. Each stack frame contains the 'args'
 * of the called function. The function arguments can contain references to
 * instantiated objects. The references prevent the objects from being
 * destructed (until test results are eventually printed), so memory cannot be
 * freed up.
 *
 * With enabled process isolation, test results are serialized in the child
 * process and unserialized in the parent process. The stack trace of Exceptions
 * may contain objects that cannot be serialized or unserialized (e.g., PDO
 * connections). Unserializing user-space objects from the child process into
 * the parent would break the intended encapsulation of process isolation.
 *
 * @see http://fabien.potencier.org/article/9/php-serialization-stack-traces-and-exceptions
 */
class PHPUnit_Framework_Exception extends RuntimeException implements PHPUnit_Exception
{
    /**
     * @var array
     */
    protected $serializableTrace;

    public function __construct($message = '', $code = 0, Exception $previous = null)
    {
        parent::__construct($message, $code, $previous);

        $this->serializableTrace = $this->getTrace();
        foreach ($this->serializableTrace as $i => $call) {
            unset($this->serializableTrace[$i]['args']);
        }
    }

    /**
     * Returns the serializable trace (without 'args').
     *
     * @return array
     */
    public function getSerializableTrace()
    {
        return $this->serializableTrace;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        $string = PHPUnit_Framework_TestFailure::exceptionToString($this);

        if ($trace = PHPUnit_Util_Filter::getFilteredStacktrace($this)) {
            $string .= "\n" . $trace;
        }

        return $string;
    }

    public function __sleep()
    {
        return array_keys(get_object_vars($this));
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Wrapper for PHP warnings.
 * You can disable notice-to-exception conversion by setting
 *
 * <code>
 * PHPUnit_Framework_Error_Warning::$enabled = false;
 * </code>
 */
class PHPUnit_Framework_Error_Warning extends PHPUnit_Framework_Error
{
    public static $enabled = true;
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Wrapper for PHP notices.
 * You can disable notice-to-exception conversion by setting
 *
 * <code>
 * PHPUnit_Framework_Error_Notice::$enabled = false;
 * </code>
 */
class PHPUnit_Framework_Error_Notice extends PHPUnit_Framework_Error
{
    public static $enabled = true;
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Wrapper for PHP deprecated errors.
 * You can disable deprecated-to-exception conversion by setting
 *
 * <code>
 * PHPUnit_Framework_Error_Deprecated::$enabled = false;
 * </code>
 */
class PHPUnit_Framework_Error_Deprecated extends PHPUnit_Framework_Error
{
    public static $enabled = true;
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use SebastianBergmann\GlobalState\Snapshot;
use SebastianBergmann\GlobalState\Restorer;
use SebastianBergmann\GlobalState\Blacklist;
use SebastianBergmann\Diff\Differ;
use SebastianBergmann\Exporter\Exporter;
use SebastianBergmann\ObjectEnumerator\Enumerator;
use Prophecy\Exception\Prediction\PredictionException;
use Prophecy\Prophet;
use DeepCopy\DeepCopy;

/**
 * A TestCase defines the fixture to run multiple tests.
 *
 * To define a TestCase
 *
 *   1) Implement a subclass of PHPUnit_Framework_TestCase.
 *   2) Define instance variables that store the state of the fixture.
 *   3) Initialize the fixture state by overriding setUp().
 *   4) Clean-up after a test by overriding tearDown().
 *
 * Each test runs in its own fixture so there can be no side effects
 * among test runs.
 *
 * Here is an example:
 *
 * <code>
 * <?php
 * class MathTest extends PHPUnit_Framework_TestCase
 * {
 *     public $value1;
 *     public $value2;
 *
 *     protected function setUp()
 *     {
 *         $this->value1 = 2;
 *         $this->value2 = 3;
 *     }
 * }
 * ?>
 * </code>
 *
 * For each test implement a method which interacts with the fixture.
 * Verify the expected results with assertions specified by calling
 * assert with a boolean.
 *
 * <code>
 * <?php
 * public function testPass()
 * {
 *     $this->assertTrue($this->value1 + $this->value2 == 5);
 * }
 * ?>
 * </code>
 */
abstract class PHPUnit_Framework_TestCase extends PHPUnit_Framework_Assert implements PHPUnit_Framework_Test, PHPUnit_Framework_SelfDescribing
{
    /**
     * Enable or disable the backup and restoration of the $GLOBALS array.
     * Overwrite this attribute in a child class of TestCase.
     * Setting this attribute in setUp() has no effect!
     *
     * @var bool
     */
    protected $backupGlobals = null;

    /**
     * @var array
     */
    protected $backupGlobalsBlacklist = [];

    /**
     * Enable or disable the backup and restoration of static attributes.
     * Overwrite this attribute in a child class of TestCase.
     * Setting this attribute in setUp() has no effect!
     *
     * @var bool
     */
    protected $backupStaticAttributes = null;

    /**
     * @var array
     */
    protected $backupStaticAttributesBlacklist = [];

    /**
     * Whether or not this test is to be run in a separate PHP process.
     *
     * @var bool
     */
    protected $runTestInSeparateProcess = null;

    /**
     * Whether or not this test should preserve the global state when
     * running in a separate PHP process.
     *
     * @var bool
     */
    protected $preserveGlobalState = true;

    /**
     * Whether or not this test is running in a separate PHP process.
     *
     * @var bool
     */
    private $inIsolation = false;

    /**
     * @var array
     */
    private $data = [];

    /**
     * @var string
     */
    private $dataName = '';

    /**
     * @var bool
     */
    private $useErrorHandler = null;

    /**
     * The name of the expected Exception.
     *
     * @var string
     */
    private $expectedException = null;

    /**
     * The message of the expected Exception.
     *
     * @var string
     */
    private $expectedExceptionMessage = null;

    /**
     * The regex pattern to validate the expected Exception message.
     *
     * @var string
     */
    private $expectedExceptionMessageRegExp = null;

    /**
     * The code of the expected Exception.
     *
     * @var int|string
     */
    private $expectedExceptionCode = null;

    /**
     * The name of the test case.
     *
     * @var string
     */
    private $name = null;

    /**
     * @var array
     */
    private $dependencies = [];

    /**
     * @var array
     */
    private $dependencyInput = [];

    /**
     * @var array
     */
    private $iniSettings = [];

    /**
     * @var array
     */
    private $locale = [];

    /**
     * @var array
     */
    private $mockObjects = [];

    /**
     * @var MockGenerator
     */
    private $mockObjectGenerator = null;

    /**
     * @var int
     */
    private $status;

    /**
     * @var string
     */
    private $statusMessage = '';

    /**
     * @var int
     */
    private $numAssertions = 0;

    /**
     * @var PHPUnit_Framework_TestResult
     */
    private $result;

    /**
     * @var mixed
     */
    private $testResult;

    /**
     * @var string
     */
    private $output = '';

    /**
     * @var string
     */
    private $outputExpectedRegex = null;

    /**
     * @var string
     */
    private $outputExpectedString = null;

    /**
     * @var mixed
     */
    private $outputCallback = false;

    /**
     * @var bool
     */
    private $outputBufferingActive = false;

    /**
     * @var int
     */
    private $outputBufferingLevel;

    /**
     * @var SebastianBergmann\GlobalState\Snapshot
     */
    private $snapshot;

    /**
     * @var Prophecy\Prophet
     */
    private $prophet;

    /**
     * @var bool
     */
    private $beStrictAboutChangesToGlobalState = false;

    /**
     * @var bool
     */
    private $registerMockObjectsFromTestArgumentsRecursively = false;

    /**
     * @var string[]
     */
    private $warnings = [];

    /**
     * @var array
     */
    private $groups = [];

    /**
     * @var bool
     */
    private $doesNotPerformAssertions = false;

    /**
     * Constructs a test case with the given name.
     *
     * @param string $name
     * @param array  $data
     * @param string $dataName
     */
    public function __construct($name = null, array $data = [], $dataName = '')
    {
        if ($name !== null) {
            $this->setName($name);
        }

        $this->data     = $data;
        $this->dataName = $dataName;
    }

    /**
     * Returns a string representation of the test case.
     *
     * @return string
     */
    public function toString()
    {
        $class = new ReflectionClass($this);

        $buffer = sprintf(
            '%s::%s',
            $class->name,
            $this->getName(false)
        );

        return $buffer . $this->getDataSetAsString();
    }

    /**
     * Counts the number of test cases executed by run(TestResult result).
     *
     * @return int
     */
    public function count()
    {
        return 1;
    }

    public function getGroups()
    {
        return $this->groups;
    }

    /**
     * @param array $groups
     */
    public function setGroups(array $groups)
    {
        $this->groups = $groups;
    }

    /**
     * Returns the annotations for this test.
     *
     * @return array
     */
    public function getAnnotations()
    {
        return PHPUnit_Util_Test::parseTestMethodAnnotations(
            get_class($this),
            $this->name
        );
    }

    /**
     * Gets the name of a TestCase.
     *
     * @param bool $withDataSet
     *
     * @return string
     */
    public function getName($withDataSet = true)
    {
        if ($withDataSet) {
            return $this->name . $this->getDataSetAsString(false);
        } else {
            return $this->name;
        }
    }

    /**
     * Returns the size of the test.
     *
     * @return int
     */
    public function getSize()
    {
        return PHPUnit_Util_Test::getSize(
            get_class($this),
            $this->getName(false)
        );
    }

    /**
     * @return bool
     */
    public function hasSize()
    {
        return $this->getSize() !== PHPUnit_Util_Test::UNKNOWN;
    }

    /**
     * @return bool
     */
    public function isSmall()
    {
        return $this->getSize() === PHPUnit_Util_Test::SMALL;
    }

    /**
     * @return bool
     */
    public function isMedium()
    {
        return $this->getSize() === PHPUnit_Util_Test::MEDIUM;
    }

    /**
     * @return bool
     */
    public function isLarge()
    {
        return $this->getSize() === PHPUnit_Util_Test::LARGE;
    }

    /**
     * @return string
     */
    public function getActualOutput()
    {
        if (!$this->outputBufferingActive) {
            return $this->output;
        } else {
            return ob_get_contents();
        }
    }

    /**
     * @return bool
     */
    public function hasOutput()
    {
        if (strlen($this->output) === 0) {
            return false;
        }

        if ($this->hasExpectationOnOutput()) {
            return false;
        }

        return true;
    }

    /**
     * @return bool
     */
    public function doesNotPerformAssertions()
    {
        return $this->doesNotPerformAssertions;
    }

    /**
     * @param string $expectedRegex
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function expectOutputRegex($expectedRegex)
    {
        if ($this->outputExpectedString !== null) {
            throw new PHPUnit_Framework_Exception;
        }

        if (is_string($expectedRegex) || is_null($expectedRegex)) {
            $this->outputExpectedRegex = $expectedRegex;
        }
    }

    /**
     * @param string $expectedString
     */
    public function expectOutputString($expectedString)
    {
        if ($this->outputExpectedRegex !== null) {
            throw new PHPUnit_Framework_Exception;
        }

        if (is_string($expectedString) || is_null($expectedString)) {
            $this->outputExpectedString = $expectedString;
        }
    }

    /**
     * @return bool
     *
     * @deprecated Use hasExpectationOnOutput() instead
     */
    public function hasPerformedExpectationsOnOutput()
    {
        return $this->hasExpectationOnOutput();
    }

    /**
     * @return bool
     */
    public function hasExpectationOnOutput()
    {
        return is_string($this->outputExpectedString) || is_string($this->outputExpectedRegex);
    }

    /**
     * @return string
     */
    public function getExpectedException()
    {
        return $this->expectedException;
    }

    /**
     * @param mixed      $exception
     * @param string     $message   Null means we do not check message at all, string (even empty) means we do. Default: null.
     * @param int|string $code      Null means we do not check code at all, non-null means we do.
     *
     * @throws PHPUnit_Framework_Exception
     *
     * @deprecated Method deprecated since Release 5.2.0; use expectException() instead
     */
    public function setExpectedException($exception, $message = '', $code = null)
    {
        if (null !== $message && !is_string($message)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'string');
        }

        if (func_num_args() < 2) {
            $message = null;
        }

        $this->expectedException = $exception;

        if ($message !== null) {
            $this->expectExceptionMessage($message);
        }

        if ($code !== null) {
            $this->expectExceptionCode($code);
        }
    }

    /**
     * @param mixed  $exception
     * @param string $messageRegExp
     * @param int    $code
     *
     * @throws PHPUnit_Framework_Exception
     *
     * @deprecated Method deprecated since Release 5.6.0; use expectExceptionMessageRegExp() instead
     */
    public function setExpectedExceptionRegExp($exception, $messageRegExp = '', $code = null)
    {
        if (!is_string($messageRegExp)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'string');
        }

        $this->expectedException              = $exception;
        $this->expectedExceptionMessageRegExp = $messageRegExp;

        if ($code !== null) {
            $this->expectExceptionCode($code);
        }
    }

    /**
     * @param string $exception
     */
    public function expectException($exception)
    {
        if (!is_string($exception)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        $this->expectedException = $exception;
    }

    /**
     * @param int|string $code
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function expectExceptionCode($code)
    {
        if (!$this->expectedException) {
            $this->expectedException = \Exception::class;
        }

        if (!is_int($code) && !is_string($code)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'integer or string');
        }

        $this->expectedExceptionCode = $code;
    }

    /**
     * @param string $message
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function expectExceptionMessage($message)
    {
        if (!$this->expectedException) {
            $this->expectedException = \Exception::class;
        }

        if (!is_string($message)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        $this->expectedExceptionMessage = $message;
    }

    /**
     * @param string $messageRegExp
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function expectExceptionMessageRegExp($messageRegExp)
    {
        if (!is_string($messageRegExp)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        $this->expectedExceptionMessageRegExp = $messageRegExp;
    }

    /**
     * @param bool $flag
     */
    public function setRegisterMockObjectsFromTestArgumentsRecursively($flag)
    {
        if (!is_bool($flag)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');
        }

        $this->registerMockObjectsFromTestArgumentsRecursively = $flag;
    }

    protected function setExpectedExceptionFromAnnotation()
    {
        try {
            $expectedException = PHPUnit_Util_Test::getExpectedException(
                get_class($this),
                $this->name
            );

            if ($expectedException !== false) {
                $this->expectException($expectedException['class']);

                if ($expectedException['code'] !== null) {
                    $this->expectExceptionCode($expectedException['code']);
                }

                if ($expectedException['message'] !== '') {
                    $this->expectExceptionMessage($expectedException['message']);
                } elseif ($expectedException['message_regex'] !== '') {
                    $this->expectExceptionMessageRegExp($expectedException['message_regex']);
                }
            }
        } catch (ReflectionException $e) {
        }
    }

    /**
     * @param bool $useErrorHandler
     */
    public function setUseErrorHandler($useErrorHandler)
    {
        $this->useErrorHandler = $useErrorHandler;
    }

    protected function setUseErrorHandlerFromAnnotation()
    {
        try {
            $useErrorHandler = PHPUnit_Util_Test::getErrorHandlerSettings(
                get_class($this),
                $this->name
            );

            if ($useErrorHandler !== null) {
                $this->setUseErrorHandler($useErrorHandler);
            }
        } catch (ReflectionException $e) {
        }
    }

    protected function checkRequirements()
    {
        if (!$this->name || !method_exists($this, $this->name)) {
            return;
        }

        $missingRequirements = PHPUnit_Util_Test::getMissingRequirements(
            get_class($this),
            $this->name
        );

        if (!empty($missingRequirements)) {
            $this->markTestSkipped(implode(PHP_EOL, $missingRequirements));
        }
    }

    /**
     * Returns the status of this test.
     *
     * @return int
     */
    public function getStatus()
    {
        return $this->status;
    }

    public function markAsRisky()
    {
        $this->status = PHPUnit_Runner_BaseTestRunner::STATUS_RISKY;
    }

    /**
     * Returns the status message of this test.
     *
     * @return string
     */
    public function getStatusMessage()
    {
        return $this->statusMessage;
    }

    /**
     * Returns whether or not this test has failed.
     *
     * @return bool
     */
    public function hasFailed()
    {
        $status = $this->getStatus();

        return $status == PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE ||
               $status == PHPUnit_Runner_BaseTestRunner::STATUS_ERROR;
    }

    /**
     * Runs the test case and collects the results in a TestResult object.
     * If no TestResult object is passed a new one will be created.
     *
     * @param PHPUnit_Framework_TestResult $result
     *
     * @return PHPUnit_Framework_TestResult
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function run(PHPUnit_Framework_TestResult $result = null)
    {
        if ($result === null) {
            $result = $this->createResult();
        }

        if (!$this instanceof PHPUnit_Framework_WarningTestCase) {
            $this->setTestResultObject($result);
            $this->setUseErrorHandlerFromAnnotation();
        }

        if ($this->useErrorHandler !== null) {
            $oldErrorHandlerSetting = $result->getConvertErrorsToExceptions();
            $result->convertErrorsToExceptions($this->useErrorHandler);
        }

        if (!$this instanceof PHPUnit_Framework_WarningTestCase &&
            !$this instanceof PHPUnit_Framework_SkippedTestCase &&
            !$this->handleDependencies()) {
            return;
        }

        if ($this->runTestInSeparateProcess === true &&
            $this->inIsolation !== true &&
            !$this instanceof PHPUnit_Extensions_PhptTestCase) {
            $class = new ReflectionClass($this);

            $template = new Text_Template(
                __DIR__ . '/../Util/PHP/Template/TestCaseMethod.tpl'
            );

            if ($this->preserveGlobalState) {
                $constants     = PHPUnit_Util_GlobalState::getConstantsAsString();
                $globals       = PHPUnit_Util_GlobalState::getGlobalsAsString();
                $includedFiles = PHPUnit_Util_GlobalState::getIncludedFilesAsString();
                $iniSettings   = PHPUnit_Util_GlobalState::getIniSettingsAsString();
            } else {
                $constants     = '';
                if (!empty($GLOBALS['__PHPUNIT_BOOTSTRAP'])) {
                    $globals     = '$GLOBALS[\'__PHPUNIT_BOOTSTRAP\'] = ' . var_export($GLOBALS['__PHPUNIT_BOOTSTRAP'], true) . ";\n";
                } else {
                    $globals     = '';
                }
                $includedFiles = '';
                $iniSettings   = '';
            }

            $coverage                                   = $result->getCollectCodeCoverageInformation()          ? 'true' : 'false';
            $isStrictAboutTestsThatDoNotTestAnything    = $result->isStrictAboutTestsThatDoNotTestAnything()    ? 'true' : 'false';
            $isStrictAboutOutputDuringTests             = $result->isStrictAboutOutputDuringTests()             ? 'true' : 'false';
            $enforcesTimeLimit                          = $result->enforcesTimeLimit()                          ? 'true' : 'false';
            $isStrictAboutTodoAnnotatedTests            = $result->isStrictAboutTodoAnnotatedTests()            ? 'true' : 'false';
            $isStrictAboutResourceUsageDuringSmallTests = $result->isStrictAboutResourceUsageDuringSmallTests() ? 'true' : 'false';

            if (defined('PHPUNIT_COMPOSER_INSTALL')) {
                $composerAutoload = var_export(PHPUNIT_COMPOSER_INSTALL, true);
            } else {
                $composerAutoload = '\'\'';
            }

            if (defined('__PHPUNIT_PHAR__')) {
                $phar = var_export(__PHPUNIT_PHAR__, true);
            } else {
                $phar = '\'\'';
            }

            if ($result->getCodeCoverage()) {
                $codeCoverageFilter = $result->getCodeCoverage()->filter();
            } else {
                $codeCoverageFilter = null;
            }

            $data               = var_export(serialize($this->data), true);
            $dataName           = var_export($this->dataName, true);
            $dependencyInput    = var_export(serialize($this->dependencyInput), true);
            $includePath        = var_export(get_include_path(), true);
            $codeCoverageFilter = var_export(serialize($codeCoverageFilter), true);
            // must do these fixes because TestCaseMethod.tpl has unserialize('{data}') in it, and we can't break BC
            // the lines above used to use addcslashes() rather than var_export(), which breaks null byte escape sequences
            $data               = "'." . $data . ".'";
            $dataName           = "'.(" . $dataName . ").'";
            $dependencyInput    = "'." . $dependencyInput . ".'";
            $includePath        = "'." . $includePath . ".'";
            $codeCoverageFilter = "'." . $codeCoverageFilter . ".'";

            $configurationFilePath = (isset($GLOBALS['__PHPUNIT_CONFIGURATION_FILE']) ? $GLOBALS['__PHPUNIT_CONFIGURATION_FILE'] : '');

            $template->setVar(
                [
                    'composerAutoload'                           => $composerAutoload,
                    'phar'                                       => $phar,
                    'filename'                                   => $class->getFileName(),
                    'className'                                  => $class->getName(),
                    'methodName'                                 => $this->name,
                    'collectCodeCoverageInformation'             => $coverage,
                    'data'                                       => $data,
                    'dataName'                                   => $dataName,
                    'dependencyInput'                            => $dependencyInput,
                    'constants'                                  => $constants,
                    'globals'                                    => $globals,
                    'include_path'                               => $includePath,
                    'included_files'                             => $includedFiles,
                    'iniSettings'                                => $iniSettings,
                    'isStrictAboutTestsThatDoNotTestAnything'    => $isStrictAboutTestsThatDoNotTestAnything,
                    'isStrictAboutOutputDuringTests'             => $isStrictAboutOutputDuringTests,
                    'enforcesTimeLimit'                          => $enforcesTimeLimit,
                    'isStrictAboutTodoAnnotatedTests'            => $isStrictAboutTodoAnnotatedTests,
                    'isStrictAboutResourceUsageDuringSmallTests' => $isStrictAboutResourceUsageDuringSmallTests,
                    'codeCoverageFilter'                         => $codeCoverageFilter,
                    'configurationFilePath'                      => $configurationFilePath
                ]
            );

            $this->prepareTemplate($template);

            $php = PHPUnit_Util_PHP::factory();
            $php->runTestJob($template->render(), $this, $result);
        } else {
            $result->run($this);
        }

        if (isset($oldErrorHandlerSetting)) {
            $result->convertErrorsToExceptions($oldErrorHandlerSetting);
        }

        $this->result = null;

        return $result;
    }

    /**
     * Runs the bare test sequence.
     */
    public function runBare()
    {
        $this->numAssertions = 0;

        $this->snapshotGlobalState();
        $this->startOutputBuffering();
        clearstatcache();
        $currentWorkingDirectory = getcwd();

        $hookMethods = PHPUnit_Util_Test::getHookMethods(get_class($this));

        try {
            $hasMetRequirements = false;
            $this->checkRequirements();
            $hasMetRequirements = true;

            if ($this->inIsolation) {
                foreach ($hookMethods['beforeClass'] as $method) {
                    $this->$method();
                }
            }

            $this->setExpectedExceptionFromAnnotation();
            $this->setDoesNotPerformAssertionsFromAnnotation();

            foreach ($hookMethods['before'] as $method) {
                $this->$method();
            }

            $this->assertPreConditions();
            $this->testResult = $this->runTest();
            $this->verifyMockObjects();
            $this->assertPostConditions();

            if (!empty($this->warnings)) {
                throw new PHPUnit_Framework_Warning(
                    implode(
                        "\n",
                        array_unique($this->warnings)
                    )
                );
            }

            $this->status = PHPUnit_Runner_BaseTestRunner::STATUS_PASSED;
        } catch (PHPUnit_Framework_IncompleteTest $e) {
            $this->status        = PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE;
            $this->statusMessage = $e->getMessage();
        } catch (PHPUnit_Framework_SkippedTest $e) {
            $this->status        = PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED;
            $this->statusMessage = $e->getMessage();
        } catch (PHPUnit_Framework_Warning $e) {
            $this->status        = PHPUnit_Runner_BaseTestRunner::STATUS_WARNING;
            $this->statusMessage = $e->getMessage();
        } catch (PHPUnit_Framework_AssertionFailedError $e) {
            $this->status        = PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE;
            $this->statusMessage = $e->getMessage();
        } catch (PredictionException $e) {
            $this->status        = PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE;
            $this->statusMessage = $e->getMessage();
        } catch (Throwable $_e) {
            $e = $_e;
        } catch (Exception $_e) {
            $e = $_e;
        }

        // Clean up the mock objects.
        $this->mockObjects = [];
        $this->prophet     = null;

        // Tear down the fixture. An exception raised in tearDown() will be
        // caught and passed on when no exception was raised before.
        try {
            if ($hasMetRequirements) {
                foreach ($hookMethods['after'] as $method) {
                    $this->$method();
                }

                if ($this->inIsolation) {
                    foreach ($hookMethods['afterClass'] as $method) {
                        $this->$method();
                    }
                }
            }
        } catch (Throwable $_e) {
            if (!isset($e)) {
                $e = $_e;
            }
        } catch (Exception $_e) {
            if (!isset($e)) {
                $e = $_e;
            }
        }

        try {
            $this->stopOutputBuffering();
        } catch (PHPUnit_Framework_RiskyTestError $_e) {
            if (!isset($e)) {
                $e = $_e;
            }
        }

        if (isset($_e)) {
            $this->status        = PHPUnit_Runner_BaseTestRunner::STATUS_ERROR;
            $this->statusMessage = $_e->getMessage();
        }

        clearstatcache();

        if ($currentWorkingDirectory != getcwd()) {
            chdir($currentWorkingDirectory);
        }

        $this->restoreGlobalState();

        // Clean up INI settings.
        foreach ($this->iniSettings as $varName => $oldValue) {
            ini_set($varName, $oldValue);
        }

        $this->iniSettings = [];

        // Clean up locale settings.
        foreach ($this->locale as $category => $locale) {
            setlocale($category, $locale);
        }

        // Perform assertion on output.
        if (!isset($e)) {
            try {
                if ($this->outputExpectedRegex !== null) {
                    $this->assertRegExp($this->outputExpectedRegex, $this->output);
                } elseif ($this->outputExpectedString !== null) {
                    $this->assertEquals($this->outputExpectedString, $this->output);
                }
            } catch (Throwable $_e) {
                $e = $_e;
            } catch (Exception $_e) {
                $e = $_e;
            }
        }

        // Workaround for missing "finally".
        if (isset($e)) {
            if ($e instanceof PredictionException) {
                $e = new PHPUnit_Framework_AssertionFailedError($e->getMessage());
            }

            $this->onNotSuccessfulTest($e);
        }
    }

    /**
     * Override to run the test and assert its state.
     *
     * @return mixed
     *
     * @throws Exception|PHPUnit_Framework_Exception
     * @throws PHPUnit_Framework_Exception
     */
    protected function runTest()
    {
        if ($this->name === null) {
            throw new PHPUnit_Framework_Exception(
                'PHPUnit_Framework_TestCase::$name must not be null.'
            );
        }

        try {
            $class  = new ReflectionClass($this);
            $method = $class->getMethod($this->name);
        } catch (ReflectionException $e) {
            $this->fail($e->getMessage());
        }

        $testArguments = array_merge($this->data, $this->dependencyInput);

        $this->registerMockObjectsFromTestArguments($testArguments);

        try {
            $testResult = $method->invokeArgs($this, $testArguments);
        } catch (Throwable $_e) {
            $e = $_e;
        } catch (Exception $_e) {
            $e = $_e;
        }

        if (isset($e)) {
            $checkException = false;

            if (!($e instanceof PHPUnit_Framework_SkippedTestError) && is_string($this->expectedException)) {
                $checkException = true;

                if ($e instanceof PHPUnit_Framework_Exception) {
                    $checkException = false;
                }

                $reflector = new ReflectionClass($this->expectedException);

                if ($this->expectedException === 'PHPUnit_Framework_Exception' ||
                    $this->expectedException === '\PHPUnit_Framework_Exception' ||
                    $reflector->isSubclassOf('PHPUnit_Framework_Exception')) {
                    $checkException = true;
                }
            }

            if ($checkException) {
                $this->assertThat(
                    $e,
                    new PHPUnit_Framework_Constraint_Exception(
                        $this->expectedException
                    )
                );

                if ($this->expectedExceptionMessage !== null) {
                    $this->assertThat(
                        $e,
                        new PHPUnit_Framework_Constraint_ExceptionMessage(
                            $this->expectedExceptionMessage
                        )
                    );
                }

                if ($this->expectedExceptionMessageRegExp !== null) {
                    $this->assertThat(
                        $e,
                        new PHPUnit_Framework_Constraint_ExceptionMessageRegExp(
                            $this->expectedExceptionMessageRegExp
                        )
                    );
                }

                if ($this->expectedExceptionCode !== null) {
                    $this->assertThat(
                        $e,
                        new PHPUnit_Framework_Constraint_ExceptionCode(
                            $this->expectedExceptionCode
                        )
                    );
                }

                return;
            } else {
                throw $e;
            }
        }

        if ($this->expectedException !== null) {
            $this->assertThat(
                null,
                new PHPUnit_Framework_Constraint_Exception(
                    $this->expectedException
                )
            );
        }

        return $testResult;
    }

    /**
     * Verifies the mock object expectations.
     */
    protected function verifyMockObjects()
    {
        foreach ($this->mockObjects as $mockObject) {
            if ($mockObject->__phpunit_hasMatchers()) {
                $this->numAssertions++;
            }

            $mockObject->__phpunit_verify(
                $this->shouldInvocationMockerBeReset($mockObject)
            );
        }

        if ($this->prophet !== null) {
            try {
                $this->prophet->checkPredictions();
            } catch (Throwable $t) {
                /* Intentionally left empty */
            } catch (Exception $t) {
                /* Intentionally left empty */
            }

            foreach ($this->prophet->getProphecies() as $objectProphecy) {
                foreach ($objectProphecy->getMethodProphecies() as $methodProphecies) {
                    foreach ($methodProphecies as $methodProphecy) {
                        $this->numAssertions += count($methodProphecy->getCheckedPredictions());
                    }
                }
            }

            if (isset($t)) {
                throw $t;
            }
        }
    }

    /**
     * Sets the name of a TestCase.
     *
     * @param  string
     */
    public function setName($name)
    {
        $this->name = $name;
    }

    /**
     * Sets the dependencies of a TestCase.
     *
     * @param array $dependencies
     */
    public function setDependencies(array $dependencies)
    {
        $this->dependencies = $dependencies;
    }

    /**
     * Returns true if the tests has dependencies
     *
     * @return bool
     */
    public function hasDependencies()
    {
        return count($this->dependencies) > 0;
    }

    /**
     * Sets
     *
     * @param array $dependencyInput
     */
    public function setDependencyInput(array $dependencyInput)
    {
        $this->dependencyInput = $dependencyInput;
    }

    /**
     * @param bool $beStrictAboutChangesToGlobalState
     */
    public function setBeStrictAboutChangesToGlobalState($beStrictAboutChangesToGlobalState)
    {
        $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState;
    }

    /**
     * Calling this method in setUp() has no effect!
     *
     * @param bool $backupGlobals
     */
    public function setBackupGlobals($backupGlobals)
    {
        if (is_null($this->backupGlobals) && is_bool($backupGlobals)) {
            $this->backupGlobals = $backupGlobals;
        }
    }

    /**
     * Calling this method in setUp() has no effect!
     *
     * @param bool $backupStaticAttributes
     */
    public function setBackupStaticAttributes($backupStaticAttributes)
    {
        if (is_null($this->backupStaticAttributes) &&
            is_bool($backupStaticAttributes)) {
            $this->backupStaticAttributes = $backupStaticAttributes;
        }
    }

    /**
     * @param bool $runTestInSeparateProcess
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function setRunTestInSeparateProcess($runTestInSeparateProcess)
    {
        if (is_bool($runTestInSeparateProcess)) {
            if ($this->runTestInSeparateProcess === null) {
                $this->runTestInSeparateProcess = $runTestInSeparateProcess;
            }
        } else {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');
        }
    }

    /**
     * @param bool $preserveGlobalState
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function setPreserveGlobalState($preserveGlobalState)
    {
        if (is_bool($preserveGlobalState)) {
            $this->preserveGlobalState = $preserveGlobalState;
        } else {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');
        }
    }

    /**
     * @param bool $inIsolation
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function setInIsolation($inIsolation)
    {
        if (is_bool($inIsolation)) {
            $this->inIsolation = $inIsolation;
        } else {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');
        }
    }

    /**
     * @return bool
     */
    public function isInIsolation()
    {
        return $this->inIsolation;
    }

    /**
     * @return mixed
     */
    public function getResult()
    {
        return $this->testResult;
    }

    /**
     * @param mixed $result
     */
    public function setResult($result)
    {
        $this->testResult = $result;
    }

    /**
     * @param callable $callback
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function setOutputCallback($callback)
    {
        if (!is_callable($callback)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'callback');
        }

        $this->outputCallback = $callback;
    }

    /**
     * @return PHPUnit_Framework_TestResult
     */
    public function getTestResultObject()
    {
        return $this->result;
    }

    /**
     * @param PHPUnit_Framework_TestResult $result
     */
    public function setTestResultObject(PHPUnit_Framework_TestResult $result)
    {
        $this->result = $result;
    }

    /**
     * @param PHPUnit_Framework_MockObject_MockObject $mockObject
     */
    public function registerMockObject(PHPUnit_Framework_MockObject_MockObject $mockObject)
    {
        $this->mockObjects[] = $mockObject;
    }

    /**
     * This method is a wrapper for the ini_set() function that automatically
     * resets the modified php.ini setting to its original value after the
     * test is run.
     *
     * @param string $varName
     * @param string $newValue
     *
     * @throws PHPUnit_Framework_Exception
     */
    protected function iniSet($varName, $newValue)
    {
        if (!is_string($varName)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        $currentValue = ini_set($varName, $newValue);

        if ($currentValue !== false) {
            $this->iniSettings[$varName] = $currentValue;
        } else {
            throw new PHPUnit_Framework_Exception(
                sprintf(
                    'INI setting "%s" could not be set to "%s".',
                    $varName,
                    $newValue
                )
            );
        }
    }

    /**
     * This method is a wrapper for the setlocale() function that automatically
     * resets the locale to its original value after the test is run.
     *
     * @param int    $category
     * @param string $locale
     *
     * @throws PHPUnit_Framework_Exception
     */
    protected function setLocale()
    {
        $args = func_get_args();

        if (count($args) < 2) {
            throw new PHPUnit_Framework_Exception;
        }

        $category = $args[0];
        $locale   = $args[1];

        $categories = [
            LC_ALL, LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME
        ];

        if (defined('LC_MESSAGES')) {
            $categories[] = LC_MESSAGES;
        }

        if (!in_array($category, $categories)) {
            throw new PHPUnit_Framework_Exception;
        }

        if (!is_array($locale) && !is_string($locale)) {
            throw new PHPUnit_Framework_Exception;
        }

        $this->locale[$category] = setlocale($category, 0);

        $result = call_user_func_array('setlocale', $args);

        if ($result === false) {
            throw new PHPUnit_Framework_Exception(
                'The locale functionality is not implemented on your platform, ' .
                'the specified locale does not exist or the category name is ' .
                'invalid.'
            );
        }
    }

    /**
     * Returns a builder object to create mock objects using a fluent interface.
     *
     * @param string|string[] $className
     *
     * @return PHPUnit_Framework_MockObject_MockBuilder
     */
    public function getMockBuilder($className)
    {
        return new PHPUnit_Framework_MockObject_MockBuilder($this, $className);
    }

    /**
     * Returns a test double for the specified class.
     *
     * @param string $originalClassName
     *
     * @return PHPUnit_Framework_MockObject_MockObject
     *
     * @throws PHPUnit_Framework_Exception
     */
    protected function createMock($originalClassName)
    {
        return $this->getMockBuilder($originalClassName)
                    ->disableOriginalConstructor()
                    ->disableOriginalClone()
                    ->disableArgumentCloning()
                    ->disallowMockingUnknownTypes()
                    ->getMock();
    }

    /**
     * Returns a configured test double for the specified class.
     *
     * @param string $originalClassName
     * @param array  $configuration
     *
     * @return PHPUnit_Framework_MockObject_MockObject
     *
     * @throws PHPUnit_Framework_Exception
     */
    protected function createConfiguredMock($originalClassName, array $configuration)
    {
        $o = $this->createMock($originalClassName);

        foreach ($configuration as $method => $return) {
            $o->method($method)->willReturn($return);
        }

        return $o;
    }

    /**
     * Returns a partial test double for the specified class.
     *
     * @param string $originalClassName
     * @param array  $methods
     *
     * @return PHPUnit_Framework_MockObject_MockObject
     *
     * @throws PHPUnit_Framework_Exception
     */
    protected function createPartialMock($originalClassName, array $methods)
    {
        return $this->getMockBuilder($originalClassName)
                    ->disableOriginalConstructor()
                    ->disableOriginalClone()
                    ->disableArgumentCloning()
                    ->disallowMockingUnknownTypes()
                    ->setMethods(empty($methods) ? null : $methods)
                    ->getMock();
    }

    /**
     * Returns a mock object for the specified class.
     *
     * @param string     $originalClassName       Name of the class to mock.
     * @param array|null $methods                 When provided, only methods whose names are in the array
     *                                            are replaced with a configurable test double. The behavior
     *                                            of the other methods is not changed.
     *                                            Providing null means that no methods will be replaced.
     * @param array      $arguments               Parameters to pass to the original class' constructor.
     * @param string     $mockClassName           Class name for the generated test double class.
     * @param bool       $callOriginalConstructor Can be used to disable the call to the original class' constructor.
     * @param bool       $callOriginalClone       Can be used to disable the call to the original class' clone constructor.
     * @param bool       $callAutoload            Can be used to disable __autoload() during the generation of the test double class.
     * @param bool       $cloneArguments
     * @param bool       $callOriginalMethods
     * @param object     $proxyTarget
     *
     * @return PHPUnit_Framework_MockObject_MockObject
     *
     * @throws PHPUnit_Framework_Exception
     *
     * @deprecated Method deprecated since Release 5.4.0; use createMock() or getMockBuilder() instead
     */
    protected function getMock($originalClassName, $methods = [], array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $cloneArguments = false, $callOriginalMethods = false, $proxyTarget = null)
    {
        $this->warnings[] = 'PHPUnit_Framework_TestCase::getMock() is deprecated, use PHPUnit_Framework_TestCase::createMock() or PHPUnit_Framework_TestCase::getMockBuilder() instead';

        $mockObject = $this->getMockObjectGenerator()->getMock(
            $originalClassName,
            $methods,
            $arguments,
            $mockClassName,
            $callOriginalConstructor,
            $callOriginalClone,
            $callAutoload,
            $cloneArguments,
            $callOriginalMethods,
            $proxyTarget
        );

        $this->registerMockObject($mockObject);

        return $mockObject;
    }

    /**
     * Returns a mock with disabled constructor object for the specified class.
     *
     * @param string $originalClassName
     *
     * @return PHPUnit_Framework_MockObject_MockObject
     *
     * @throws PHPUnit_Framework_Exception
     *
     * @deprecated Method deprecated since Release 5.4.0; use createMock() instead
     */
    protected function getMockWithoutInvokingTheOriginalConstructor($originalClassName)
    {
        $this->warnings[] = 'PHPUnit_Framework_TestCase::getMockWithoutInvokingTheOriginalConstructor() is deprecated, use PHPUnit_Framework_TestCase::createMock() instead';

        return $this->getMockBuilder($originalClassName)
                    ->disableOriginalConstructor()
                    ->getMock();
    }

    /**
     * Mocks the specified class and returns the name of the mocked class.
     *
     * @param string $originalClassName
     * @param array  $methods
     * @param array  $arguments
     * @param string $mockClassName
     * @param bool   $callOriginalConstructor
     * @param bool   $callOriginalClone
     * @param bool   $callAutoload
     * @param bool   $cloneArguments
     *
     * @return string
     *
     * @throws PHPUnit_Framework_Exception
     */
    protected function getMockClass($originalClassName, $methods = [], array $arguments = [], $mockClassName = '', $callOriginalConstructor = false, $callOriginalClone = true, $callAutoload = true, $cloneArguments = false)
    {
        $mock = $this->getMockObjectGenerator()->getMock(
            $originalClassName,
            $methods,
            $arguments,
            $mockClassName,
            $callOriginalConstructor,
            $callOriginalClone,
            $callAutoload,
            $cloneArguments
        );

        return get_class($mock);
    }

    /**
     * Returns a mock object for the specified abstract class with all abstract
     * methods of the class mocked. Concrete methods are not mocked by default.
     * To mock concrete methods, use the 7th parameter ($mockedMethods).
     *
     * @param string $originalClassName
     * @param array  $arguments
     * @param string $mockClassName
     * @param bool   $callOriginalConstructor
     * @param bool   $callOriginalClone
     * @param bool   $callAutoload
     * @param array  $mockedMethods
     * @param bool   $cloneArguments
     *
     * @return PHPUnit_Framework_MockObject_MockObject
     *
     * @throws PHPUnit_Framework_Exception
     */
    protected function getMockForAbstractClass($originalClassName, array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = [], $cloneArguments = false)
    {
        $mockObject = $this->getMockObjectGenerator()->getMockForAbstractClass(
            $originalClassName,
            $arguments,
            $mockClassName,
            $callOriginalConstructor,
            $callOriginalClone,
            $callAutoload,
            $mockedMethods,
            $cloneArguments
        );

        $this->registerMockObject($mockObject);

        return $mockObject;
    }

    /**
     * Returns a mock object based on the given WSDL file.
     *
     * @param string $wsdlFile
     * @param string $originalClassName
     * @param string $mockClassName
     * @param array  $methods
     * @param bool   $callOriginalConstructor
     * @param array  $options                 An array of options passed to SOAPClient::_construct
     *
     * @return PHPUnit_Framework_MockObject_MockObject
     */
    protected function getMockFromWsdl($wsdlFile, $originalClassName = '', $mockClassName = '', array $methods = [], $callOriginalConstructor = true, array $options = [])
    {
        if ($originalClassName === '') {
            $originalClassName = pathinfo(basename(parse_url($wsdlFile)['path']), PATHINFO_FILENAME);
        }

        if (!class_exists($originalClassName)) {
            eval(
                $this->getMockObjectGenerator()->generateClassFromWsdl(
                    $wsdlFile,
                    $originalClassName,
                    $methods,
                    $options
                )
            );
        }

        $mockObject = $this->getMockObjectGenerator()->getMock(
            $originalClassName,
            $methods,
            ['', $options],
            $mockClassName,
            $callOriginalConstructor,
            false,
            false
        );

        $this->registerMockObject($mockObject);

        return $mockObject;
    }

    /**
     * Returns a mock object for the specified trait with all abstract methods
     * of the trait mocked. Concrete methods to mock can be specified with the
     * `$mockedMethods` parameter.
     *
     * @param string $traitName
     * @param array  $arguments
     * @param string $mockClassName
     * @param bool   $callOriginalConstructor
     * @param bool   $callOriginalClone
     * @param bool   $callAutoload
     * @param array  $mockedMethods
     * @param bool   $cloneArguments
     *
     * @return PHPUnit_Framework_MockObject_MockObject
     *
     * @throws PHPUnit_Framework_Exception
     */
    protected function getMockForTrait($traitName, array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = [], $cloneArguments = false)
    {
        $mockObject = $this->getMockObjectGenerator()->getMockForTrait(
            $traitName,
            $arguments,
            $mockClassName,
            $callOriginalConstructor,
            $callOriginalClone,
            $callAutoload,
            $mockedMethods,
            $cloneArguments
        );

        $this->registerMockObject($mockObject);

        return $mockObject;
    }

    /**
     * Returns an object for the specified trait.
     *
     * @param string $traitName
     * @param array  $arguments
     * @param string $traitClassName
     * @param bool   $callOriginalConstructor
     * @param bool   $callOriginalClone
     * @param bool   $callAutoload
     * @param bool   $cloneArguments
     *
     * @return object
     *
     * @throws PHPUnit_Framework_Exception
     */
    protected function getObjectForTrait($traitName, array $arguments = [], $traitClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $cloneArguments = false)
    {
        return $this->getMockObjectGenerator()->getObjectForTrait(
            $traitName,
            $arguments,
            $traitClassName,
            $callOriginalConstructor,
            $callOriginalClone,
            $callAutoload,
            $cloneArguments
        );
    }

    /**
     * @param string|null $classOrInterface
     *
     * @return \Prophecy\Prophecy\ObjectProphecy
     *
     * @throws \LogicException
     */
    protected function prophesize($classOrInterface = null)
    {
        return $this->getProphet()->prophesize($classOrInterface);
    }

    /**
     * Adds a value to the assertion counter.
     *
     * @param int $count
     */
    public function addToAssertionCount($count)
    {
        $this->numAssertions += $count;
    }

    /**
     * Returns the number of assertions performed by this test.
     *
     * @return int
     */
    public function getNumAssertions()
    {
        return $this->numAssertions;
    }

    /**
     * Returns a matcher that matches when the method is executed
     * zero or more times.
     *
     * @return PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount
     */
    public static function any()
    {
        return new PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount;
    }

    /**
     * Returns a matcher that matches when the method is never executed.
     *
     * @return PHPUnit_Framework_MockObject_Matcher_InvokedCount
     */
    public static function never()
    {
        return new PHPUnit_Framework_MockObject_Matcher_InvokedCount(0);
    }

    /**
     * Returns a matcher that matches when the method is executed
     * at least N times.
     *
     * @param int $requiredInvocations
     *
     * @return PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastCount
     */
    public static function atLeast($requiredInvocations)
    {
        return new PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastCount(
            $requiredInvocations
        );
    }

    /**
     * Returns a matcher that matches when the method is executed at least once.
     *
     * @return PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastOnce
     */
    public static function atLeastOnce()
    {
        return new PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastOnce;
    }

    /**
     * Returns a matcher that matches when the method is executed exactly once.
     *
     * @return PHPUnit_Framework_MockObject_Matcher_InvokedCount
     */
    public static function once()
    {
        return new PHPUnit_Framework_MockObject_Matcher_InvokedCount(1);
    }

    /**
     * Returns a matcher that matches when the method is executed
     * exactly $count times.
     *
     * @param int $count
     *
     * @return PHPUnit_Framework_MockObject_Matcher_InvokedCount
     */
    public static function exactly($count)
    {
        return new PHPUnit_Framework_MockObject_Matcher_InvokedCount($count);
    }

    /**
     * Returns a matcher that matches when the method is executed
     * at most N times.
     *
     * @param int $allowedInvocations
     *
     * @return PHPUnit_Framework_MockObject_Matcher_InvokedAtMostCount
     */
    public static function atMost($allowedInvocations)
    {
        return new PHPUnit_Framework_MockObject_Matcher_InvokedAtMostCount(
            $allowedInvocations
        );
    }

    /**
     * Returns a matcher that matches when the method is executed
     * at the given index.
     *
     * @param int $index
     *
     * @return PHPUnit_Framework_MockObject_Matcher_InvokedAtIndex
     */
    public static function at($index)
    {
        return new PHPUnit_Framework_MockObject_Matcher_InvokedAtIndex($index);
    }

    /**
     * @param mixed $value
     *
     * @return PHPUnit_Framework_MockObject_Stub_Return
     */
    public static function returnValue($value)
    {
        return new PHPUnit_Framework_MockObject_Stub_Return($value);
    }

    /**
     * @param array $valueMap
     *
     * @return PHPUnit_Framework_MockObject_Stub_ReturnValueMap
     */
    public static function returnValueMap(array $valueMap)
    {
        return new PHPUnit_Framework_MockObject_Stub_ReturnValueMap($valueMap);
    }

    /**
     * @param int $argumentIndex
     *
     * @return PHPUnit_Framework_MockObject_Stub_ReturnArgument
     */
    public static function returnArgument($argumentIndex)
    {
        return new PHPUnit_Framework_MockObject_Stub_ReturnArgument(
            $argumentIndex
        );
    }

    /**
     * @param mixed $callback
     *
     * @return PHPUnit_Framework_MockObject_Stub_ReturnCallback
     */
    public static function returnCallback($callback)
    {
        return new PHPUnit_Framework_MockObject_Stub_ReturnCallback($callback);
    }

    /**
     * Returns the current object.
     *
     * This method is useful when mocking a fluent interface.
     *
     * @return PHPUnit_Framework_MockObject_Stub_ReturnSelf
     */
    public static function returnSelf()
    {
        return new PHPUnit_Framework_MockObject_Stub_ReturnSelf();
    }

    /**
     * @param Throwable|Exception $exception
     *
     * @return PHPUnit_Framework_MockObject_Stub_Exception
     *
     * @todo   Add type declaration when support for PHP 5 is dropped
     */
    public static function throwException($exception)
    {
        return new PHPUnit_Framework_MockObject_Stub_Exception($exception);
    }

    /**
     * @param mixed $value, ...
     *
     * @return PHPUnit_Framework_MockObject_Stub_ConsecutiveCalls
     */
    public static function onConsecutiveCalls()
    {
        $args = func_get_args();

        return new PHPUnit_Framework_MockObject_Stub_ConsecutiveCalls($args);
    }

    /**
     * @return bool
     */
    public function usesDataProvider()
    {
        return !empty($this->data);
    }

    /**
     * @return string
     */
    public function dataDescription()
    {
        return is_string($this->dataName) ? $this->dataName : '';
    }

    /**
     * Gets the data set description of a TestCase.
     *
     * @param bool $includeData
     *
     * @return string
     */
    protected function getDataSetAsString($includeData = true)
    {
        $buffer = '';

        if (!empty($this->data)) {
            if (is_int($this->dataName)) {
                $buffer .= sprintf(' with data set #%d', $this->dataName);
            } else {
                $buffer .= sprintf(' with data set "%s"', $this->dataName);
            }

            $exporter = new Exporter;

            if ($includeData) {
                $buffer .= sprintf(' (%s)', $exporter->shortenedRecursiveExport($this->data));
            }
        }

        return $buffer;
    }

    /**
     * Gets the data set of a TestCase.
     *
     * @return array
     */
    protected function getProvidedData()
    {
        return $this->data;
    }

    /**
     * Creates a default TestResult object.
     *
     * @return PHPUnit_Framework_TestResult
     */
    protected function createResult()
    {
        return new PHPUnit_Framework_TestResult;
    }

    protected function handleDependencies()
    {
        if (!empty($this->dependencies) && !$this->inIsolation) {
            $className  = get_class($this);
            $passed     = $this->result->passed();
            $passedKeys = array_keys($passed);
            $numKeys    = count($passedKeys);

            for ($i = 0; $i < $numKeys; $i++) {
                $pos = strpos($passedKeys[$i], ' with data set');

                if ($pos !== false) {
                    $passedKeys[$i] = substr($passedKeys[$i], 0, $pos);
                }
            }

            $passedKeys = array_flip(array_unique($passedKeys));

            foreach ($this->dependencies as $dependency) {
                $clone = false;

                if (strpos($dependency, 'clone ') === 0) {
                    $clone      = true;
                    $dependency = substr($dependency, strlen('clone '));
                } elseif (strpos($dependency, '!clone ') === 0) {
                    $clone      = false;
                    $dependency = substr($dependency, strlen('!clone '));
                }

                if (strpos($dependency, '::') === false) {
                    $dependency = $className . '::' . $dependency;
                }

                if (!isset($passedKeys[$dependency])) {
                    $this->result->startTest($this);
                    $this->result->addError(
                        $this,
                        new PHPUnit_Framework_SkippedTestError(
                            sprintf(
                                'This test depends on "%s" to pass.',
                                $dependency
                            )
                        ),
                        0
                    );
                    $this->result->endTest($this, 0);

                    return false;
                }

                if (isset($passed[$dependency])) {
                    if ($passed[$dependency]['size'] != PHPUnit_Util_Test::UNKNOWN &&
                        $this->getSize() != PHPUnit_Util_Test::UNKNOWN &&
                        $passed[$dependency]['size'] > $this->getSize()) {
                        $this->result->addError(
                            $this,
                            new PHPUnit_Framework_SkippedTestError(
                                'This test depends on a test that is larger than itself.'
                            ),
                            0
                        );

                        return false;
                    }

                    if ($clone) {
                        $deepCopy   = new DeepCopy;
                        $deepCopy->skipUncloneable(false);

                        $this->dependencyInput[$dependency] = $deepCopy->copy($passed[$dependency]['result']);
                    } else {
                        $this->dependencyInput[$dependency] = $passed[$dependency]['result'];
                    }
                } else {
                    $this->dependencyInput[$dependency] = null;
                }
            }
        }

        return true;
    }

    /**
     * This method is called before the first test of this test class is run.
     */
    public static function setUpBeforeClass()
    {
    }

    /**
     * Sets up the fixture, for example, open a network connection.
     * This method is called before a test is executed.
     */
    protected function setUp()
    {
    }

    /**
     * Performs assertions shared by all tests of a test case.
     *
     * This method is called before the execution of a test starts
     * and after setUp() is called.
     */
    protected function assertPreConditions()
    {
    }

    /**
     * Performs assertions shared by all tests of a test case.
     *
     * This method is called after the execution of a test ends
     * and before tearDown() is called.
     */
    protected function assertPostConditions()
    {
    }

    /**
     * Tears down the fixture, for example, close a network connection.
     * This method is called after a test is executed.
     */
    protected function tearDown()
    {
    }

    /**
     * This method is called after the last test of this test class is run.
     */
    public static function tearDownAfterClass()
    {
    }

    /**
     * This method is called when a test method did not execute successfully.
     *
     * @param Exception|Throwable $e
     *
     * @throws Exception|Throwable
     */
    protected function onNotSuccessfulTest($e)
    {
        $expected = PHP_MAJOR_VERSION >= 7 ? 'Throwable' : 'Exception';

        if ($e instanceof $expected) {
            throw $e;
        }

        throw PHPUnit_Util_InvalidArgumentHelper::factory(
            1,
            'Throwable or Exception'
        );
    }

    /**
     * Performs custom preparations on the process isolation template.
     *
     * @param Text_Template $template
     */
    protected function prepareTemplate(Text_Template $template)
    {
    }

    /**
     * Get the mock object generator, creating it if it doesn't exist.
     *
     * @return PHPUnit_Framework_MockObject_Generator
     */
    protected function getMockObjectGenerator()
    {
        if (null === $this->mockObjectGenerator) {
            $this->mockObjectGenerator = new PHPUnit_Framework_MockObject_Generator;
        }

        return $this->mockObjectGenerator;
    }

    private function startOutputBuffering()
    {
        ob_start();

        $this->outputBufferingActive = true;
        $this->outputBufferingLevel  = ob_get_level();
    }

    private function stopOutputBuffering()
    {
        if (ob_get_level() != $this->outputBufferingLevel) {
            while (ob_get_level() >= $this->outputBufferingLevel) {
                ob_end_clean();
            }

            throw new PHPUnit_Framework_RiskyTestError(
                'Test code or tested code did not (only) close its own output buffers'
            );
        }

        $output = ob_get_contents();

        if ($this->outputCallback === false) {
            $this->output = $output;
        } else {
            $this->output = call_user_func_array(
                $this->outputCallback,
                [$output]
            );
        }

        ob_end_clean();

        $this->outputBufferingActive = false;
        $this->outputBufferingLevel  = ob_get_level();
    }

    private function snapshotGlobalState()
    {
        $backupGlobals = $this->backupGlobals === null || $this->backupGlobals === true;

        if ($this->runTestInSeparateProcess ||
            $this->inIsolation ||
            (!$backupGlobals && !$this->backupStaticAttributes)) {
            return;
        }

        $this->snapshot = $this->createGlobalStateSnapshot($backupGlobals);
    }

    private function restoreGlobalState()
    {
        if (!$this->snapshot instanceof Snapshot) {
            return;
        }

        $backupGlobals = $this->backupGlobals === null || $this->backupGlobals === true;

        if ($this->beStrictAboutChangesToGlobalState) {
            try {
                $this->compareGlobalStateSnapshots(
                    $this->snapshot,
                    $this->createGlobalStateSnapshot($backupGlobals)
                );
            } catch (PHPUnit_Framework_RiskyTestError $rte) {
                // Intentionally left empty
            }
        }

        $restorer = new Restorer;

        if ($backupGlobals) {
            $restorer->restoreGlobalVariables($this->snapshot);
        }

        if ($this->backupStaticAttributes) {
            $restorer->restoreStaticAttributes($this->snapshot);
        }

        $this->snapshot = null;

        if (isset($rte)) {
            throw $rte;
        }
    }

    /**
     * @param bool $backupGlobals
     *
     * @return Snapshot
     */
    private function createGlobalStateSnapshot($backupGlobals)
    {
        $blacklist = new Blacklist;

        foreach ($this->backupGlobalsBlacklist as $globalVariable) {
            $blacklist->addGlobalVariable($globalVariable);
        }

        if (!defined('PHPUNIT_TESTSUITE')) {
            $blacklist->addClassNamePrefix('PHPUnit');
            $blacklist->addClassNamePrefix('File_Iterator');
            $blacklist->addClassNamePrefix('SebastianBergmann\CodeCoverage');
            $blacklist->addClassNamePrefix('PHP_Invoker');
            $blacklist->addClassNamePrefix('PHP_Timer');
            $blacklist->addClassNamePrefix('PHP_Token');
            $blacklist->addClassNamePrefix('Symfony');
            $blacklist->addClassNamePrefix('Text_Template');
            $blacklist->addClassNamePrefix('Doctrine\Instantiator');
            $blacklist->addClassNamePrefix('Prophecy');

            foreach ($this->backupStaticAttributesBlacklist as $class => $attributes) {
                foreach ($attributes as $attribute) {
                    $blacklist->addStaticAttribute($class, $attribute);
                }
            }
        }

        return new Snapshot(
            $blacklist,
            $backupGlobals,
            (bool) $this->backupStaticAttributes,
            false,
            false,
            false,
            false,
            false,
            false,
            false
        );
    }

    /**
     * @param Snapshot $before
     * @param Snapshot $after
     *
     * @throws PHPUnit_Framework_RiskyTestError
     */
    private function compareGlobalStateSnapshots(Snapshot $before, Snapshot $after)
    {
        $backupGlobals = $this->backupGlobals === null || $this->backupGlobals === true;

        if ($backupGlobals) {
            $this->compareGlobalStateSnapshotPart(
                $before->globalVariables(),
                $after->globalVariables(),
                "--- Global variables before the test\n+++ Global variables after the test\n"
            );

            $this->compareGlobalStateSnapshotPart(
                $before->superGlobalVariables(),
                $after->superGlobalVariables(),
                "--- Super-global variables before the test\n+++ Super-global variables after the test\n"
            );
        }

        if ($this->backupStaticAttributes) {
            $this->compareGlobalStateSnapshotPart(
                $before->staticAttributes(),
                $after->staticAttributes(),
                "--- Static attributes before the test\n+++ Static attributes after the test\n"
            );
        }
    }

    /**
     * @param array  $before
     * @param array  $after
     * @param string $header
     *
     * @throws PHPUnit_Framework_RiskyTestError
     */
    private function compareGlobalStateSnapshotPart(array $before, array $after, $header)
    {
        if ($before != $after) {
            $differ   = new Differ($header);
            $exporter = new Exporter;

            $diff = $differ->diff(
                $exporter->export($before),
                $exporter->export($after)
            );

            throw new PHPUnit_Framework_RiskyTestError(
                $diff
            );
        }
    }

    /**
     * @return Prophecy\Prophet
     */
    private function getProphet()
    {
        if ($this->prophet === null) {
            $this->prophet = new Prophet;
        }

        return $this->prophet;
    }

    /**
     * @param PHPUnit_Framework_MockObject_MockObject $mock
     *
     * @return bool
     */
    private function shouldInvocationMockerBeReset(PHPUnit_Framework_MockObject_MockObject $mock)
    {
        $enumerator = new Enumerator;

        foreach ($enumerator->enumerate($this->dependencyInput) as $object) {
            if ($mock === $object) {
                return false;
            }
        }

        if (!is_array($this->testResult) && !is_object($this->testResult)) {
            return true;
        }

        foreach ($enumerator->enumerate($this->testResult) as $object) {
            if ($mock === $object) {
                return false;
            }
        }

        return true;
    }

    /**
     * @param array $testArguments
     * @param array $originalTestArguments
     */
    private function registerMockObjectsFromTestArguments(array $testArguments, array &$visited = [])
    {
        if ($this->registerMockObjectsFromTestArgumentsRecursively) {
            $enumerator = new Enumerator;

            foreach ($enumerator->enumerate($testArguments) as $object) {
                if ($object instanceof PHPUnit_Framework_MockObject_MockObject) {
                    $this->registerMockObject($object);
                }
            }
        } else {
            foreach ($testArguments as $testArgument) {
                if ($testArgument instanceof PHPUnit_Framework_MockObject_MockObject) {
                    if ($this->isCloneable($testArgument)) {
                        $testArgument = clone $testArgument;
                    }

                    $this->registerMockObject($testArgument);
                } elseif (is_array($testArgument) && !in_array($testArgument, $visited, true)) {
                    $visited[] = $testArgument;

                    $this->registerMockObjectsFromTestArguments(
                        $testArgument,
                        $visited
                    );
                }
            }
        }
    }

    private function setDoesNotPerformAssertionsFromAnnotation()
    {
        $annotations = $this->getAnnotations();

        if (isset($annotations['method']['doesNotPerformAssertions'])) {
            $this->doesNotPerformAssertions = true;
        }
    }

    /**
     * @param PHPUnit_Framework_MockObject_MockObject $testArgument
     *
     * @return bool
     */
    private function isCloneable(PHPUnit_Framework_MockObject_MockObject $testArgument)
    {
        $reflector = new ReflectionObject($testArgument);

        if (!$reflector->isCloneable()) {
            return false;
        }

        if ($reflector->hasMethod('__clone') &&
            $reflector->getMethod('__clone')->isPublic()) {
            return true;
        }

        return false;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A warning.
 */
class PHPUnit_Framework_WarningTestCase extends PHPUnit_Framework_TestCase
{
    /**
     * @var string
     */
    protected $message = '';

    /**
     * @var bool
     */
    protected $backupGlobals = false;

    /**
     * @var bool
     */
    protected $backupStaticAttributes = false;

    /**
     * @var bool
     */
    protected $runTestInSeparateProcess = false;

    /**
     * @var bool
     */
    protected $useErrorHandler = false;

    /**
     * @param string $message
     */
    public function __construct($message = '')
    {
        $this->message = $message;
        parent::__construct('Warning');
    }

    /**
     * @throws PHPUnit_Framework_Exception
     */
    protected function runTest()
    {
        throw new PHPUnit_Framework_Warning($this->message);
    }

    /**
     * @return string
     */
    public function getMessage()
    {
        return $this->message;
    }

    /**
     * Returns a string representation of the test case.
     *
     * @return string
     */
    public function toString()
    {
        return 'Warning';
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that asserts that the object it is evaluated for is an instance
 * of a given class.
 *
 * The expected class name is passed in the constructor.
 */
class PHPUnit_Framework_Constraint_IsInstanceOf extends PHPUnit_Framework_Constraint
{
    /**
     * @var string
     */
    protected $className;

    /**
     * @param string $className
     */
    public function __construct($className)
    {
        parent::__construct();
        $this->className = $className;
    }

    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        return ($other instanceof $this->className);
    }

    /**
     * Returns the description of the failure
     *
     * The beginning of failure messages is "Failed asserting that" in most
     * cases. This method should return the second part of that sentence.
     *
     * @param mixed $other Evaluated value or object.
     *
     * @return string
     */
    protected function failureDescription($other)
    {
        return sprintf(
            '%s is an instance of %s "%s"',
            $this->exporter->shortenedExport($other),
            $this->getType(),
            $this->className
        );
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        return sprintf(
            'is instance of %s "%s"',
            $this->getType(),
            $this->className
        );
    }

    private function getType()
    {
        try {
            $reflection = new ReflectionClass($this->className);
            if ($reflection->isInterface()) {
                return 'interface';
            }
        } catch (ReflectionException $e) {
        }

        return 'class';
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that checks if the file/dir(name) that it is evaluated for is readable.
 *
 * The file path to check is passed as $other in evaluate().
 */
class PHPUnit_Framework_Constraint_IsReadable extends PHPUnit_Framework_Constraint
{
    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        return is_readable($other);
    }

    /**
     * Returns the description of the failure
     *
     * The beginning of failure messages is "Failed asserting that" in most
     * cases. This method should return the second part of that sentence.
     *
     * @param mixed $other Evaluated value or object.
     *
     * @return string
     */
    protected function failureDescription($other)
    {
        return sprintf(
            '"%s" is readable',
            $other
        );
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        return 'is readable';
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that accepts false.
 */
class PHPUnit_Framework_Constraint_IsFalse extends PHPUnit_Framework_Constraint
{
    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        return $other === false;
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        return 'is false';
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that asserts that the value it is evaluated for is less than
 * a given value.
 */
class PHPUnit_Framework_Constraint_LessThan extends PHPUnit_Framework_Constraint
{
    /**
     * @var numeric
     */
    protected $value;

    /**
     * @param numeric $value
     */
    public function __construct($value)
    {
        parent::__construct();
        $this->value = $value;
    }

    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        return $this->value > $other;
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        return 'is less than ' . $this->exporter->export($this->value);
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that checks if the directory(name) that it is evaluated for exists.
 *
 * The file path to check is passed as $other in evaluate().
 */
class PHPUnit_Framework_Constraint_DirectoryExists extends PHPUnit_Framework_Constraint
{
    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        return is_dir($other);
    }

    /**
     * Returns the description of the failure
     *
     * The beginning of failure messages is "Failed asserting that" in most
     * cases. This method should return the second part of that sentence.
     *
     * @param mixed $other Evaluated value or object.
     *
     * @return string
     */
    protected function failureDescription($other)
    {
        return sprintf(
            'directory "%s" exists',
            $other
        );
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        return 'directory exists';
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that accepts any input value.
 */
class PHPUnit_Framework_Constraint_IsAnything extends PHPUnit_Framework_Constraint
{
    /**
     * Evaluates the constraint for parameter $other
     *
     * If $returnResult is set to false (the default), an exception is thrown
     * in case of a failure. null is returned otherwise.
     *
     * If $returnResult is true, the result of the evaluation is returned as
     * a boolean value instead: true in case of success, false in case of a
     * failure.
     *
     * @param mixed  $other        Value or object to evaluate.
     * @param string $description  Additional information about the test
     * @param bool   $returnResult Whether to return a result or throw an exception
     *
     * @return mixed
     *
     * @throws PHPUnit_Framework_ExpectationFailedException
     */
    public function evaluate($other, $description = '', $returnResult = false)
    {
        return $returnResult ? true : null;
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        return 'is anything';
    }

    /**
     * Counts the number of constraint elements.
     *
     * @return int
     */
    public function count()
    {
        return 0;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

class PHPUnit_Framework_Constraint_ExceptionMessage extends PHPUnit_Framework_Constraint
{
    /**
     * @var int
     */
    protected $expectedMessage;

    /**
     * @param string $expected
     */
    public function __construct($expected)
    {
        parent::__construct();
        $this->expectedMessage = $expected;
    }

    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param Exception $other
     *
     * @return bool
     */
    protected function matches($other)
    {
        if ($this->expectedMessage === '') {
            return $other->getMessage() === '';
        }

        return strpos($other->getMessage(), $this->expectedMessage) !== false;
    }

    /**
     * Returns the description of the failure
     *
     * The beginning of failure messages is "Failed asserting that" in most
     * cases. This method should return the second part of that sentence.
     *
     * @param mixed $other Evaluated value or object.
     *
     * @return string
     */
    protected function failureDescription($other)
    {
        if ($this->expectedMessage === '') {
            return sprintf(
                "exception message is empty but is '%s'",
                $other->getMessage()
            );
        }

        return sprintf(
            "exception message '%s' contains '%s'",
            $other->getMessage(),
            $this->expectedMessage
        );
    }

    /**
     * @return string
     */
    public function toString()
    {
        if ($this->expectedMessage === '') {
            return 'exception message is empty';
        }

        return 'exception message contains ';
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Logical AND.
 */
class PHPUnit_Framework_Constraint_And extends PHPUnit_Framework_Constraint
{
    /**
     * @var PHPUnit_Framework_Constraint[]
     */
    protected $constraints = [];

    /**
     * @var PHPUnit_Framework_Constraint
     */
    protected $lastConstraint = null;

    /**
     * @param PHPUnit_Framework_Constraint[] $constraints
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function setConstraints(array $constraints)
    {
        $this->constraints = [];

        foreach ($constraints as $constraint) {
            if (!($constraint instanceof PHPUnit_Framework_Constraint)) {
                throw new PHPUnit_Framework_Exception(
                    'All parameters to ' . __CLASS__ .
                    ' must be a constraint object.'
                );
            }

            $this->constraints[] = $constraint;
        }
    }

    /**
     * Evaluates the constraint for parameter $other
     *
     * If $returnResult is set to false (the default), an exception is thrown
     * in case of a failure. null is returned otherwise.
     *
     * If $returnResult is true, the result of the evaluation is returned as
     * a boolean value instead: true in case of success, false in case of a
     * failure.
     *
     * @param mixed  $other        Value or object to evaluate.
     * @param string $description  Additional information about the test
     * @param bool   $returnResult Whether to return a result or throw an exception
     *
     * @return mixed
     *
     * @throws PHPUnit_Framework_ExpectationFailedException
     */
    public function evaluate($other, $description = '', $returnResult = false)
    {
        $success    = true;
        $constraint = null;

        foreach ($this->constraints as $constraint) {
            if (!$constraint->evaluate($other, $description, true)) {
                $success = false;
                break;
            }
        }

        if ($returnResult) {
            return $success;
        }

        if (!$success) {
            $this->fail($other, $description);
        }
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        $text = '';

        foreach ($this->constraints as $key => $constraint) {
            if ($key > 0) {
                $text .= ' and ';
            }

            $text .= $constraint->toString();
        }

        return $text;
    }

    /**
     * Counts the number of constraint elements.
     *
     * @return int
     */
    public function count()
    {
        $count = 0;

        foreach ($this->constraints as $constraint) {
            $count += count($constraint);
        }

        return $count;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that asserts that the Traversable it is applied to contains
 * only values of a given type.
 */
class PHPUnit_Framework_Constraint_TraversableContainsOnly extends PHPUnit_Framework_Constraint
{
    /**
     * @var PHPUnit_Framework_Constraint
     */
    protected $constraint;

    /**
     * @var string
     */
    protected $type;

    /**
     * @param string $type
     * @param bool   $isNativeType
     */
    public function __construct($type, $isNativeType = true)
    {
        parent::__construct();

        if ($isNativeType) {
            $this->constraint = new PHPUnit_Framework_Constraint_IsType($type);
        } else {
            $this->constraint = new PHPUnit_Framework_Constraint_IsInstanceOf(
                $type
            );
        }

        $this->type = $type;
    }

    /**
     * Evaluates the constraint for parameter $other
     *
     * If $returnResult is set to false (the default), an exception is thrown
     * in case of a failure. null is returned otherwise.
     *
     * If $returnResult is true, the result of the evaluation is returned as
     * a boolean value instead: true in case of success, false in case of a
     * failure.
     *
     * @param mixed  $other        Value or object to evaluate.
     * @param string $description  Additional information about the test
     * @param bool   $returnResult Whether to return a result or throw an exception
     *
     * @return mixed
     *
     * @throws PHPUnit_Framework_ExpectationFailedException
     */
    public function evaluate($other, $description = '', $returnResult = false)
    {
        $success = true;

        foreach ($other as $item) {
            if (!$this->constraint->evaluate($item, '', true)) {
                $success = false;
                break;
            }
        }

        if ($returnResult) {
            return $success;
        }

        if (!$success) {
            $this->fail($other, $description);
        }
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        return 'contains only values of type "' . $this->type . '"';
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that asserts that the class it is evaluated for has a given
 * static attribute.
 *
 * The attribute name is passed in the constructor.
 */
class PHPUnit_Framework_Constraint_ClassHasStaticAttribute extends PHPUnit_Framework_Constraint_ClassHasAttribute
{
    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        $class = new ReflectionClass($other);

        if ($class->hasProperty($this->attributeName)) {
            $attribute = $class->getProperty($this->attributeName);

            return $attribute->isStatic();
        } else {
            return false;
        }
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        return sprintf(
            'has static attribute "%s"',
            $this->attributeName
        );
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that accepts true.
 */
class PHPUnit_Framework_Constraint_IsTrue extends PHPUnit_Framework_Constraint
{
    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        return $other === true;
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        return 'is true';
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that asserts that the array it is evaluated for has a given key.
 *
 * Uses array_key_exists() to check if the key is found in the input array, if
 * not found the evaluation fails.
 *
 * The array key is passed in the constructor.
 */
class PHPUnit_Framework_Constraint_ArrayHasKey extends PHPUnit_Framework_Constraint
{
    /**
     * @var int|string
     */
    protected $key;

    /**
     * @param int|string $key
     */
    public function __construct($key)
    {
        parent::__construct();
        $this->key = $key;
    }

    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        if (is_array($other)) {
            return array_key_exists($this->key, $other);
        }

        if ($other instanceof ArrayAccess) {
            return $other->offsetExists($this->key);
        }

        return false;
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        return 'has the key ' . $this->exporter->export($this->key);
    }

    /**
     * Returns the description of the failure
     *
     * The beginning of failure messages is "Failed asserting that" in most
     * cases. This method should return the second part of that sentence.
     *
     * @param mixed $other Evaluated value or object.
     *
     * @return string
     */
    protected function failureDescription($other)
    {
        return 'an array ' . $this->toString();
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

abstract class PHPUnit_Framework_Constraint_Composite extends PHPUnit_Framework_Constraint
{
    /**
     * @var PHPUnit_Framework_Constraint
     */
    protected $innerConstraint;

    /**
     * @param PHPUnit_Framework_Constraint $innerConstraint
     */
    public function __construct(PHPUnit_Framework_Constraint $innerConstraint)
    {
        parent::__construct();
        $this->innerConstraint = $innerConstraint;
    }

    /**
     * Evaluates the constraint for parameter $other
     *
     * If $returnResult is set to false (the default), an exception is thrown
     * in case of a failure. null is returned otherwise.
     *
     * If $returnResult is true, the result of the evaluation is returned as
     * a boolean value instead: true in case of success, false in case of a
     * failure.
     *
     * @param mixed  $other        Value or object to evaluate.
     * @param string $description  Additional information about the test
     * @param bool   $returnResult Whether to return a result or throw an exception
     *
     * @return mixed
     *
     * @throws PHPUnit_Framework_ExpectationFailedException
     */
    public function evaluate($other, $description = '', $returnResult = false)
    {
        try {
            return $this->innerConstraint->evaluate(
                $other,
                $description,
                $returnResult
            );
        } catch (PHPUnit_Framework_ExpectationFailedException $e) {
            $this->fail($other, $description);
        }
    }

    /**
     * Counts the number of constraint elements.
     *
     * @return int
     */
    public function count()
    {
        return count($this->innerConstraint);
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that asserts that the string it is evaluated for begins with a
 * given prefix.
 */
class PHPUnit_Framework_Constraint_StringStartsWith extends PHPUnit_Framework_Constraint
{
    /**
     * @var string
     */
    protected $prefix;

    /**
     * @param string $prefix
     */
    public function __construct($prefix)
    {
        parent::__construct();
        $this->prefix = $prefix;
    }

    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        return strpos($other, $this->prefix) === 0;
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        return 'starts with "' . $this->prefix . '"';
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Provides human readable messages for each JSON error.
 */
class PHPUnit_Framework_Constraint_JsonMatches_ErrorMessageProvider
{
    /**
     * Translates JSON error to a human readable string.
     *
     * @param string $error
     * @param string $prefix
     *
     * @return string
     */
    public static function determineJsonError($error, $prefix = '')
    {
        switch ($error) {
            case JSON_ERROR_NONE:
                return;
            case JSON_ERROR_DEPTH:
                return $prefix . 'Maximum stack depth exceeded';
            case JSON_ERROR_STATE_MISMATCH:
                return $prefix . 'Underflow or the modes mismatch';
            case JSON_ERROR_CTRL_CHAR:
                return $prefix . 'Unexpected control character found';
            case JSON_ERROR_SYNTAX:
                return $prefix . 'Syntax error, malformed JSON';
            case JSON_ERROR_UTF8:
                return $prefix . 'Malformed UTF-8 characters, possibly incorrectly encoded';
            default:
                return $prefix . 'Unknown error';
        }
    }

    /**
     * Translates a given type to a human readable message prefix.
     *
     * @param string $type
     *
     * @return string
     */
    public static function translateTypeToPrefix($type)
    {
        switch (strtolower($type)) {
            case 'expected':
                $prefix = 'Expected value JSON decode error - ';
                break;
            case 'actual':
                $prefix = 'Actual value JSON decode error - ';
                break;
            default:
                $prefix = '';
                break;
        }

        return $prefix;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that asserts that a string is valid JSON.
 */
class PHPUnit_Framework_Constraint_IsJson extends PHPUnit_Framework_Constraint
{
    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        if ($other === '') {
            return false;
        }

        json_decode($other);
        if (json_last_error()) {
            return false;
        }

        return true;
    }

    /**
     * Returns the description of the failure
     *
     * The beginning of failure messages is "Failed asserting that" in most
     * cases. This method should return the second part of that sentence.
     *
     * @param mixed $other Evaluated value or object.
     *
     * @return string
     */
    protected function failureDescription($other)
    {
        if ($other === '') {
            return 'an empty string is valid JSON';
        }

        json_decode($other);
        $error = PHPUnit_Framework_Constraint_JsonMatches_ErrorMessageProvider::determineJsonError(
            json_last_error()
        );

        return sprintf(
            '%s is valid JSON (%s)',
            $this->exporter->shortenedExport($other),
            $error
        );
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        return 'is valid JSON';
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Asserts whether or not two JSON objects are equal.
 */
class PHPUnit_Framework_Constraint_JsonMatches extends PHPUnit_Framework_Constraint
{
    /**
     * @var string
     */
    protected $value;

    /**
     * Creates a new constraint.
     *
     * @param string $value
     */
    public function __construct($value)
    {
        parent::__construct();
        $this->value = $value;
    }

    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * This method can be overridden to implement the evaluation algorithm.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        $decodedOther = json_decode($other);
        if (json_last_error()) {
            return false;
        }

        $decodedValue = json_decode($this->value);
        if (json_last_error()) {
            return false;
        }

        return $decodedOther == $decodedValue;
    }

    /**
     * Returns a string representation of the object.
     *
     * @return string
     */
    public function toString()
    {
        return sprintf(
            'matches JSON string "%s"',
            $this->value
        );
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

class PHPUnit_Framework_Constraint_Attribute extends PHPUnit_Framework_Constraint_Composite
{
    /**
     * @var string
     */
    protected $attributeName;

    /**
     * @param PHPUnit_Framework_Constraint $constraint
     * @param string                       $attributeName
     */
    public function __construct(PHPUnit_Framework_Constraint $constraint, $attributeName)
    {
        parent::__construct($constraint);

        $this->attributeName = $attributeName;
    }

    /**
     * Evaluates the constraint for parameter $other
     *
     * If $returnResult is set to false (the default), an exception is thrown
     * in case of a failure. null is returned otherwise.
     *
     * If $returnResult is true, the result of the evaluation is returned as
     * a boolean value instead: true in case of success, false in case of a
     * failure.
     *
     * @param mixed  $other        Value or object to evaluate.
     * @param string $description  Additional information about the test
     * @param bool   $returnResult Whether to return a result or throw an exception
     *
     * @return mixed
     *
     * @throws PHPUnit_Framework_ExpectationFailedException
     */
    public function evaluate($other, $description = '', $returnResult = false)
    {
        return parent::evaluate(
            PHPUnit_Framework_Assert::readAttribute(
                $other,
                $this->attributeName
            ),
            $description,
            $returnResult
        );
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        return 'attribute "' . $this->attributeName . '" ' .
               $this->innerConstraint->toString();
    }

    /**
     * Returns the description of the failure
     *
     * The beginning of failure messages is "Failed asserting that" in most
     * cases. This method should return the second part of that sentence.
     *
     * @param mixed $other Evaluated value or object.
     *
     * @return string
     */
    protected function failureDescription($other)
    {
        return $this->toString();
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use SebastianBergmann\Diff\Differ;

/**
 * ...
 */
class PHPUnit_Framework_Constraint_StringMatches extends PHPUnit_Framework_Constraint_PCREMatch
{
    /**
     * @var string
     */
    protected $string;

    /**
     * @param string $string
     */
    public function __construct($string)
    {
        parent::__construct($string);

        $this->pattern = $this->createPatternFromFormat(
            preg_replace('/\r\n/', "\n", $string)
        );

        $this->string = $string;
    }

    protected function failureDescription($other)
    {
        return 'format description matches text';
    }

    protected function additionalFailureDescription($other)
    {
        $from = preg_split('(\r\n|\r|\n)', $this->string);
        $to   = preg_split('(\r\n|\r|\n)', $other);

        foreach ($from as $index => $line) {
            if (isset($to[$index]) && $line !== $to[$index]) {
                $line = $this->createPatternFromFormat($line);

                if (preg_match($line, $to[$index]) > 0) {
                    $from[$index] = $to[$index];
                }
            }
        }

        $this->string = implode("\n", $from);
        $other        = implode("\n", $to);

        $differ = new Differ("--- Expected\n+++ Actual\n");

        return $differ->diff($this->string, $other);
    }

    protected function createPatternFromFormat($string)
    {
        $string = str_replace(
            [
            '%e',
            '%s',
            '%S',
            '%a',
            '%A',
            '%w',
            '%i',
            '%d',
            '%x',
            '%f',
            '%c'
            ],
            [
            '\\' . DIRECTORY_SEPARATOR,
            '[^\r\n]+',
            '[^\r\n]*',
            '.+',
            '.*',
            '\s*',
            '[+-]?\d+',
            '\d+',
            '[0-9a-fA-F]+',
            '[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?',
            '.'
            ],
            preg_quote($string, '/')
        );

        return '/^' . $string . '$/s';
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Logical XOR.
 */
class PHPUnit_Framework_Constraint_Xor extends PHPUnit_Framework_Constraint
{
    /**
     * @var PHPUnit_Framework_Constraint[]
     */
    protected $constraints = [];

    /**
     * @param PHPUnit_Framework_Constraint[] $constraints
     */
    public function setConstraints(array $constraints)
    {
        $this->constraints = [];

        foreach ($constraints as $constraint) {
            if (!($constraint instanceof PHPUnit_Framework_Constraint)) {
                $constraint = new PHPUnit_Framework_Constraint_IsEqual(
                    $constraint
                );
            }

            $this->constraints[] = $constraint;
        }
    }

    /**
     * Evaluates the constraint for parameter $other
     *
     * If $returnResult is set to false (the default), an exception is thrown
     * in case of a failure. null is returned otherwise.
     *
     * If $returnResult is true, the result of the evaluation is returned as
     * a boolean value instead: true in case of success, false in case of a
     * failure.
     *
     * @param mixed  $other        Value or object to evaluate.
     * @param string $description  Additional information about the test
     * @param bool   $returnResult Whether to return a result or throw an exception
     *
     * @return mixed
     *
     * @throws PHPUnit_Framework_ExpectationFailedException
     */
    public function evaluate($other, $description = '', $returnResult = false)
    {
        $success    = true;
        $lastResult = null;
        $constraint = null;

        foreach ($this->constraints as $constraint) {
            $result = $constraint->evaluate($other, $description, true);

            if ($result === $lastResult) {
                $success = false;
                break;
            }

            $lastResult = $result;
        }

        if ($returnResult) {
            return $success;
        }

        if (!$success) {
            $this->fail($other, $description);
        }
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        $text = '';

        foreach ($this->constraints as $key => $constraint) {
            if ($key > 0) {
                $text .= ' xor ';
            }

            $text .= $constraint->toString();
        }

        return $text;
    }

    /**
     * Counts the number of constraint elements.
     *
     * @return int
     */
    public function count()
    {
        $count = 0;

        foreach ($this->constraints as $constraint) {
            $count += count($constraint);
        }

        return $count;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that asserts that the value it is evaluated for is of a
 * specified type.
 *
 * The expected value is passed in the constructor.
 */
class PHPUnit_Framework_Constraint_IsType extends PHPUnit_Framework_Constraint
{
    const TYPE_ARRAY    = 'array';
    const TYPE_BOOL     = 'bool';
    const TYPE_FLOAT    = 'float';
    const TYPE_INT      = 'int';
    const TYPE_NULL     = 'null';
    const TYPE_NUMERIC  = 'numeric';
    const TYPE_OBJECT   = 'object';
    const TYPE_RESOURCE = 'resource';
    const TYPE_STRING   = 'string';
    const TYPE_SCALAR   = 'scalar';
    const TYPE_CALLABLE = 'callable';

    /**
     * @var array
     */
    protected $types = [
        'array'    => true,
        'boolean'  => true,
        'bool'     => true,
        'double'   => true,
        'float'    => true,
        'integer'  => true,
        'int'      => true,
        'null'     => true,
        'numeric'  => true,
        'object'   => true,
        'real'     => true,
        'resource' => true,
        'string'   => true,
        'scalar'   => true,
        'callable' => true
    ];

    /**
     * @var string
     */
    protected $type;

    /**
     * @param string $type
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function __construct($type)
    {
        parent::__construct();

        if (!isset($this->types[$type])) {
            throw new PHPUnit_Framework_Exception(
                sprintf(
                    'Type specified for PHPUnit_Framework_Constraint_IsType <%s> ' .
                    'is not a valid type.',
                    $type
                )
            );
        }

        $this->type = $type;
    }

    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        switch ($this->type) {
            case 'numeric':
                return is_numeric($other);

            case 'integer':
            case 'int':
                return is_int($other);

            case 'double':
            case 'float':
            case 'real':
                return is_float($other);

            case 'string':
                return is_string($other);

            case 'boolean':
            case 'bool':
                return is_bool($other);

            case 'null':
                return is_null($other);

            case 'array':
                return is_array($other);

            case 'object':
                return is_object($other);

            case 'resource':
                return is_resource($other) || is_string(@get_resource_type($other));

            case 'scalar':
                return is_scalar($other);

            case 'callable':
                return is_callable($other);
        }
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        return sprintf(
            'is of type "%s"',
            $this->type
        );
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that checks whether a variable is empty().
 */
class PHPUnit_Framework_Constraint_IsEmpty extends PHPUnit_Framework_Constraint
{
    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        if ($other instanceof Countable) {
            return count($other) === 0;
        }

        return empty($other);
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        return 'is empty';
    }

    /**
     * Returns the description of the failure
     *
     * The beginning of failure messages is "Failed asserting that" in most
     * cases. This method should return the second part of that sentence.
     *
     * @param mixed $other Evaluated value or object.
     *
     * @return string
     */
    protected function failureDescription($other)
    {
        $type = gettype($other);

        return sprintf(
            '%s %s %s',
            $type[0] == 'a' || $type[0] == 'o' ? 'an' : 'a',
            $type,
            $this->toString()
        );
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that asserts that the Traversable it is applied to contains
 * a given value.
 */
class PHPUnit_Framework_Constraint_TraversableContains extends PHPUnit_Framework_Constraint
{
    /**
     * @var bool
     */
    protected $checkForObjectIdentity;

    /**
     * @var bool
     */
    protected $checkForNonObjectIdentity;

    /**
     * @var mixed
     */
    protected $value;

    /**
     * @param mixed $value
     * @param bool  $checkForObjectIdentity
     * @param bool  $checkForNonObjectIdentity
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function __construct($value, $checkForObjectIdentity = true, $checkForNonObjectIdentity = false)
    {
        parent::__construct();

        if (!is_bool($checkForObjectIdentity)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'boolean');
        }

        if (!is_bool($checkForNonObjectIdentity)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(3, 'boolean');
        }

        $this->checkForObjectIdentity    = $checkForObjectIdentity;
        $this->checkForNonObjectIdentity = $checkForNonObjectIdentity;
        $this->value                     = $value;
    }

    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        if ($other instanceof SplObjectStorage) {
            return $other->contains($this->value);
        }

        if (is_object($this->value)) {
            foreach ($other as $element) {
                if ($this->checkForObjectIdentity && $element === $this->value) {
                    return true;
                } elseif (!$this->checkForObjectIdentity && $element == $this->value) {
                    return true;
                }
            }
        } else {
            foreach ($other as $element) {
                if ($this->checkForNonObjectIdentity && $element === $this->value) {
                    return true;
                } elseif (!$this->checkForNonObjectIdentity && $element == $this->value) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        if (is_string($this->value) && strpos($this->value, "\n") !== false) {
            return 'contains "' . $this->value . '"';
        } else {
            return 'contains ' . $this->exporter->export($this->value);
        }
    }

    /**
     * Returns the description of the failure
     *
     * The beginning of failure messages is "Failed asserting that" in most
     * cases. This method should return the second part of that sentence.
     *
     * @param mixed $other Evaluated value or object.
     *
     * @return string
     */
    protected function failureDescription($other)
    {
        return sprintf(
            '%s %s',
            is_array($other) ? 'an array' : 'a traversable',
            $this->toString()
        );
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that checks if one value is equal to another.
 *
 * Equality is checked with PHP's == operator, the operator is explained in
 * detail at {@url http://www.php.net/manual/en/types.comparisons.php}.
 * Two values are equal if they have the same value disregarding type.
 *
 * The expected value is passed in the constructor.
 */
class PHPUnit_Framework_Constraint_IsEqual extends PHPUnit_Framework_Constraint
{
    /**
     * @var mixed
     */
    protected $value;

    /**
     * @var float
     */
    protected $delta = 0.0;

    /**
     * @var int
     */
    protected $maxDepth = 10;

    /**
     * @var bool
     */
    protected $canonicalize = false;

    /**
     * @var bool
     */
    protected $ignoreCase = false;

    /**
     * @var SebastianBergmann\Comparator\ComparisonFailure
     */
    protected $lastFailure;

    /**
     * @param mixed $value
     * @param float $delta
     * @param int   $maxDepth
     * @param bool  $canonicalize
     * @param bool  $ignoreCase
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function __construct($value, $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false)
    {
        parent::__construct();

        if (!is_numeric($delta)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'numeric');
        }

        if (!is_int($maxDepth)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(3, 'integer');
        }

        if (!is_bool($canonicalize)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(4, 'boolean');
        }

        if (!is_bool($ignoreCase)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(5, 'boolean');
        }

        $this->value        = $value;
        $this->delta        = $delta;
        $this->maxDepth     = $maxDepth;
        $this->canonicalize = $canonicalize;
        $this->ignoreCase   = $ignoreCase;
    }

    /**
     * Evaluates the constraint for parameter $other
     *
     * If $returnResult is set to false (the default), an exception is thrown
     * in case of a failure. null is returned otherwise.
     *
     * If $returnResult is true, the result of the evaluation is returned as
     * a boolean value instead: true in case of success, false in case of a
     * failure.
     *
     * @param mixed  $other        Value or object to evaluate.
     * @param string $description  Additional information about the test
     * @param bool   $returnResult Whether to return a result or throw an exception
     *
     * @return mixed
     *
     * @throws PHPUnit_Framework_ExpectationFailedException
     */
    public function evaluate($other, $description = '', $returnResult = false)
    {
        // If $this->value and $other are identical, they are also equal.
        // This is the most common path and will allow us to skip
        // initialization of all the comparators.
        if ($this->value === $other) {
            return true;
        }

        $comparatorFactory = SebastianBergmann\Comparator\Factory::getInstance();

        try {
            $comparator = $comparatorFactory->getComparatorFor(
                $this->value,
                $other
            );

            $comparator->assertEquals(
                $this->value,
                $other,
                $this->delta,
                $this->canonicalize,
                $this->ignoreCase
            );
        } catch (SebastianBergmann\Comparator\ComparisonFailure $f) {
            if ($returnResult) {
                return false;
            }

            throw new PHPUnit_Framework_ExpectationFailedException(
                trim($description . "\n" . $f->getMessage()),
                $f
            );
        }

        return true;
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        $delta = '';

        if (is_string($this->value)) {
            if (strpos($this->value, "\n") !== false) {
                return 'is equal to <text>';
            } else {
                return sprintf(
                    'is equal to <string:%s>',
                    $this->value
                );
            }
        } else {
            if ($this->delta != 0) {
                $delta = sprintf(
                    ' with delta <%F>',
                    $this->delta
                );
            }

            return sprintf(
                'is equal to %s%s',
                $this->exporter->export($this->value),
                $delta
            );
        }
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that accepts nan.
 */
class PHPUnit_Framework_Constraint_IsNan extends PHPUnit_Framework_Constraint
{
    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        return is_nan($other);
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        return 'is nan';
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that asserts that the class it is evaluated for has a given
 * attribute.
 *
 * The attribute name is passed in the constructor.
 */
class PHPUnit_Framework_Constraint_ClassHasAttribute extends PHPUnit_Framework_Constraint
{
    /**
     * @var string
     */
    protected $attributeName;

    /**
     * @param string $attributeName
     */
    public function __construct($attributeName)
    {
        parent::__construct();
        $this->attributeName = $attributeName;
    }

    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        $class = new ReflectionClass($other);

        return $class->hasProperty($this->attributeName);
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        return sprintf(
            'has attribute "%s"',
            $this->attributeName
        );
    }

    /**
     * Returns the description of the failure
     *
     * The beginning of failure messages is "Failed asserting that" in most
     * cases. This method should return the second part of that sentence.
     *
     * @param mixed $other Evaluated value or object.
     *
     * @return string
     */
    protected function failureDescription($other)
    {
        return sprintf(
            '%sclass "%s" %s',
            is_object($other) ? 'object of ' : '',
            is_object($other) ? get_class($other) : $other,
            $this->toString()
        );
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

class PHPUnit_Framework_Constraint_Count extends PHPUnit_Framework_Constraint
{
    /**
     * @var int
     */
    protected $expectedCount = 0;

    /**
     * @param int $expected
     */
    public function __construct($expected)
    {
        parent::__construct();
        $this->expectedCount = $expected;
    }

    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param mixed $other
     *
     * @return bool
     */
    protected function matches($other)
    {
        return $this->expectedCount === $this->getCountOf($other);
    }

    /**
     * @param mixed $other
     *
     * @return bool
     */
    protected function getCountOf($other)
    {
        if ($other instanceof Countable || is_array($other)) {
            return count($other);
        } elseif ($other instanceof Traversable) {
            if ($other instanceof IteratorAggregate) {
                $iterator = $other->getIterator();
            } else {
                $iterator = $other;
            }

            if ($iterator instanceof Generator) {
                return $this->getCountOfGenerator($iterator);
            }

            $key   = $iterator->key();
            $count = iterator_count($iterator);

            // Manually rewind $iterator to previous key, since iterator_count
            // moves pointer.
            if ($key !== null) {
                $iterator->rewind();
                while ($iterator->valid() && $key !== $iterator->key()) {
                    $iterator->next();
                }
            }

            return $count;
        }
    }

    /**
     * Returns the total number of iterations from a generator.
     * This will fully exhaust the generator.
     *
     * @param Generator $generator
     *
     * @return int
     */
    protected function getCountOfGenerator(Generator $generator)
    {
        for ($count = 0; $generator->valid(); $generator->next()) {
            $count += 1;
        }

        return $count;
    }

    /**
     * Returns the description of the failure.
     *
     * The beginning of failure messages is "Failed asserting that" in most
     * cases. This method should return the second part of that sentence.
     *
     * @param mixed $other Evaluated value or object.
     *
     * @return string
     */
    protected function failureDescription($other)
    {
        return sprintf(
            'actual size %d matches expected size %d',
            $this->getCountOf($other),
            $this->expectedCount
        );
    }

    /**
     * @return string
     */
    public function toString()
    {
        return sprintf(
            'count matches %d',
            $this->expectedCount
        );
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that asserts that the object it is evaluated for has a given
 * attribute.
 *
 * The attribute name is passed in the constructor.
 */
class PHPUnit_Framework_Constraint_ObjectHasAttribute extends PHPUnit_Framework_Constraint_ClassHasAttribute
{
    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        $object = new ReflectionObject($other);

        return $object->hasProperty($this->attributeName);
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that asserts that the string it is evaluated for matches
 * a regular expression.
 *
 * Checks a given value using the Perl Compatible Regular Expression extension
 * in PHP. The pattern is matched by executing preg_match().
 *
 * The pattern string passed in the constructor.
 */
class PHPUnit_Framework_Constraint_PCREMatch extends PHPUnit_Framework_Constraint
{
    /**
     * @var string
     */
    protected $pattern;

    /**
     * @param string $pattern
     */
    public function __construct($pattern)
    {
        parent::__construct();
        $this->pattern = $pattern;
    }

    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        return preg_match($this->pattern, $other) > 0;
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        return sprintf(
            'matches PCRE pattern "%s"',
            $this->pattern
        );
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that accepts infinite.
 */
class PHPUnit_Framework_Constraint_IsInfinite extends PHPUnit_Framework_Constraint
{
    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        return is_infinite($other);
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        return 'is infinite';
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

class PHPUnit_Framework_Constraint_ExceptionMessageRegExp extends PHPUnit_Framework_Constraint
{
    /**
     * @var int
     */
    protected $expectedMessageRegExp;

    /**
     * @param string $expected
     */
    public function __construct($expected)
    {
        parent::__construct();
        $this->expectedMessageRegExp = $expected;
    }

    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param Exception $other
     *
     * @return bool
     */
    protected function matches($other)
    {
        $match = PHPUnit_Util_Regex::pregMatchSafe($this->expectedMessageRegExp, $other->getMessage());

        if (false === $match) {
            throw new PHPUnit_Framework_Exception(
                "Invalid expected exception message regex given: '{$this->expectedMessageRegExp}'"
            );
        }

        return 1 === $match;
    }

    /**
     * Returns the description of the failure
     *
     * The beginning of failure messages is "Failed asserting that" in most
     * cases. This method should return the second part of that sentence.
     *
     * @param mixed $other Evaluated value or object.
     *
     * @return string
     */
    protected function failureDescription($other)
    {
        return sprintf(
            "exception message '%s' matches '%s'",
            $other->getMessage(),
            $this->expectedMessageRegExp
        );
    }

    /**
     * @return string
     */
    public function toString()
    {
        return 'exception message matches ';
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that asserts that the string it is evaluated for contains
 * a given string.
 *
 * Uses mb_strpos() to find the position of the string in the input, if not
 * found the evaluation fails.
 *
 * The sub-string is passed in the constructor.
 */
class PHPUnit_Framework_Constraint_StringContains extends PHPUnit_Framework_Constraint
{
    /**
     * @var string
     */
    protected $string;

    /**
     * @var bool
     */
    protected $ignoreCase;

    /**
     * @param string $string
     * @param bool   $ignoreCase
     */
    public function __construct($string, $ignoreCase = false)
    {
        parent::__construct();

        $this->string     = $string;
        $this->ignoreCase = $ignoreCase;
    }

    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        if ($this->ignoreCase) {
            return mb_stripos($other, $this->string) !== false;
        } else {
            return mb_strpos($other, $this->string) !== false;
        }
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        if ($this->ignoreCase) {
            $string = mb_strtolower($this->string);
        } else {
            $string = $this->string;
        }

        return sprintf(
            'contains "%s"',
            $string
        );
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that asserts that one value is identical to another.
 *
 * Identical check is performed with PHP's === operator, the operator is
 * explained in detail at
 * {@url http://www.php.net/manual/en/types.comparisons.php}.
 * Two values are identical if they have the same value and are of the same
 * type.
 *
 * The expected value is passed in the constructor.
 */
class PHPUnit_Framework_Constraint_IsIdentical extends PHPUnit_Framework_Constraint
{
    /**
     * @var float
     */
    const EPSILON = 0.0000000001;

    /**
     * @var mixed
     */
    protected $value;

    /**
     * @param mixed $value
     */
    public function __construct($value)
    {
        parent::__construct();
        $this->value = $value;
    }

    /**
     * Evaluates the constraint for parameter $other
     *
     * If $returnResult is set to false (the default), an exception is thrown
     * in case of a failure. null is returned otherwise.
     *
     * If $returnResult is true, the result of the evaluation is returned as
     * a boolean value instead: true in case of success, false in case of a
     * failure.
     *
     * @param mixed  $other        Value or object to evaluate.
     * @param string $description  Additional information about the test
     * @param bool   $returnResult Whether to return a result or throw an exception
     *
     * @return mixed
     *
     * @throws PHPUnit_Framework_ExpectationFailedException
     */
    public function evaluate($other, $description = '', $returnResult = false)
    {
        if (is_float($this->value) && is_float($other) &&
            !is_infinite($this->value) && !is_infinite($other) &&
            !is_nan($this->value) && !is_nan($other)) {
            $success = abs($this->value - $other) < self::EPSILON;
        } else {
            $success = $this->value === $other;
        }

        if ($returnResult) {
            return $success;
        }

        if (!$success) {
            $f = null;

            // if both values are strings, make sure a diff is generated
            if (is_string($this->value) && is_string($other)) {
                $f = new SebastianBergmann\Comparator\ComparisonFailure(
                    $this->value,
                    $other,
                    $this->value,
                    $other
                );
            }

            $this->fail($other, $description, $f);
        }
    }

    /**
     * Returns the description of the failure
     *
     * The beginning of failure messages is "Failed asserting that" in most
     * cases. This method should return the second part of that sentence.
     *
     * @param mixed $other Evaluated value or object.
     *
     * @return string
     */
    protected function failureDescription($other)
    {
        if (is_object($this->value) && is_object($other)) {
            return 'two variables reference the same object';
        }

        if (is_string($this->value) && is_string($other)) {
            return 'two strings are identical';
        }

        return parent::failureDescription($other);
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        if (is_object($this->value)) {
            return 'is identical to an object of class "' .
                   get_class($this->value) . '"';
        } else {
            return 'is identical to ' .
                   $this->exporter->export($this->value);
        }
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that accepts null.
 */
class PHPUnit_Framework_Constraint_IsNull extends PHPUnit_Framework_Constraint
{
    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        return $other === null;
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        return 'is null';
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Logical OR.
 */
class PHPUnit_Framework_Constraint_Or extends PHPUnit_Framework_Constraint
{
    /**
     * @var PHPUnit_Framework_Constraint[]
     */
    protected $constraints = [];

    /**
     * @param PHPUnit_Framework_Constraint[] $constraints
     */
    public function setConstraints(array $constraints)
    {
        $this->constraints = [];

        foreach ($constraints as $constraint) {
            if (!($constraint instanceof PHPUnit_Framework_Constraint)) {
                $constraint = new PHPUnit_Framework_Constraint_IsEqual(
                    $constraint
                );
            }

            $this->constraints[] = $constraint;
        }
    }

    /**
     * Evaluates the constraint for parameter $other
     *
     * If $returnResult is set to false (the default), an exception is thrown
     * in case of a failure. null is returned otherwise.
     *
     * If $returnResult is true, the result of the evaluation is returned as
     * a boolean value instead: true in case of success, false in case of a
     * failure.
     *
     * @param mixed  $other        Value or object to evaluate.
     * @param string $description  Additional information about the test
     * @param bool   $returnResult Whether to return a result or throw an exception
     *
     * @return mixed
     *
     * @throws PHPUnit_Framework_ExpectationFailedException
     */
    public function evaluate($other, $description = '', $returnResult = false)
    {
        $success    = false;
        $constraint = null;

        foreach ($this->constraints as $constraint) {
            if ($constraint->evaluate($other, $description, true)) {
                $success = true;
                break;
            }
        }

        if ($returnResult) {
            return $success;
        }

        if (!$success) {
            $this->fail($other, $description);
        }
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        $text = '';

        foreach ($this->constraints as $key => $constraint) {
            if ($key > 0) {
                $text .= ' or ';
            }

            $text .= $constraint->toString();
        }

        return $text;
    }

    /**
     * Counts the number of constraint elements.
     *
     * @return int
     */
    public function count()
    {
        $count = 0;

        foreach ($this->constraints as $constraint) {
            $count += count($constraint);
        }

        return $count;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that asserts that the array it is evaluated for has a specified subset.
 *
 * Uses array_replace_recursive() to check if a key value subset is part of the
 * subject array.
 */
class PHPUnit_Framework_Constraint_ArraySubset extends PHPUnit_Framework_Constraint
{
    /**
     * @var array|Traversable
     */
    protected $subset;

    /**
     * @var bool
     */
    protected $strict;

    /**
     * @param array|Traversable $subset
     * @param bool              $strict Check for object identity
     */
    public function __construct($subset, $strict = false)
    {
        parent::__construct();
        $this->strict = $strict;
        $this->subset = $subset;
    }

    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param array|Traversable $other Array or Traversable object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        //type cast $other & $this->subset as an array to allow
        //support in standard array functions.
        $other        = $this->toArray($other);
        $this->subset = $this->toArray($this->subset);

        $patched = array_replace_recursive($other, $this->subset);

        if ($this->strict) {
            return $other === $patched;
        } else {
            return $other == $patched;
        }
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        return 'has the subset ' . $this->exporter->export($this->subset);
    }

    /**
     * Returns the description of the failure
     *
     * The beginning of failure messages is "Failed asserting that" in most
     * cases. This method should return the second part of that sentence.
     *
     * @param mixed $other Evaluated value or object.
     *
     * @return string
     */
    protected function failureDescription($other)
    {
        return 'an array ' . $this->toString();
    }

    /**
     * @param array|Traversable $other
     *
     * @return array
     */
    private function toArray($other)
    {
        if (is_array($other)) {
            return $other;
        } elseif ($other instanceof ArrayObject) {
            return $other->getArrayCopy();
        } elseif ($other instanceof Traversable) {
            return iterator_to_array($other);
        }

        // Keep BC even if we know that array would not be the expected one
        return (array) $other;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

class PHPUnit_Framework_Constraint_SameSize extends PHPUnit_Framework_Constraint_Count
{
    /**
     * @var int
     */
    protected $expectedCount;

    /**
     * @param int $expected
     */
    public function __construct($expected)
    {
        parent::__construct($this->getCountOf($expected));
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Logical NOT.
 */
class PHPUnit_Framework_Constraint_Not extends PHPUnit_Framework_Constraint
{
    /**
     * @var PHPUnit_Framework_Constraint
     */
    protected $constraint;

    /**
     * @param PHPUnit_Framework_Constraint $constraint
     */
    public function __construct($constraint)
    {
        parent::__construct();

        if (!($constraint instanceof PHPUnit_Framework_Constraint)) {
            $constraint = new PHPUnit_Framework_Constraint_IsEqual($constraint);
        }

        $this->constraint = $constraint;
    }

    /**
     * @param string $string
     *
     * @return string
     */
    public static function negate($string)
    {
        return str_replace(
            [
            'contains ',
            'exists',
            'has ',
            'is ',
            'are ',
            'matches ',
            'starts with ',
            'ends with ',
            'reference ',
            'not not '
            ],
            [
            'does not contain ',
            'does not exist',
            'does not have ',
            'is not ',
            'are not ',
            'does not match ',
            'starts not with ',
            'ends not with ',
            'don\'t reference ',
            'not '
            ],
            $string
        );
    }

    /**
     * Evaluates the constraint for parameter $other
     *
     * If $returnResult is set to false (the default), an exception is thrown
     * in case of a failure. null is returned otherwise.
     *
     * If $returnResult is true, the result of the evaluation is returned as
     * a boolean value instead: true in case of success, false in case of a
     * failure.
     *
     * @param mixed  $other        Value or object to evaluate.
     * @param string $description  Additional information about the test
     * @param bool   $returnResult Whether to return a result or throw an exception
     *
     * @return mixed
     *
     * @throws PHPUnit_Framework_ExpectationFailedException
     */
    public function evaluate($other, $description = '', $returnResult = false)
    {
        $success = !$this->constraint->evaluate($other, $description, true);

        if ($returnResult) {
            return $success;
        }

        if (!$success) {
            $this->fail($other, $description);
        }
    }

    /**
     * Returns the description of the failure
     *
     * The beginning of failure messages is "Failed asserting that" in most
     * cases. This method should return the second part of that sentence.
     *
     * @param mixed $other Evaluated value or object.
     *
     * @return string
     */
    protected function failureDescription($other)
    {
        switch (get_class($this->constraint)) {
            case 'PHPUnit_Framework_Constraint_And':
            case 'PHPUnit_Framework_Constraint_Not':
            case 'PHPUnit_Framework_Constraint_Or':
                return 'not( ' . $this->constraint->failureDescription($other) . ' )';

            default:
                return self::negate(
                    $this->constraint->failureDescription($other)
                );
        }
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        switch (get_class($this->constraint)) {
            case 'PHPUnit_Framework_Constraint_And':
            case 'PHPUnit_Framework_Constraint_Not':
            case 'PHPUnit_Framework_Constraint_Or':
                return 'not( ' . $this->constraint->toString() . ' )';

            default:
                return self::negate(
                    $this->constraint->toString()
                );
        }
    }

    /**
     * Counts the number of constraint elements.
     *
     * @return int
     */
    public function count()
    {
        return count($this->constraint);
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

class PHPUnit_Framework_Constraint_Exception extends PHPUnit_Framework_Constraint
{
    /**
     * @var string
     */
    protected $className;

    /**
     * @param string $className
     */
    public function __construct($className)
    {
        parent::__construct();
        $this->className = $className;
    }

    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        return $other instanceof $this->className;
    }

    /**
     * Returns the description of the failure
     *
     * The beginning of failure messages is "Failed asserting that" in most
     * cases. This method should return the second part of that sentence.
     *
     * @param mixed $other Evaluated value or object.
     *
     * @return string
     */
    protected function failureDescription($other)
    {
        if ($other !== null) {
            $message = '';
            if ($other instanceof Exception || $other instanceof Throwable) {
                $message = '. Message was: "' . $other->getMessage() . '" at'
                        . "\n" . PHPUnit_Util_Filter::getFilteredStacktrace($other);
            }

            return sprintf(
                'exception of type "%s" matches expected exception "%s"%s',
                get_class($other),
                $this->className,
                $message
            );
        }

        return sprintf(
            'exception of type "%s" is thrown',
            $this->className
        );
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        return sprintf(
            'exception of type "%s"',
            $this->className
        );
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that checks if the file/dir(name) that it is evaluated for is writable.
 *
 * The file path to check is passed as $other in evaluate().
 */
class PHPUnit_Framework_Constraint_IsWritable extends PHPUnit_Framework_Constraint
{
    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        return is_writable($other);
    }

    /**
     * Returns the description of the failure
     *
     * The beginning of failure messages is "Failed asserting that" in most
     * cases. This method should return the second part of that sentence.
     *
     * @param mixed $other Evaluated value or object.
     *
     * @return string
     */
    protected function failureDescription($other)
    {
        return sprintf(
            '"%s" is writable',
            $other
        );
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        return 'is writable';
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

class PHPUnit_Framework_Constraint_ExceptionCode extends PHPUnit_Framework_Constraint
{
    /**
     * @var int
     */
    protected $expectedCode;

    /**
     * @param int $expected
     */
    public function __construct($expected)
    {
        parent::__construct();
        $this->expectedCode = $expected;
    }

    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param Exception $other
     *
     * @return bool
     */
    protected function matches($other)
    {
        return (string) $other->getCode() == (string) $this->expectedCode;
    }

    /**
     * Returns the description of the failure
     *
     * The beginning of failure messages is "Failed asserting that" in most
     * cases. This method should return the second part of that sentence.
     *
     * @param mixed $other Evaluated value or object.
     *
     * @return string
     */
    protected function failureDescription($other)
    {
        return sprintf(
            '%s is equal to expected exception code %s',
            $this->exporter->export($other->getCode()),
            $this->exporter->export($this->expectedCode)
        );
    }

    /**
     * @return string
     */
    public function toString()
    {
        return 'exception code is ';
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that evaluates against a specified closure.
 */
class PHPUnit_Framework_Constraint_Callback extends PHPUnit_Framework_Constraint
{
    private $callback;

    /**
     * @param callable $callback
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function __construct($callback)
    {
        if (!is_callable($callback)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(
                1,
                'callable'
            );
        }

        parent::__construct();

        $this->callback = $callback;
    }

    /**
     * Evaluates the constraint for parameter $value. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        return call_user_func($this->callback, $other);
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        return 'is accepted by specified callback';
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that asserts that the value it is evaluated for is greater
 * than a given value.
 */
class PHPUnit_Framework_Constraint_GreaterThan extends PHPUnit_Framework_Constraint
{
    /**
     * @var numeric
     */
    protected $value;

    /**
     * @param numeric $value
     */
    public function __construct($value)
    {
        parent::__construct();
        $this->value = $value;
    }

    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        return $this->value < $other;
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        return 'is greater than ' . $this->exporter->export($this->value);
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that asserts that the string it is evaluated for ends with a given
 * suffix.
 */
class PHPUnit_Framework_Constraint_StringEndsWith extends PHPUnit_Framework_Constraint
{
    /**
     * @var string
     */
    protected $suffix;

    /**
     * @param string $suffix
     */
    public function __construct($suffix)
    {
        parent::__construct();
        $this->suffix = $suffix;
    }

    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        return substr($other, 0 - strlen($this->suffix)) == $this->suffix;
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        return 'ends with "' . $this->suffix . '"';
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that accepts finite.
 */
class PHPUnit_Framework_Constraint_IsFinite extends PHPUnit_Framework_Constraint
{
    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        return is_finite($other);
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        return 'is finite';
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Constraint that checks if the file(name) that it is evaluated for exists.
 *
 * The file path to check is passed as $other in evaluate().
 */
class PHPUnit_Framework_Constraint_FileExists extends PHPUnit_Framework_Constraint
{
    /**
     * Evaluates the constraint for parameter $other. Returns true if the
     * constraint is met, false otherwise.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    protected function matches($other)
    {
        return file_exists($other);
    }

    /**
     * Returns the description of the failure
     *
     * The beginning of failure messages is "Failed asserting that" in most
     * cases. This method should return the second part of that sentence.
     *
     * @param mixed $other Evaluated value or object.
     *
     * @return string
     */
    protected function failureDescription($other)
    {
        return sprintf(
            'file "%s" exists',
            $other
        );
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        return 'file exists';
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * An incomplete test case
 */
class PHPUnit_Framework_IncompleteTestCase extends PHPUnit_Framework_TestCase
{
    /**
     * @var string
     */
    protected $message = '';

    /**
     * @var bool
     */
    protected $backupGlobals = false;

    /**
     * @var bool
     */
    protected $backupStaticAttributes = false;

    /**
     * @var bool
     */
    protected $runTestInSeparateProcess = false;

    /**
     * @var bool
     */
    protected $useErrorHandler = false;

    /**
     * @var bool
     */
    protected $useOutputBuffering = false;

    /**
     * @param string $className
     * @param string $methodName
     * @param string $message
     */
    public function __construct($className, $methodName, $message = '')
    {
        $this->message = $message;
        parent::__construct($className . '::' . $methodName);
    }

    /**
     * @throws PHPUnit_Framework_Exception
     */
    protected function runTest()
    {
        $this->markTestIncomplete($this->message);
    }

    /**
     * @return string
     */
    public function getMessage()
    {
        return $this->message;
    }

    /**
     * Returns a string representation of the test case.
     *
     * @return string
     */
    public function toString()
    {
        return $this->getName();
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A TestFailure collects a failed test together with the caught exception.
 */
class PHPUnit_Framework_TestFailure
{
    /**
     * @var string
     */
    private $testName;

    /**
     * @var PHPUnit_Framework_Test|null
     */
    protected $failedTest;

    /**
     * @var Exception
     */
    protected $thrownException;

    /**
     * Constructs a TestFailure with the given test and exception.
     *
     * @param PHPUnit_Framework_Test $failedTest
     * @param Throwable              $t
     */
    public function __construct(PHPUnit_Framework_Test $failedTest, $t)
    {
        if ($failedTest instanceof PHPUnit_Framework_SelfDescribing) {
            $this->testName = $failedTest->toString();
        } else {
            $this->testName = get_class($failedTest);
        }

        if (!$failedTest instanceof PHPUnit_Framework_TestCase || !$failedTest->isInIsolation()) {
            $this->failedTest = $failedTest;
        }

        $this->thrownException = $t;
    }

    /**
     * Returns a short description of the failure.
     *
     * @return string
     */
    public function toString()
    {
        return sprintf(
            '%s: %s',
            $this->testName,
            $this->thrownException->getMessage()
        );
    }

    /**
     * Returns a description for the thrown exception.
     *
     * @return string
     */
    public function getExceptionAsString()
    {
        return self::exceptionToString($this->thrownException);
    }

    /**
     * Returns a description for an exception.
     *
     * @param Exception $e
     *
     * @return string
     */
    public static function exceptionToString(Exception $e)
    {
        if ($e instanceof PHPUnit_Framework_SelfDescribing) {
            $buffer = $e->toString();

            if ($e instanceof PHPUnit_Framework_ExpectationFailedException && $e->getComparisonFailure()) {
                $buffer = $buffer . $e->getComparisonFailure()->getDiff();
            }

            if (!empty($buffer)) {
                $buffer = trim($buffer) . "\n";
            }
        } elseif ($e instanceof PHPUnit_Framework_Error) {
            $buffer = $e->getMessage() . "\n";
        } elseif ($e instanceof PHPUnit_Framework_ExceptionWrapper) {
            $buffer = $e->getClassName() . ': ' . $e->getMessage() . "\n";
        } else {
            $buffer = get_class($e) . ': ' . $e->getMessage() . "\n";
        }

        return $buffer;
    }

    /**
     * Returns the name of the failing test (including data set, if any).
     *
     * @return string
     */
    public function getTestName()
    {
        return $this->testName;
    }

    /**
     * Returns the failing test.
     *
     * Note: The test object is not set when the test is executed in process
     * isolation.
     *
     * @see PHPUnit_Framework_Exception
     *
     * @return PHPUnit_Framework_Test|null
     */
    public function failedTest()
    {
        return $this->failedTest;
    }

    /**
     * Gets the thrown exception.
     *
     * @return Exception
     */
    public function thrownException()
    {
        return $this->thrownException;
    }

    /**
     * Returns the exception's message.
     *
     * @return string
     */
    public function exceptionMessage()
    {
        return $this->thrownException()->getMessage();
    }

    /**
     * Returns true if the thrown exception
     * is of type AssertionFailedError.
     *
     * @return bool
     */
    public function isFailure()
    {
        return ($this->thrownException() instanceof PHPUnit_Framework_AssertionFailedError);
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A skipped test case
 */
class PHPUnit_Framework_SkippedTestCase extends PHPUnit_Framework_TestCase
{
    /**
     * @var string
     */
    protected $message = '';

    /**
     * @var bool
     */
    protected $backupGlobals = false;

    /**
     * @var bool
     */
    protected $backupStaticAttributes = false;

    /**
     * @var bool
     */
    protected $runTestInSeparateProcess = false;

    /**
     * @var bool
     */
    protected $useErrorHandler = false;

    /**
     * @var bool
     */
    protected $useOutputBuffering = false;

    /**
     * @param string $message
     */
    public function __construct($className, $methodName, $message = '')
    {
        $this->message = $message;
        parent::__construct($className . '::' . $methodName);
    }

    /**
     * @throws PHPUnit_Framework_Exception
     */
    protected function runTest()
    {
        $this->markTestSkipped($this->message);
    }

    /**
     * @return string
     */
    public function getMessage()
    {
        return $this->message;
    }

    /**
     * Returns a string representation of the test case.
     *
     * @return string
     */
    public function toString()
    {
        return $this->getName();
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Thrown when an assertion failed.
 */
class PHPUnit_Framework_AssertionFailedError extends PHPUnit_Framework_Exception implements PHPUnit_Framework_SelfDescribing
{
    /**
     * Wrapper for getMessage() which is declared as final.
     *
     * @return string
     */
    public function toString()
    {
        return $this->getMessage();
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Base class for all test runners.
 */
abstract class PHPUnit_Runner_BaseTestRunner
{
    const STATUS_PASSED     = 0;
    const STATUS_SKIPPED    = 1;
    const STATUS_INCOMPLETE = 2;
    const STATUS_FAILURE    = 3;
    const STATUS_ERROR      = 4;
    const STATUS_RISKY      = 5;
    const STATUS_WARNING    = 6;
    const SUITE_METHODNAME  = 'suite';

    /**
     * Returns the loader to be used.
     *
     * @return PHPUnit_Runner_TestSuiteLoader
     */
    public function getLoader()
    {
        return new PHPUnit_Runner_StandardTestSuiteLoader;
    }

    /**
     * Returns the Test corresponding to the given suite.
     * This is a template method, subclasses override
     * the runFailed() and clearStatus() methods.
     *
     * @param string $suiteClassName
     * @param string $suiteClassFile
     * @param mixed  $suffixes
     *
     * @return PHPUnit_Framework_Test
     */
    public function getTest($suiteClassName, $suiteClassFile = '', $suffixes = '')
    {
        if (is_dir($suiteClassName) &&
            !is_file($suiteClassName . '.php') && empty($suiteClassFile)) {
            $facade = new File_Iterator_Facade;
            $files  = $facade->getFilesAsArray(
                $suiteClassName,
                $suffixes
            );

            $suite = new PHPUnit_Framework_TestSuite($suiteClassName);
            $suite->addTestFiles($files);

            return $suite;
        }

        try {
            $testClass = $this->loadSuiteClass(
                $suiteClassName,
                $suiteClassFile
            );
        } catch (PHPUnit_Framework_Exception $e) {
            $this->runFailed($e->getMessage());

            return;
        }

        try {
            $suiteMethod = $testClass->getMethod(self::SUITE_METHODNAME);

            if (!$suiteMethod->isStatic()) {
                $this->runFailed(
                    'suite() method must be static.'
                );

                return;
            }

            try {
                $test = $suiteMethod->invoke(null, $testClass->getName());
            } catch (ReflectionException $e) {
                $this->runFailed(
                    sprintf(
                        "Failed to invoke suite() method.\n%s",
                        $e->getMessage()
                    )
                );

                return;
            }
        } catch (ReflectionException $e) {
            try {
                $test = new PHPUnit_Framework_TestSuite($testClass);
            } catch (PHPUnit_Framework_Exception $e) {
                $test = new PHPUnit_Framework_TestSuite;
                $test->setName($suiteClassName);
            }
        }

        $this->clearStatus();

        return $test;
    }

    /**
     * Returns the loaded ReflectionClass for a suite name.
     *
     * @param string $suiteClassName
     * @param string $suiteClassFile
     *
     * @return ReflectionClass
     */
    protected function loadSuiteClass($suiteClassName, $suiteClassFile = '')
    {
        $loader = $this->getLoader();

        return $loader->load($suiteClassName, $suiteClassFile);
    }

    /**
     * Clears the status message.
     */
    protected function clearStatus()
    {
    }

    /**
     * Override to define how to handle a failed loading of
     * a test suite.
     *
     * @param string $message
     */
    abstract protected function runFailed($message);
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * The standard test suite loader.
 */
class PHPUnit_Runner_StandardTestSuiteLoader implements PHPUnit_Runner_TestSuiteLoader
{
    /**
     * @param string $suiteClassName
     * @param string $suiteClassFile
     *
     * @return ReflectionClass
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function load($suiteClassName, $suiteClassFile = '')
    {
        $suiteClassName = str_replace('.php', '', $suiteClassName);

        if (empty($suiteClassFile)) {
            $suiteClassFile = PHPUnit_Util_Filesystem::classNameToFilename(
                $suiteClassName
            );
        }

        if (!class_exists($suiteClassName, false)) {
            $loadedClasses = get_declared_classes();

            $filename = PHPUnit_Util_Fileloader::checkAndLoad($suiteClassFile);

            $loadedClasses = array_values(
                array_diff(get_declared_classes(), $loadedClasses)
            );
        }

        if (!class_exists($suiteClassName, false) && !empty($loadedClasses)) {
            $offset = 0 - strlen($suiteClassName);

            foreach ($loadedClasses as $loadedClass) {
                $class = new ReflectionClass($loadedClass);
                if (substr($loadedClass, $offset) === $suiteClassName &&
                    $class->getFileName() == $filename) {
                    $suiteClassName = $loadedClass;
                    break;
                }
            }
        }

        if (!class_exists($suiteClassName, false) && !empty($loadedClasses)) {
            $testCaseClass = 'PHPUnit_Framework_TestCase';

            foreach ($loadedClasses as $loadedClass) {
                $class     = new ReflectionClass($loadedClass);
                $classFile = $class->getFileName();

                if ($class->isSubclassOf($testCaseClass) &&
                    !$class->isAbstract()) {
                    $suiteClassName = $loadedClass;
                    $testCaseClass  = $loadedClass;

                    if ($classFile == realpath($suiteClassFile)) {
                        break;
                    }
                }

                if ($class->hasMethod('suite')) {
                    $method = $class->getMethod('suite');

                    if (!$method->isAbstract() &&
                        $method->isPublic() &&
                        $method->isStatic()) {
                        $suiteClassName = $loadedClass;

                        if ($classFile == realpath($suiteClassFile)) {
                            break;
                        }
                    }
                }
            }
        }

        if (class_exists($suiteClassName, false)) {
            $class = new ReflectionClass($suiteClassName);

            if ($class->getFileName() == realpath($suiteClassFile)) {
                return $class;
            }
        }

        throw new PHPUnit_Framework_Exception(
            sprintf(
                "Class '%s' could not be found in '%s'.",
                $suiteClassName,
                $suiteClassFile
            )
        );
    }

    /**
     * @param ReflectionClass $aClass
     *
     * @return ReflectionClass
     */
    public function reload(ReflectionClass $aClass)
    {
        return $aClass;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use SebastianBergmann\Version;

/**
 * This class defines the current version of PHPUnit.
 */
class PHPUnit_Runner_Version
{
    private static $pharVersion;
    private static $version;

    /**
     * Returns the current version of PHPUnit.
     *
     * @return string
     */
    public static function id()
    {
        if (self::$pharVersion !== null) {
            return self::$pharVersion;
        }

        if (self::$version === null) {
            $version       = new Version('5.7.27', dirname(dirname(__DIR__)));
            self::$version = $version->getVersion();
        }

        return self::$version;
    }

    /**
     * @return string
     */
    public static function series()
    {
        if (strpos(self::id(), '-')) {
            $version = explode('-', self::id())[0];
        } else {
            $version = self::id();
        }

        return implode('.', array_slice(explode('.', $version), 0, 2));
    }

    /**
     * @return string
     */
    public static function getVersionString()
    {
        return 'PHPUnit ' . self::id() . ' by Sebastian Bergmann and contributors.';
    }

    /**
     * @return string
     */
    public static function getReleaseChannel()
    {
        if (strpos(self::$pharVersion, '-') !== false) {
            return '-nightly';
        }

        return '';
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * An interface to define how a test suite should be loaded.
 */
interface PHPUnit_Runner_TestSuiteLoader
{
    /**
     * @param string $suiteClassName
     * @param string $suiteClassFile
     *
     * @return ReflectionClass
     */
    public function load($suiteClassName, $suiteClassFile = '');

    /**
     * @param ReflectionClass $aClass
     *
     * @return ReflectionClass
     */
    public function reload(ReflectionClass $aClass);
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

class PHPUnit_Runner_Filter_Factory
{
    /**
     * @var array
     */
    private $filters = [];

    /**
     * @param ReflectionClass $filter
     * @param mixed           $args
     */
    public function addFilter(ReflectionClass $filter, $args)
    {
        if (!$filter->isSubclassOf('RecursiveFilterIterator')) {
            throw new InvalidArgumentException(
                sprintf(
                    'Class "%s" does not extend RecursiveFilterIterator',
                    $filter->name
                )
            );
        }

        $this->filters[] = [$filter, $args];
    }

    /**
     * @return FilterIterator
     */
    public function factory(Iterator $iterator, PHPUnit_Framework_TestSuite $suite)
    {
        foreach ($this->filters as $filter) {
            list($class, $args) = $filter;
            $iterator           = $class->newInstance($iterator, $args, $suite);
        }

        return $iterator;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

abstract class PHPUnit_Runner_Filter_GroupFilterIterator extends RecursiveFilterIterator
{
    /**
     * @var array
     */
    protected $groupTests = [];

    /**
     * @param RecursiveIterator           $iterator
     * @param array                       $groups
     * @param PHPUnit_Framework_TestSuite $suite
     */
    public function __construct(RecursiveIterator $iterator, array $groups, PHPUnit_Framework_TestSuite $suite)
    {
        parent::__construct($iterator);

        foreach ($suite->getGroupDetails() as $group => $tests) {
            if (in_array($group, $groups)) {
                $testHashes = array_map(
                    function ($test) {
                        return spl_object_hash($test);
                    },
                    $tests
                );

                $this->groupTests = array_merge($this->groupTests, $testHashes);
            }
        }
    }

    /**
     * @return bool
     */
    public function accept()
    {
        $test = $this->getInnerIterator()->current();

        if ($test instanceof PHPUnit_Framework_TestSuite) {
            return true;
        }

        return $this->doAccept(spl_object_hash($test));
    }

    abstract protected function doAccept($hash);
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

class PHPUnit_Runner_Filter_Group_Include extends PHPUnit_Runner_Filter_GroupFilterIterator
{
    /**
     * @param string $hash
     *
     * @return bool
     */
    protected function doAccept($hash)
    {
        return in_array($hash, $this->groupTests);
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

class PHPUnit_Runner_Filter_Group_Exclude extends PHPUnit_Runner_Filter_GroupFilterIterator
{
    /**
     * @param string $hash
     *
     * @return bool
     */
    protected function doAccept($hash)
    {
        return !in_array($hash, $this->groupTests);
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

class PHPUnit_Runner_Filter_Test extends RecursiveFilterIterator
{
    /**
     * @var string
     */
    protected $filter = null;

    /**
     * @var int
     */
    protected $filterMin;
    /**
     * @var int
     */
    protected $filterMax;

    /**
     * @param RecursiveIterator $iterator
     * @param string            $filter
     */
    public function __construct(RecursiveIterator $iterator, $filter)
    {
        parent::__construct($iterator);
        $this->setFilter($filter);
    }

    /**
     * @param string $filter
     */
    protected function setFilter($filter)
    {
        if (PHPUnit_Util_Regex::pregMatchSafe($filter, '') === false) {
            // Handles:
            //  * testAssertEqualsSucceeds#4
            //  * testAssertEqualsSucceeds#4-8
            if (preg_match('/^(.*?)#(\d+)(?:-(\d+))?$/', $filter, $matches)) {
                if (isset($matches[3]) && $matches[2] < $matches[3]) {
                    $filter = sprintf(
                        '%s.*with data set #(\d+)$',
                        $matches[1]
                    );

                    $this->filterMin = $matches[2];
                    $this->filterMax = $matches[3];
                } else {
                    $filter = sprintf(
                        '%s.*with data set #%s$',
                        $matches[1],
                        $matches[2]
                    );
                }
            } // Handles:
            //  * testDetermineJsonError@JSON_ERROR_NONE
            //  * testDetermineJsonError@JSON.*
            elseif (preg_match('/^(.*?)@(.+)$/', $filter, $matches)) {
                $filter = sprintf(
                    '%s.*with data set "%s"$',
                    $matches[1],
                    $matches[2]
                );
            }

            // Escape delimiters in regular expression. Do NOT use preg_quote,
            // to keep magic characters.
            $filter = sprintf('/%s/', str_replace(
                '/',
                '\\/',
                $filter
            ));
        }

        $this->filter = $filter;
    }

    /**
     * @return bool
     */
    public function accept()
    {
        $test = $this->getInnerIterator()->current();

        if ($test instanceof PHPUnit_Framework_TestSuite) {
            return true;
        }

        if ($test instanceof PHPUnit_Framework_WarningTestCase) {
            $name = $test->getMessage();
        } else {
            $tmp = PHPUnit_Util_Test::describe($test, false);

            if ($tmp[0] != '') {
                $name = implode('::', $tmp);
            } else {
                $name = $tmp[1];
            }
        }

        $accepted = @preg_match($this->filter, $name, $matches);

        if ($accepted && isset($this->filterMax)) {
            $set      = end($matches);
            $accepted = $set >= $this->filterMin && $set <= $this->filterMax;
        }

        return $accepted;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

class PHPUnit_Runner_Exception extends RuntimeException implements PHPUnit_Exception
{
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Suite for .phpt test cases.
 */
class PHPUnit_Extensions_PhptTestSuite extends PHPUnit_Framework_TestSuite
{
    /**
     * Constructs a new TestSuite for .phpt test cases.
     *
     * @param string $directory
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function __construct($directory)
    {
        if (is_string($directory) && is_dir($directory)) {
            $this->setName($directory);

            $facade = new File_Iterator_Facade;
            $files  = $facade->getFilesAsArray($directory, '.phpt');

            foreach ($files as $file) {
                $this->addTestFile($file);
            }
        } else {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'directory name');
        }
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A Decorator that runs a test repeatedly.
 */
class PHPUnit_Extensions_RepeatedTest extends PHPUnit_Extensions_TestDecorator
{
    /**
     * @var bool
     */
    protected $processIsolation = false;

    /**
     * @var int
     */
    protected $timesRepeat = 1;

    /**
     * @param PHPUnit_Framework_Test $test
     * @param int                    $timesRepeat
     * @param bool                   $processIsolation
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function __construct(PHPUnit_Framework_Test $test, $timesRepeat = 1, $processIsolation = false)
    {
        parent::__construct($test);

        if (is_int($timesRepeat) &&
            $timesRepeat >= 0) {
            $this->timesRepeat = $timesRepeat;
        } else {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(
                2,
                'positive integer'
            );
        }

        $this->processIsolation = $processIsolation;
    }

    /**
     * Counts the number of test cases that
     * will be run by this test.
     *
     * @return int
     */
    public function count()
    {
        return $this->timesRepeat * count($this->test);
    }

    /**
     * Runs the decorated test and collects the
     * result in a TestResult.
     *
     * @param PHPUnit_Framework_TestResult $result
     *
     * @return PHPUnit_Framework_TestResult
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function run(PHPUnit_Framework_TestResult $result = null)
    {
        if ($result === null) {
            $result = $this->createResult();
        }

        //@codingStandardsIgnoreStart
        for ($i = 0; $i < $this->timesRepeat && !$result->shouldStop(); $i++) {
            //@codingStandardsIgnoreEnd
            if ($this->test instanceof PHPUnit_Framework_TestSuite) {
                $this->test->setRunTestInSeparateProcess($this->processIsolation);
            }
            $this->test->run($result);
        }

        return $result;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Runner for PHPT test cases.
 */
class PHPUnit_Extensions_PhptTestCase implements PHPUnit_Framework_Test, PHPUnit_Framework_SelfDescribing
{
    /**
     * @var string
     */
    private $filename;

    /**
     * @var PHPUnit_Util_PHP
     */
    private $phpUtil;

    /**
     * @var array
     */
    private $settings = [
        'allow_url_fopen=1',
        'auto_append_file=',
        'auto_prepend_file=',
        'disable_functions=',
        'display_errors=1',
        'docref_root=',
        'docref_ext=.html',
        'error_append_string=',
        'error_prepend_string=',
        'error_reporting=-1',
        'html_errors=0',
        'log_errors=0',
        'magic_quotes_runtime=0',
        'output_handler=',
        'open_basedir=',
        'output_buffering=Off',
        'report_memleaks=0',
        'report_zend_debug=0',
        'safe_mode=0',
        'xdebug.default_enable=0'
    ];

    /**
     * Constructs a test case with the given filename.
     *
     * @param string           $filename
     * @param PHPUnit_Util_PHP $phpUtil
     *
     * @throws PHPUnit_Framework_Exception
     */
    public function __construct($filename, $phpUtil = null)
    {
        if (!is_string($filename)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        if (!is_file($filename)) {
            throw new PHPUnit_Framework_Exception(
                sprintf(
                    'File "%s" does not exist.',
                    $filename
                )
            );
        }

        $this->filename = $filename;
        $this->phpUtil  = $phpUtil ?: PHPUnit_Util_PHP::factory();
    }

    /**
     * Counts the number of test cases executed by run(TestResult result).
     *
     * @return int
     */
    public function count()
    {
        return 1;
    }

    /**
     * @param array  $sections
     * @param string $output
     */
    private function assertPhptExpectation(array $sections, $output)
    {
        $assertions = [
            'EXPECT'      => 'assertEquals',
            'EXPECTF'     => 'assertStringMatchesFormat',
            'EXPECTREGEX' => 'assertRegExp',
        ];

        $actual = preg_replace('/\r\n/', "\n", trim($output));

        foreach ($assertions as $sectionName => $sectionAssertion) {
            if (isset($sections[$sectionName])) {
                $sectionContent = preg_replace('/\r\n/', "\n", trim($sections[$sectionName]));
                $assertion      = $sectionAssertion;
                $expected       = $sectionName == 'EXPECTREGEX' ? "/{$sectionContent}/" : $sectionContent;

                break;
            }
        }

        PHPUnit_Framework_Assert::$assertion($expected, $actual);
    }

    /**
     * Runs a test and collects its result in a TestResult instance.
     *
     * @param PHPUnit_Framework_TestResult $result
     *
     * @return PHPUnit_Framework_TestResult
     */
    public function run(PHPUnit_Framework_TestResult $result = null)
    {
        $sections = $this->parse();
        $code     = $this->render($sections['FILE']);

        if ($result === null) {
            $result = new PHPUnit_Framework_TestResult;
        }

        $skip     = false;
        $xfail    = false;
        $time     = 0;
        $settings = $this->settings;

        $result->startTest($this);

        if (isset($sections['INI'])) {
            $settings = array_merge($settings, $this->parseIniSection($sections['INI']));
        }

        if (isset($sections['ENV'])) {
            $env = $this->parseEnvSection($sections['ENV']);
            $this->phpUtil->setEnv($env);
        }

        // Redirects STDERR to STDOUT
        $this->phpUtil->setUseStderrRedirection(true);

        if ($result->enforcesTimeLimit()) {
            $this->phpUtil->setTimeout($result->getTimeoutForLargeTests());
        }

        if (isset($sections['SKIPIF'])) {
            $jobResult = $this->phpUtil->runJob($sections['SKIPIF'], $settings);

            if (!strncasecmp('skip', ltrim($jobResult['stdout']), 4)) {
                if (preg_match('/^\s*skip\s*(.+)\s*/i', $jobResult['stdout'], $message)) {
                    $message = substr($message[1], 2);
                } else {
                    $message = '';
                }

                $result->addFailure($this, new PHPUnit_Framework_SkippedTestError($message), 0);

                $skip = true;
            }
        }

        if (isset($sections['XFAIL'])) {
            $xfail = trim($sections['XFAIL']);
        }

        if (!$skip) {
            if (isset($sections['STDIN'])) {
                $this->phpUtil->setStdin($sections['STDIN']);
            }

            if (isset($sections['ARGS'])) {
                $this->phpUtil->setArgs($sections['ARGS']);
            }

            PHP_Timer::start();

            $jobResult = $this->phpUtil->runJob($code, $settings);
            $time      = PHP_Timer::stop();

            try {
                $this->assertPhptExpectation($sections, $jobResult['stdout']);
            } catch (PHPUnit_Framework_AssertionFailedError $e) {
                if ($xfail !== false) {
                    $result->addFailure(
                        $this,
                        new PHPUnit_Framework_IncompleteTestError(
                            $xfail,
                            0,
                            $e
                        ),
                        $time
                    );
                } else {
                    $result->addFailure($this, $e, $time);
                }
            } catch (Throwable $t) {
                $result->addError($this, $t, $time);
            } catch (Exception $e) {
                $result->addError($this, $e, $time);
            }

            if ($result->allCompletelyImplemented() && $xfail !== false) {
                $result->addFailure(
                    $this,
                    new PHPUnit_Framework_IncompleteTestError(
                        'XFAIL section but test passes'
                    ),
                    $time
                );
            }

            $this->phpUtil->setStdin('');
            $this->phpUtil->setArgs('');

            if (isset($sections['CLEAN'])) {
                $cleanCode = $this->render($sections['CLEAN']);

                $this->phpUtil->runJob($cleanCode, $this->settings);
            }
        }

        $result->endTest($this, $time);

        return $result;
    }

    /**
     * Returns the name of the test case.
     *
     * @return string
     */
    public function getName()
    {
        return $this->toString();
    }

    /**
     * Returns a string representation of the test case.
     *
     * @return string
     */
    public function toString()
    {
        return $this->filename;
    }

    /**
     * @return array
     *
     * @throws PHPUnit_Framework_Exception
     */
    private function parse()
    {
        $sections = [];
        $section  = '';

        $allowExternalSections = [
            'FILE',
            'EXPECT',
            'EXPECTF',
            'EXPECTREGEX'
        ];

        $requiredSections = [
            'FILE',
            [
                'EXPECT',
                'EXPECTF',
                'EXPECTREGEX'
            ]
        ];

        $unsupportedSections = [
            'REDIRECTTEST',
            'REQUEST',
            'POST',
            'PUT',
            'POST_RAW',
            'GZIP_POST',
            'DEFLATE_POST',
            'GET',
            'COOKIE',
            'HEADERS',
            'CGI',
            'EXPECTHEADERS',
            'EXTENSIONS',
            'PHPDBG'
        ];

        foreach (file($this->filename) as $line) {
            if (preg_match('/^--([_A-Z]+)--/', $line, $result)) {
                $section            = $result[1];
                $sections[$section] = '';

                continue;
            } elseif (empty($section)) {
                throw new PHPUnit_Framework_Exception('Invalid PHPT file');
            }

            $sections[$section] .= $line;
        }

        if (isset($sections['FILEEOF'])) {
            $sections['FILE'] = rtrim($sections['FILEEOF'], "\r\n");
            unset($sections['FILEEOF']);
        }

        $testDirectory = dirname($this->filename) . DIRECTORY_SEPARATOR;

        foreach ($allowExternalSections as $section) {
            if (isset($sections[$section . '_EXTERNAL'])) {
                // do not allow directory traversal
                $externalFilename = str_replace('..', '', trim($sections[$section . '_EXTERNAL']));

                // only allow files from the test directory
                if (!is_file($testDirectory . $externalFilename) || !is_readable($testDirectory . $externalFilename)) {
                    throw new PHPUnit_Framework_Exception(
                        sprintf(
                            'Could not load --%s-- %s for PHPT file',
                            $section . '_EXTERNAL',
                            $testDirectory . $externalFilename
                        )
                    );
                }

                $sections[$section] = file_get_contents($testDirectory . $externalFilename);

                unset($sections[$section . '_EXTERNAL']);
            }
        }

        $isValid = true;

        foreach ($requiredSections as $section) {
            if (is_array($section)) {
                $foundSection = false;

                foreach ($section as $anySection) {
                    if (isset($sections[$anySection])) {
                        $foundSection = true;

                        break;
                    }
                }

                if (!$foundSection) {
                    $isValid = false;

                    break;
                }
            } else {
                if (!isset($sections[$section])) {
                    $isValid = false;

                    break;
                }
            }
        }

        if (!$isValid) {
            throw new PHPUnit_Framework_Exception('Invalid PHPT file');
        }

        foreach ($unsupportedSections as $section) {
            if (isset($sections[$section])) {
                throw new PHPUnit_Framework_Exception(
                    'PHPUnit does not support this PHPT file'
                );
            }
        }

        return $sections;
    }

    /**
     * @param string $code
     *
     * @return string
     */
    private function render($code)
    {
        return str_replace(
            [
                '__DIR__',
                '__FILE__'
            ],
            [
                "'" . dirname($this->filename) . "'",
                "'" . $this->filename . "'"
            ],
            $code
        );
    }

    /**
     * Parse --INI-- section key value pairs and return as array.
     *
     * @param string
     *
     * @return array
     */
    protected function parseIniSection($content)
    {
        return preg_split('/\n|\r/', $content, -1, PREG_SPLIT_NO_EMPTY);
    }

    protected function parseEnvSection($content)
    {
        $env = [];

        foreach (explode("\n", trim($content)) as $e) {
            $e = explode('=', trim($e), 2);

            if (!empty($e[0]) && isset($e[1])) {
                $env[$e[0]] = $e[1];
            }
        }

        return $env;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A Decorator for Tests.
 *
 * Use TestDecorator as the base class for defining new
 * test decorators. Test decorator subclasses can be introduced
 * to add behaviour before or after a test is run.
 */
class PHPUnit_Extensions_TestDecorator extends PHPUnit_Framework_Assert implements PHPUnit_Framework_Test, PHPUnit_Framework_SelfDescribing
{
    /**
     * The Test to be decorated.
     *
     * @var object
     */
    protected $test = null;

    /**
     * Constructor.
     *
     * @param PHPUnit_Framework_Test $test
     */
    public function __construct(PHPUnit_Framework_Test $test)
    {
        $this->test = $test;
    }

    /**
     * Returns a string representation of the test.
     *
     * @return string
     */
    public function toString()
    {
        return $this->test->toString();
    }

    /**
     * Runs the test and collects the
     * result in a TestResult.
     *
     * @param PHPUnit_Framework_TestResult $result
     */
    public function basicRun(PHPUnit_Framework_TestResult $result)
    {
        $this->test->run($result);
    }

    /**
     * Counts the number of test cases that
     * will be run by this test.
     *
     * @return int
     */
    public function count()
    {
        return count($this->test);
    }

    /**
     * Creates a default TestResult object.
     *
     * @return PHPUnit_Framework_TestResult
     */
    protected function createResult()
    {
        return new PHPUnit_Framework_TestResult;
    }

    /**
     * Returns the test to be run.
     *
     * @return PHPUnit_Framework_Test
     */
    public function getTest()
    {
        return $this->test;
    }

    /**
     * Runs the decorated test and collects the
     * result in a TestResult.
     *
     * @param PHPUnit_Framework_TestResult $result
     *
     * @return PHPUnit_Framework_TestResult
     */
    public function run(PHPUnit_Framework_TestResult $result = null)
    {
        if ($result === null) {
            $result = $this->createResult();
        }

        $this->basicRun($result);

        return $result;
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Base class for test listeners that interact with an issue tracker.
 */
abstract class PHPUnit_Extensions_TicketListener implements PHPUnit_Framework_TestListener
{
    /**
     * @var array
     */
    protected $ticketCounts = [];

    /**
     * @var bool
     */
    protected $ran = false;

    /**
     * An error occurred.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addError(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
    }

    /**
     * A failure occurred.
     *
     * @param PHPUnit_Framework_Test                 $test
     * @param PHPUnit_Framework_AssertionFailedError $e
     * @param float                                  $time
     */
    public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time)
    {
    }

    /**
     * Incomplete test.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
    }

    /**
     * Risky test.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
    }

    /**
     * Skipped test.
     *
     * @param PHPUnit_Framework_Test $test
     * @param Exception              $e
     * @param float                  $time
     */
    public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
    }

    /**
     * A test suite started.
     *
     * @param PHPUnit_Framework_TestSuite $suite
     */
    public function startTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
    }

    /**
     * A test suite ended.
     *
     * @param PHPUnit_Framework_TestSuite $suite
     */
    public function endTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
    }

    /**
     * A test started.
     *
     * @param PHPUnit_Framework_Test $test
     */
    public function startTest(PHPUnit_Framework_Test $test)
    {
        if (!$test instanceof PHPUnit_Framework_WarningTestCase) {
            if ($this->ran) {
                return;
            }

            $name    = $test->getName(false);
            $tickets = PHPUnit_Util_Test::getTickets(get_class($test), $name);

            foreach ($tickets as $ticket) {
                $this->ticketCounts[$ticket][$name] = 1;
            }

            $this->ran = true;
        }
    }

    /**
     * A test ended.
     *
     * @param PHPUnit_Framework_Test $test
     * @param float                  $time
     */
    public function endTest(PHPUnit_Framework_Test $test, $time)
    {
        if (!$test instanceof PHPUnit_Framework_WarningTestCase) {
            if ($test->getStatus() == PHPUnit_Runner_BaseTestRunner::STATUS_PASSED) {
                $ifStatus   = ['assigned', 'new', 'reopened'];
                $newStatus  = 'closed';
                $message    = 'Automatically closed by PHPUnit (test passed).';
                $resolution = 'fixed';
                $cumulative = true;
            } elseif ($test->getStatus() == PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE) {
                $ifStatus   = ['closed'];
                $newStatus  = 'reopened';
                $message    = 'Automatically reopened by PHPUnit (test failed).';
                $resolution = '';
                $cumulative = false;
            } else {
                return;
            }

            $name    = $test->getName(false);
            $tickets = PHPUnit_Util_Test::getTickets(get_class($test), $name);

            foreach ($tickets as $ticket) {
                // Remove this test from the totals (if it passed).
                if ($test->getStatus() == PHPUnit_Runner_BaseTestRunner::STATUS_PASSED) {
                    unset($this->ticketCounts[$ticket][$name]);
                }

                // Only close tickets if ALL referenced cases pass
                // but reopen tickets if a single test fails.
                if ($cumulative) {
                    // Determine number of to-pass tests:
                    if (count($this->ticketCounts[$ticket]) > 0) {
                        // There exist remaining test cases with this reference.
                        $adjustTicket = false;
                    } else {
                        // No remaining tickets, go ahead and adjust.
                        $adjustTicket = true;
                    }
                } else {
                    $adjustTicket = true;
                }

                $ticketInfo = $this->getTicketInfo($ticket);

                if ($adjustTicket && in_array($ticketInfo['status'], $ifStatus)) {
                    $this->updateTicket($ticket, $newStatus, $message, $resolution);
                }
            }
        }
    }

    /**
     * @param mixed $ticketId
     *
     * @return mixed
     */
    abstract protected function getTicketInfo($ticketId = null);

    /**
     * @param string $ticketId
     * @param string $newStatus
     * @param string $message
     * @param string $resolution
     */
    abstract protected function updateTicket($ticketId, $newStatus, $message, $resolution);
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * We have a TestSuite object A.
 * In TestSuite object A we have Tests tagged with @group.
 * We want a TestSuite object B that contains TestSuite objects C, D, ...
 * for the Tests tagged with @group C, @group D, ...
 * Running the Tests from TestSuite object B results in Tests tagged with both
 *
 * @group C and @group D in TestSuite object A to be run twice .
 *
 * <code>
 * $suite = new PHPUnit_Extensions_GroupTestSuite($A, array('C', 'D'));
 * </code>
 */
class PHPUnit_Extensions_GroupTestSuite extends PHPUnit_Framework_TestSuite
{
    public function __construct(PHPUnit_Framework_TestSuite $suite, array $groups)
    {
        $groupSuites = [];
        $name        = $suite->getName();

        foreach ($groups as $group) {
            $groupSuites[$group] = new PHPUnit_Framework_TestSuite($name . ' - ' . $group);
            $this->addTest($groupSuites[$group]);
        }

        $tests = new RecursiveIteratorIterator(
            new PHPUnit_Util_TestSuiteIterator($suite),
            RecursiveIteratorIterator::LEAVES_ONLY
        );

        foreach ($tests as $test) {
            if ($test instanceof PHPUnit_Framework_TestCase) {
                $testGroups = PHPUnit_Util_Test::getGroups(
                    get_class($test),
                    $test->getName(false)
                );

                foreach ($groups as $group) {
                    foreach ($testGroups as $testGroup) {
                        if ($group == $testGroup) {
                            $groupSuites[$group]->addTest($test);
                        }
                    }
                }
            }
        }
    }
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace PHPUnit\Framework;

use PHPUnit_Framework_TestListener;

interface TestListener extends PHPUnit_Framework_TestListener
{
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace PHPUnit\Framework;

use PHPUnit_Framework_BaseTestListener;

abstract class BaseTestListener extends PHPUnit_Framework_BaseTestListener
{
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace PHPUnit\Framework;

use PHPUnit_Framework_TestSuite;

class TestSuite extends PHPUnit_Framework_TestSuite
{
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace PHPUnit\Framework;

use PHPUnit_Framework_Assert;

abstract class Assert extends PHPUnit_Framework_Assert
{
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace PHPUnit\Framework;

use PHPUnit_Framework_Test;

interface Test extends PHPUnit_Framework_Test
{
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace PHPUnit\Framework;

use PHPUnit_Framework_TestCase;

abstract class TestCase extends PHPUnit_Framework_TestCase
{
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace PHPUnit\Framework;

use PHPUnit_Framework_AssertionFailedError;

class AssertionFailedError extends PHPUnit_Framework_AssertionFailedError
{
}
<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Marker interface for PHPUnit exceptions.
 */
interface PHPUnit_Exception
{
}
{
    "name": "phpunit/php-text-template",
    "description": "Simple template engine.",
    "type": "library",
    "keywords": [
        "template"
    ],
    "homepage": "https://github.com/sebastianbergmann/php-text-template/",
    "license": "BSD-3-Clause",
    "authors": [
        {
            "name": "Sebastian Bergmann",
            "email": "sebastian@phpunit.de",
            "role": "lead"
        }
    ],
    "support": {
        "issues": "https://github.com/sebastianbergmann/php-text-template/issues"
    },
    "require": {
        "php": ">=5.3.3"
    },
    "autoload": {
        "classmap": [
            "src/"
        ]
    }
}

<?php
/*
 * This file is part of the Text_Template package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A simple template engine.
 *
 * @since Class available since Release 1.0.0
 */
class Text_Template
{
    /**
     * @var string
     */
    protected $template = '';

    /**
     * @var string
     */
    protected $openDelimiter = '{';

    /**
     * @var string
     */
    protected $closeDelimiter = '}';

    /**
     * @var array
     */
    protected $values = array();

    /**
     * Constructor.
     *
     * @param  string                   $file
     * @throws InvalidArgumentException
     */
    public function __construct($file = '', $openDelimiter = '{', $closeDelimiter = '}')
    {
        $this->setFile($file);
        $this->openDelimiter  = $openDelimiter;
        $this->closeDelimiter = $closeDelimiter;
    }

    /**
     * Sets the template file.
     *
     * @param  string                   $file
     * @throws InvalidArgumentException
     */
    public function setFile($file)
    {
        $distFile = $file . '.dist';

        if (file_exists($file)) {
            $this->template = file_get_contents($file);
        }

        else if (file_exists($distFile)) {
            $this->template = file_get_contents($distFile);
        }

        else {
            throw new InvalidArgumentException(
              'Template file could not be loaded.'
            );
        }
    }

    /**
     * Sets one or more template variables.
     *
     * @param array $values
     * @param bool  $merge
     */
    public function setVar(array $values, $merge = TRUE)
    {
        if (!$merge || empty($this->values)) {
            $this->values = $values;
        } else {
            $this->values = array_merge($this->values, $values);
        }
    }

    /**
     * Renders the template and returns the result.
     *
     * @return string
     */
    public function render()
    {
        $keys = array();

        foreach ($this->values as $key => $value) {
            $keys[] = $this->openDelimiter . $key . $this->closeDelimiter;
        }

        return str_replace($keys, $this->values, $this->template);
    }

    /**
     * Renders the template and writes the result to a file.
     *
     * @param string $target
     */
    public function renderTo($target)
    {
        $fp = @fopen($target, 'wt');

        if ($fp) {
            fwrite($fp, $this->render());
            fclose($fp);
        } else {
            $error = error_get_last();

            throw new RuntimeException(
              sprintf(
                'Could not write to %s: %s',
                $target,
                substr(
                  $error['message'],
                  strpos($error['message'], ':') + 2
                )
              )
            );
        }
    }
}

{
    "name": "phpunit/php-code-coverage",
    "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
    "type": "library",
    "keywords": [
        "coverage",
        "testing",
        "xunit"
    ],
    "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
    "license": "BSD-3-Clause",
    "authors": [
        {
            "name": "Sebastian Bergmann",
            "email": "sb@sebastian-bergmann.de",
            "role": "lead"
        }
    ],
    "support": {
        "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
        "irc": "irc://irc.freenode.net/phpunit"
    },
    "require": {
        "php": "^5.6 || ^7.0",
        "ext-dom": "*",
        "ext-xmlwriter": "*",
        "phpunit/php-file-iterator": "^1.3",
        "phpunit/php-token-stream": "^1.4.2 || ^2.0",
        "phpunit/php-text-template": "^1.2",
        "sebastian/code-unit-reverse-lookup": "^1.0",
        "sebastian/environment": "^1.3.2 || ^2.0",
        "sebastian/version": "^1.0 || ^2.0"
    },
    "require-dev": {
        "phpunit/phpunit": "^5.7",
        "ext-xdebug": "^2.1.4"
    },
    "suggest": {
        "ext-xdebug": "^2.5.1"
    },
    "autoload": {
        "classmap": [
            "src/"
        ]
    },
    "extra": {
        "branch-alias": {
            "dev-master": "4.0.x-dev"
        }
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage;

use SebastianBergmann\CodeCoverage\Driver\Driver;
use SebastianBergmann\CodeCoverage\Driver\Xdebug;
use SebastianBergmann\CodeCoverage\Driver\HHVM;
use SebastianBergmann\CodeCoverage\Driver\PHPDBG;
use SebastianBergmann\CodeCoverage\Node\Builder;
use SebastianBergmann\CodeCoverage\Node\Directory;
use SebastianBergmann\CodeUnitReverseLookup\Wizard;
use SebastianBergmann\Environment\Runtime;

/**
 * Provides collection functionality for PHP code coverage information.
 */
class CodeCoverage
{
    /**
     * @var Driver
     */
    private $driver;

    /**
     * @var Filter
     */
    private $filter;

    /**
     * @var Wizard
     */
    private $wizard;

    /**
     * @var bool
     */
    private $cacheTokens = false;

    /**
     * @var bool
     */
    private $checkForUnintentionallyCoveredCode = false;

    /**
     * @var bool
     */
    private $forceCoversAnnotation = false;

    /**
     * @var bool
     */
    private $checkForUnexecutedCoveredCode = false;

    /**
     * @var bool
     */
    private $checkForMissingCoversAnnotation = false;

    /**
     * @var bool
     */
    private $addUncoveredFilesFromWhitelist = true;

    /**
     * @var bool
     */
    private $processUncoveredFilesFromWhitelist = false;

    /**
     * @var bool
     */
    private $ignoreDeprecatedCode = false;

    /**
     * @var mixed
     */
    private $currentId;

    /**
     * Code coverage data.
     *
     * @var array
     */
    private $data = [];

    /**
     * @var array
     */
    private $ignoredLines = [];

    /**
     * @var bool
     */
    private $disableIgnoredLines = false;

    /**
     * Test data.
     *
     * @var array
     */
    private $tests = [];

    /**
     * @var string[]
     */
    private $unintentionallyCoveredSubclassesWhitelist = [];

    /**
     * Determine if the data has been initialized or not
     *
     * @var bool
     */
    private $isInitialized = false;

    /**
     * Determine whether we need to check for dead and unused code on each test
     *
     * @var bool
     */
    private $shouldCheckForDeadAndUnused = true;

    /**
     * Constructor.
     *
     * @param Driver $driver
     * @param Filter $filter
     *
     * @throws RuntimeException
     */
    public function __construct(Driver $driver = null, Filter $filter = null)
    {
        if ($driver === null) {
            $driver = $this->selectDriver();
        }

        if ($filter === null) {
            $filter = new Filter;
        }

        $this->driver = $driver;
        $this->filter = $filter;

        $this->wizard = new Wizard;
    }

    /**
     * Returns the code coverage information as a graph of node objects.
     *
     * @return Directory
     */
    public function getReport()
    {
        $builder = new Builder;

        return $builder->build($this);
    }

    /**
     * Clears collected code coverage data.
     */
    public function clear()
    {
        $this->isInitialized = false;
        $this->currentId     = null;
        $this->data          = [];
        $this->tests         = [];
    }

    /**
     * Returns the filter object used.
     *
     * @return Filter
     */
    public function filter()
    {
        return $this->filter;
    }

    /**
     * Returns the collected code coverage data.
     * Set $raw = true to bypass all filters.
     *
     * @param bool $raw
     *
     * @return array
     */
    public function getData($raw = false)
    {
        if (!$raw && $this->addUncoveredFilesFromWhitelist) {
            $this->addUncoveredFilesFromWhitelist();
        }

        return $this->data;
    }

    /**
     * Sets the coverage data.
     *
     * @param array $data
     */
    public function setData(array $data)
    {
        $this->data = $data;
    }

    /**
     * Returns the test data.
     *
     * @return array
     */
    public function getTests()
    {
        return $this->tests;
    }

    /**
     * Sets the test data.
     *
     * @param array $tests
     */
    public function setTests(array $tests)
    {
        $this->tests = $tests;
    }

    /**
     * Start collection of code coverage information.
     *
     * @param mixed $id
     * @param bool  $clear
     *
     * @throws InvalidArgumentException
     */
    public function start($id, $clear = false)
    {
        if (!is_bool($clear)) {
            throw InvalidArgumentException::create(
                1,
                'boolean'
            );
        }

        if ($clear) {
            $this->clear();
        }

        if ($this->isInitialized === false) {
            $this->initializeData();
        }

        $this->currentId = $id;

        $this->driver->start($this->shouldCheckForDeadAndUnused);
    }

    /**
     * Stop collection of code coverage information.
     *
     * @param bool  $append
     * @param mixed $linesToBeCovered
     * @param array $linesToBeUsed
     *
     * @return array
     *
     * @throws InvalidArgumentException
     */
    public function stop($append = true, $linesToBeCovered = [], array $linesToBeUsed = [])
    {
        if (!is_bool($append)) {
            throw InvalidArgumentException::create(
                1,
                'boolean'
            );
        }

        if (!is_array($linesToBeCovered) && $linesToBeCovered !== false) {
            throw InvalidArgumentException::create(
                2,
                'array or false'
            );
        }

        $data = $this->driver->stop();
        $this->append($data, null, $append, $linesToBeCovered, $linesToBeUsed);

        $this->currentId = null;

        return $data;
    }

    /**
     * Appends code coverage data.
     *
     * @param array $data
     * @param mixed $id
     * @param bool  $append
     * @param mixed $linesToBeCovered
     * @param array $linesToBeUsed
     *
     * @throws RuntimeException
     */
    public function append(array $data, $id = null, $append = true, $linesToBeCovered = [], array $linesToBeUsed = [])
    {
        if ($id === null) {
            $id = $this->currentId;
        }

        if ($id === null) {
            throw new RuntimeException;
        }

        $this->applyListsFilter($data);
        $this->applyIgnoredLinesFilter($data);
        $this->initializeFilesThatAreSeenTheFirstTime($data);

        if (!$append) {
            return;
        }

        if ($id != 'UNCOVERED_FILES_FROM_WHITELIST') {
            $this->applyCoversAnnotationFilter(
                $data,
                $linesToBeCovered,
                $linesToBeUsed
            );
        }

        if (empty($data)) {
            return;
        }

        $size   = 'unknown';
        $status = null;

        if ($id instanceof \PHPUnit_Framework_TestCase) {
            $_size = $id->getSize();

            if ($_size == \PHPUnit_Util_Test::SMALL) {
                $size = 'small';
            } elseif ($_size == \PHPUnit_Util_Test::MEDIUM) {
                $size = 'medium';
            } elseif ($_size == \PHPUnit_Util_Test::LARGE) {
                $size = 'large';
            }

            $status = $id->getStatus();
            $id     = get_class($id) . '::' . $id->getName();
        } elseif ($id instanceof \PHPUnit_Extensions_PhptTestCase) {
            $size = 'large';
            $id   = $id->getName();
        }

        $this->tests[$id] = ['size' => $size, 'status' => $status];

        foreach ($data as $file => $lines) {
            if (!$this->filter->isFile($file)) {
                continue;
            }

            foreach ($lines as $k => $v) {
                if ($v == Driver::LINE_EXECUTED) {
                    if (empty($this->data[$file][$k]) || !in_array($id, $this->data[$file][$k])) {
                        $this->data[$file][$k][] = $id;
                    }
                }
            }
        }
    }

    /**
     * Merges the data from another instance.
     *
     * @param CodeCoverage $that
     */
    public function merge(CodeCoverage $that)
    {
        $this->filter->setWhitelistedFiles(
            array_merge($this->filter->getWhitelistedFiles(), $that->filter()->getWhitelistedFiles())
        );

        foreach ($that->data as $file => $lines) {
            if (!isset($this->data[$file])) {
                if (!$this->filter->isFiltered($file)) {
                    $this->data[$file] = $lines;
                }

                continue;
            }

            foreach ($lines as $line => $data) {
                if ($data !== null) {
                    if (!isset($this->data[$file][$line])) {
                        $this->data[$file][$line] = $data;
                    } else {
                        $this->data[$file][$line] = array_unique(
                            array_merge($this->data[$file][$line], $data)
                        );
                    }
                }
            }
        }

        $this->tests = array_merge($this->tests, $that->getTests());
    }

    /**
     * @param bool $flag
     *
     * @throws InvalidArgumentException
     */
    public function setCacheTokens($flag)
    {
        if (!is_bool($flag)) {
            throw InvalidArgumentException::create(
                1,
                'boolean'
            );
        }

        $this->cacheTokens = $flag;
    }

    /**
     * @return bool
     */
    public function getCacheTokens()
    {
        return $this->cacheTokens;
    }

    /**
     * @param bool $flag
     *
     * @throws InvalidArgumentException
     */
    public function setCheckForUnintentionallyCoveredCode($flag)
    {
        if (!is_bool($flag)) {
            throw InvalidArgumentException::create(
                1,
                'boolean'
            );
        }

        $this->checkForUnintentionallyCoveredCode = $flag;
    }

    /**
     * @param bool $flag
     *
     * @throws InvalidArgumentException
     */
    public function setForceCoversAnnotation($flag)
    {
        if (!is_bool($flag)) {
            throw InvalidArgumentException::create(
                1,
                'boolean'
            );
        }

        $this->forceCoversAnnotation = $flag;
    }

    /**
     * @param bool $flag
     *
     * @throws InvalidArgumentException
     */
    public function setCheckForMissingCoversAnnotation($flag)
    {
        if (!is_bool($flag)) {
            throw InvalidArgumentException::create(
                1,
                'boolean'
            );
        }

        $this->checkForMissingCoversAnnotation = $flag;
    }

    /**
     * @param bool $flag
     *
     * @throws InvalidArgumentException
     */
    public function setCheckForUnexecutedCoveredCode($flag)
    {
        if (!is_bool($flag)) {
            throw InvalidArgumentException::create(
                1,
                'boolean'
            );
        }

        $this->checkForUnexecutedCoveredCode = $flag;
    }

    /**
     * @deprecated
     *
     * @param bool $flag
     *
     * @throws InvalidArgumentException
     */
    public function setMapTestClassNameToCoveredClassName($flag)
    {
    }

    /**
     * @param bool $flag
     *
     * @throws InvalidArgumentException
     */
    public function setAddUncoveredFilesFromWhitelist($flag)
    {
        if (!is_bool($flag)) {
            throw InvalidArgumentException::create(
                1,
                'boolean'
            );
        }

        $this->addUncoveredFilesFromWhitelist = $flag;
    }

    /**
     * @param bool $flag
     *
     * @throws InvalidArgumentException
     */
    public function setProcessUncoveredFilesFromWhitelist($flag)
    {
        if (!is_bool($flag)) {
            throw InvalidArgumentException::create(
                1,
                'boolean'
            );
        }

        $this->processUncoveredFilesFromWhitelist = $flag;
    }

    /**
     * @param bool $flag
     *
     * @throws InvalidArgumentException
     */
    public function setDisableIgnoredLines($flag)
    {
        if (!is_bool($flag)) {
            throw InvalidArgumentException::create(
                1,
                'boolean'
            );
        }

        $this->disableIgnoredLines = $flag;
    }

    /**
     * @param bool $flag
     *
     * @throws InvalidArgumentException
     */
    public function setIgnoreDeprecatedCode($flag)
    {
        if (!is_bool($flag)) {
            throw InvalidArgumentException::create(
                1,
                'boolean'
            );
        }

        $this->ignoreDeprecatedCode = $flag;
    }

    /**
     * @param array $whitelist
     */
    public function setUnintentionallyCoveredSubclassesWhitelist(array $whitelist)
    {
        $this->unintentionallyCoveredSubclassesWhitelist = $whitelist;
    }

    /**
     * Applies the @covers annotation filtering.
     *
     * @param array $data
     * @param mixed $linesToBeCovered
     * @param array $linesToBeUsed
     *
     * @throws MissingCoversAnnotationException
     * @throws UnintentionallyCoveredCodeException
     */
    private function applyCoversAnnotationFilter(array &$data, $linesToBeCovered, array $linesToBeUsed)
    {
        if ($linesToBeCovered === false ||
            ($this->forceCoversAnnotation && empty($linesToBeCovered))) {
            if ($this->checkForMissingCoversAnnotation) {
                throw new MissingCoversAnnotationException;
            }

            $data = [];

            return;
        }

        if (empty($linesToBeCovered)) {
            return;
        }

        if ($this->checkForUnintentionallyCoveredCode &&
            (!$this->currentId instanceof \PHPUnit_Framework_TestCase ||
            (!$this->currentId->isMedium() && !$this->currentId->isLarge()))) {
            $this->performUnintentionallyCoveredCodeCheck(
                $data,
                $linesToBeCovered,
                $linesToBeUsed
            );
        }

        if ($this->checkForUnexecutedCoveredCode) {
            $this->performUnexecutedCoveredCodeCheck($data, $linesToBeCovered, $linesToBeUsed);
        }

        $data = array_intersect_key($data, $linesToBeCovered);

        foreach (array_keys($data) as $filename) {
            $_linesToBeCovered = array_flip($linesToBeCovered[$filename]);

            $data[$filename] = array_intersect_key(
                $data[$filename],
                $_linesToBeCovered
            );
        }
    }

    /**
     * Applies the whitelist filtering.
     *
     * @param array $data
     */
    private function applyListsFilter(array &$data)
    {
        foreach (array_keys($data) as $filename) {
            if ($this->filter->isFiltered($filename)) {
                unset($data[$filename]);
            }
        }
    }

    /**
     * Applies the "ignored lines" filtering.
     *
     * @param array $data
     */
    private function applyIgnoredLinesFilter(array &$data)
    {
        foreach (array_keys($data) as $filename) {
            if (!$this->filter->isFile($filename)) {
                continue;
            }

            foreach ($this->getLinesToBeIgnored($filename) as $line) {
                unset($data[$filename][$line]);
            }
        }
    }

    /**
     * @param array $data
     */
    private function initializeFilesThatAreSeenTheFirstTime(array $data)
    {
        foreach ($data as $file => $lines) {
            if ($this->filter->isFile($file) && !isset($this->data[$file])) {
                $this->data[$file] = [];

                foreach ($lines as $k => $v) {
                    $this->data[$file][$k] = $v == -2 ? null : [];
                }
            }
        }
    }

    /**
     * Processes whitelisted files that are not covered.
     */
    private function addUncoveredFilesFromWhitelist()
    {
        $data           = [];
        $uncoveredFiles = array_diff(
            $this->filter->getWhitelist(),
            array_keys($this->data)
        );

        foreach ($uncoveredFiles as $uncoveredFile) {
            if (!file_exists($uncoveredFile)) {
                continue;
            }

            if (!$this->processUncoveredFilesFromWhitelist) {
                $data[$uncoveredFile] = [];

                $lines = count(file($uncoveredFile));

                for ($i = 1; $i <= $lines; $i++) {
                    $data[$uncoveredFile][$i] = Driver::LINE_NOT_EXECUTED;
                }
            }
        }

        $this->append($data, 'UNCOVERED_FILES_FROM_WHITELIST');
    }

    /**
     * Returns the lines of a source file that should be ignored.
     *
     * @param string $filename
     *
     * @return array
     *
     * @throws InvalidArgumentException
     */
    private function getLinesToBeIgnored($filename)
    {
        if (!is_string($filename)) {
            throw InvalidArgumentException::create(
                1,
                'string'
            );
        }

        if (!isset($this->ignoredLines[$filename])) {
            $this->ignoredLines[$filename] = [];

            if ($this->disableIgnoredLines) {
                return $this->ignoredLines[$filename];
            }

            $ignore   = false;
            $stop     = false;
            $lines    = file($filename);
            $numLines = count($lines);

            foreach ($lines as $index => $line) {
                if (!trim($line)) {
                    $this->ignoredLines[$filename][] = $index + 1;
                }
            }

            if ($this->cacheTokens) {
                $tokens = \PHP_Token_Stream_CachingFactory::get($filename);
            } else {
                $tokens = new \PHP_Token_Stream($filename);
            }

            $classes = array_merge($tokens->getClasses(), $tokens->getTraits());
            $tokens  = $tokens->tokens();

            foreach ($tokens as $token) {
                switch (get_class($token)) {
                    case 'PHP_Token_COMMENT':
                    case 'PHP_Token_DOC_COMMENT':
                        $_token = trim($token);
                        $_line  = trim($lines[$token->getLine() - 1]);

                        if ($_token == '// @codeCoverageIgnore' ||
                            $_token == '//@codeCoverageIgnore') {
                            $ignore = true;
                            $stop   = true;
                        } elseif ($_token == '// @codeCoverageIgnoreStart' ||
                            $_token == '//@codeCoverageIgnoreStart') {
                            $ignore = true;
                        } elseif ($_token == '// @codeCoverageIgnoreEnd' ||
                            $_token == '//@codeCoverageIgnoreEnd') {
                            $stop = true;
                        }

                        if (!$ignore) {
                            $start = $token->getLine();
                            $end   = $start + substr_count($token, "\n");

                            // Do not ignore the first line when there is a token
                            // before the comment
                            if (0 !== strpos($_token, $_line)) {
                                $start++;
                            }

                            for ($i = $start; $i < $end; $i++) {
                                $this->ignoredLines[$filename][] = $i;
                            }

                            // A DOC_COMMENT token or a COMMENT token starting with "/*"
                            // does not contain the final \n character in its text
                            if (isset($lines[$i-1]) && 0 === strpos($_token, '/*') && '*/' === substr(trim($lines[$i-1]), -2)) {
                                $this->ignoredLines[$filename][] = $i;
                            }
                        }
                        break;

                    case 'PHP_Token_INTERFACE':
                    case 'PHP_Token_TRAIT':
                    case 'PHP_Token_CLASS':
                    case 'PHP_Token_FUNCTION':
                        /* @var \PHP_Token_Interface $token */

                        $docblock = $token->getDocblock();

                        $this->ignoredLines[$filename][] = $token->getLine();

                        if (strpos($docblock, '@codeCoverageIgnore') || ($this->ignoreDeprecatedCode && strpos($docblock, '@deprecated'))) {
                            $endLine = $token->getEndLine();

                            for ($i = $token->getLine(); $i <= $endLine; $i++) {
                                $this->ignoredLines[$filename][] = $i;
                            }
                        } elseif ($token instanceof \PHP_Token_INTERFACE ||
                            $token instanceof \PHP_Token_TRAIT ||
                            $token instanceof \PHP_Token_CLASS) {
                            if (empty($classes[$token->getName()]['methods'])) {
                                for ($i = $token->getLine();
                                     $i <= $token->getEndLine();
                                     $i++) {
                                    $this->ignoredLines[$filename][] = $i;
                                }
                            } else {
                                $firstMethod = array_shift(
                                    $classes[$token->getName()]['methods']
                                );

                                do {
                                    $lastMethod = array_pop(
                                        $classes[$token->getName()]['methods']
                                    );
                                } while ($lastMethod !== null &&
                                    substr($lastMethod['signature'], 0, 18) == 'anonymous function');

                                if ($lastMethod === null) {
                                    $lastMethod = $firstMethod;
                                }

                                for ($i = $token->getLine();
                                     $i < $firstMethod['startLine'];
                                     $i++) {
                                    $this->ignoredLines[$filename][] = $i;
                                }

                                for ($i = $token->getEndLine();
                                     $i > $lastMethod['endLine'];
                                     $i--) {
                                    $this->ignoredLines[$filename][] = $i;
                                }
                            }
                        }
                        break;

                    case 'PHP_Token_NAMESPACE':
                        $this->ignoredLines[$filename][] = $token->getEndLine();

                    // Intentional fallthrough
                    case 'PHP_Token_DECLARE':
                    case 'PHP_Token_OPEN_TAG':
                    case 'PHP_Token_CLOSE_TAG':
                    case 'PHP_Token_USE':
                        $this->ignoredLines[$filename][] = $token->getLine();
                        break;
                }

                if ($ignore) {
                    $this->ignoredLines[$filename][] = $token->getLine();

                    if ($stop) {
                        $ignore = false;
                        $stop   = false;
                    }
                }
            }

            $this->ignoredLines[$filename][] = $numLines + 1;

            $this->ignoredLines[$filename] = array_unique(
                $this->ignoredLines[$filename]
            );

            sort($this->ignoredLines[$filename]);
        }

        return $this->ignoredLines[$filename];
    }

    /**
     * @param array $data
     * @param array $linesToBeCovered
     * @param array $linesToBeUsed
     *
     * @throws UnintentionallyCoveredCodeException
     */
    private function performUnintentionallyCoveredCodeCheck(array &$data, array $linesToBeCovered, array $linesToBeUsed)
    {
        $allowedLines = $this->getAllowedLines(
            $linesToBeCovered,
            $linesToBeUsed
        );

        $unintentionallyCoveredUnits = [];

        foreach ($data as $file => $_data) {
            foreach ($_data as $line => $flag) {
                if ($flag == 1 && !isset($allowedLines[$file][$line])) {
                    $unintentionallyCoveredUnits[] = $this->wizard->lookup($file, $line);
                }
            }
        }

        $unintentionallyCoveredUnits = $this->processUnintentionallyCoveredUnits($unintentionallyCoveredUnits);

        if (!empty($unintentionallyCoveredUnits)) {
            throw new UnintentionallyCoveredCodeException(
                $unintentionallyCoveredUnits
            );
        }
    }

    /**
     * @param array $data
     * @param array $linesToBeCovered
     * @param array $linesToBeUsed
     *
     * @throws CoveredCodeNotExecutedException
     */
    private function performUnexecutedCoveredCodeCheck(array &$data, array $linesToBeCovered, array $linesToBeUsed)
    {
        $expectedLines = $this->getAllowedLines(
            $linesToBeCovered,
            $linesToBeUsed
        );

        foreach ($data as $file => $_data) {
            foreach (array_keys($_data) as $line) {
                if (!isset($expectedLines[$file][$line])) {
                    continue;
                }

                unset($expectedLines[$file][$line]);
            }
        }

        $message = '';

        foreach ($expectedLines as $file => $lines) {
            if (empty($lines)) {
                continue;
            }

            foreach (array_keys($lines) as $line) {
                $message .= sprintf('- %s:%d' . PHP_EOL, $file, $line);
            }
        }

        if (!empty($message)) {
            throw new CoveredCodeNotExecutedException($message);
        }
    }

    /**
     * @param array $linesToBeCovered
     * @param array $linesToBeUsed
     *
     * @return array
     */
    private function getAllowedLines(array $linesToBeCovered, array $linesToBeUsed)
    {
        $allowedLines = [];

        foreach (array_keys($linesToBeCovered) as $file) {
            if (!isset($allowedLines[$file])) {
                $allowedLines[$file] = [];
            }

            $allowedLines[$file] = array_merge(
                $allowedLines[$file],
                $linesToBeCovered[$file]
            );
        }

        foreach (array_keys($linesToBeUsed) as $file) {
            if (!isset($allowedLines[$file])) {
                $allowedLines[$file] = [];
            }

            $allowedLines[$file] = array_merge(
                $allowedLines[$file],
                $linesToBeUsed[$file]
            );
        }

        foreach (array_keys($allowedLines) as $file) {
            $allowedLines[$file] = array_flip(
                array_unique($allowedLines[$file])
            );
        }

        return $allowedLines;
    }

    /**
     * @return Driver
     *
     * @throws RuntimeException
     */
    private function selectDriver()
    {
        $runtime = new Runtime;

        if (!$runtime->canCollectCodeCoverage()) {
            throw new RuntimeException('No code coverage driver available');
        }

        if ($runtime->isHHVM()) {
            return new HHVM;
        } elseif ($runtime->isPHPDBG()) {
            return new PHPDBG;
        } else {
            return new Xdebug;
        }
    }

    /**
     * @param array $unintentionallyCoveredUnits
     *
     * @return array
     */
    private function processUnintentionallyCoveredUnits(array $unintentionallyCoveredUnits)
    {
        $unintentionallyCoveredUnits = array_unique($unintentionallyCoveredUnits);
        sort($unintentionallyCoveredUnits);

        foreach (array_keys($unintentionallyCoveredUnits) as $k => $v) {
            $unit = explode('::', $unintentionallyCoveredUnits[$k]);

            if (count($unit) != 2) {
                continue;
            }

            $class = new \ReflectionClass($unit[0]);

            foreach ($this->unintentionallyCoveredSubclassesWhitelist as $whitelisted) {
                if ($class->isSubclassOf($whitelisted)) {
                    unset($unintentionallyCoveredUnits[$k]);
                    break;
                }
            }
        }

        return array_values($unintentionallyCoveredUnits);
    }

    /**
     * If we are processing uncovered files from whitelist,
     * we can initialize the data before we start to speed up the tests
     */
    protected function initializeData()
    {
        $this->isInitialized = true;

        if ($this->processUncoveredFilesFromWhitelist) {
            $this->shouldCheckForDeadAndUnused = false;

            $this->driver->start(true);

            foreach ($this->filter->getWhitelist() as $file) {
                if ($this->filter->isFile($file)) {
                    include_once($file);
                }
            }

            $data     = [];
            $coverage = $this->driver->stop();

            foreach ($coverage as $file => $fileCoverage) {
                if ($this->filter->isFiltered($file)) {
                    continue;
                }

                foreach (array_keys($fileCoverage) as $key) {
                    if ($fileCoverage[$key] == Driver::LINE_EXECUTED) {
                        $fileCoverage[$key] = Driver::LINE_NOT_EXECUTED;
                    }
                }

                $data[$file] = $fileCoverage;
            }

            $this->append($data, 'UNCOVERED_FILES_FROM_WHITELIST');
        }
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage\Driver;

/**
 * Driver for HHVM's code coverage functionality.
 *
 * @codeCoverageIgnore
 */
class HHVM extends Xdebug
{
    /**
     * Start collection of code coverage information.
     *
     * @param bool $determineUnusedAndDead
     */
    public function start($determineUnusedAndDead = true)
    {
        xdebug_start_code_coverage();
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage\Driver;

/**
 * Interface for code coverage drivers.
 */
interface Driver
{
    /**
     * @var int
     *
     * @see http://xdebug.org/docs/code_coverage
     */
    const LINE_EXECUTED = 1;

    /**
     * @var int
     *
     * @see http://xdebug.org/docs/code_coverage
     */
    const LINE_NOT_EXECUTED = -1;

    /**
     * @var int
     *
     * @see http://xdebug.org/docs/code_coverage
     */
    const LINE_NOT_EXECUTABLE = -2;

    /**
     * Start collection of code coverage information.
     *
     * @param bool $determineUnusedAndDead
     */
    public function start($determineUnusedAndDead = true);

    /**
     * Stop collection of code coverage information.
     *
     * @return array
     */
    public function stop();
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage\Driver;

use SebastianBergmann\CodeCoverage\RuntimeException;

/**
 * Driver for PHPDBG's code coverage functionality.
 *
 * @codeCoverageIgnore
 */
class PHPDBG implements Driver
{
    /**
     * Constructor.
     */
    public function __construct()
    {
        if (PHP_SAPI !== 'phpdbg') {
            throw new RuntimeException(
                'This driver requires the PHPDBG SAPI'
            );
        }

        if (!function_exists('phpdbg_start_oplog')) {
            throw new RuntimeException(
                'This build of PHPDBG does not support code coverage'
            );
        }
    }

    /**
     * Start collection of code coverage information.
     *
     * @param bool $determineUnusedAndDead
     */
    public function start($determineUnusedAndDead = true)
    {
        phpdbg_start_oplog();
    }

    /**
     * Stop collection of code coverage information.
     *
     * @return array
     */
    public function stop()
    {
        static $fetchedLines = [];

        $dbgData = phpdbg_end_oplog();

        if ($fetchedLines == []) {
            $sourceLines = phpdbg_get_executable();
        } else {
            $newFiles = array_diff(
                get_included_files(),
                array_keys($fetchedLines)
            );

            if ($newFiles) {
                $sourceLines = phpdbg_get_executable(
                    ['files' => $newFiles]
                );
            } else {
                $sourceLines = [];
            }
        }

        foreach ($sourceLines as $file => $lines) {
            foreach ($lines as $lineNo => $numExecuted) {
                $sourceLines[$file][$lineNo] = self::LINE_NOT_EXECUTED;
            }
        }

        $fetchedLines = array_merge($fetchedLines, $sourceLines);

        return $this->detectExecutedLines($fetchedLines, $dbgData);
    }

    /**
     * Convert phpdbg based data into the format CodeCoverage expects
     *
     * @param array $sourceLines
     * @param array $dbgData
     *
     * @return array
     */
    private function detectExecutedLines(array $sourceLines, array $dbgData)
    {
        foreach ($dbgData as $file => $coveredLines) {
            foreach ($coveredLines as $lineNo => $numExecuted) {
                // phpdbg also reports $lineNo=0 when e.g. exceptions get thrown.
                // make sure we only mark lines executed which are actually executable.
                if (isset($sourceLines[$file][$lineNo])) {
                    $sourceLines[$file][$lineNo] = self::LINE_EXECUTED;
                }
            }
        }

        return $sourceLines;
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage\Driver;

use SebastianBergmann\CodeCoverage\RuntimeException;

/**
 * Driver for Xdebug's code coverage functionality.
 *
 * @codeCoverageIgnore
 */
class Xdebug implements Driver
{
    /**
     * Cache the number of lines for each file
     *
     * @var array
     */
    private $cacheNumLines = [];

    /**
     * Constructor.
     */
    public function __construct()
    {
        if (!extension_loaded('xdebug')) {
            throw new RuntimeException('This driver requires Xdebug');
        }

        if (version_compare(phpversion('xdebug'), '2.2.1', '>=') &&
            !ini_get('xdebug.coverage_enable')) {
            throw new RuntimeException(
                'xdebug.coverage_enable=On has to be set in php.ini'
            );
        }
    }

    /**
     * Start collection of code coverage information.
     *
     * @param bool $determineUnusedAndDead
     */
    public function start($determineUnusedAndDead = true)
    {
        if ($determineUnusedAndDead) {
            xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);
        } else {
            xdebug_start_code_coverage();
        }
    }

    /**
     * Stop collection of code coverage information.
     *
     * @return array
     */
    public function stop()
    {
        $data = xdebug_get_code_coverage();
        xdebug_stop_code_coverage();

        return $this->cleanup($data);
    }

    /**
     * @param array $data
     *
     * @return array
     */
    private function cleanup(array $data)
    {
        foreach (array_keys($data) as $file) {
            unset($data[$file][0]);

            if (strpos($file, 'xdebug://debug-eval') !== 0 && file_exists($file)) {
                $numLines = $this->getNumberOfLinesInFile($file);

                foreach (array_keys($data[$file]) as $line) {
                    if ($line > $numLines) {
                        unset($data[$file][$line]);
                    }
                }
            }
        }

        return $data;
    }

    /**
     * @param string $file
     *
     * @return int
     */
    private function getNumberOfLinesInFile($file)
    {
        if (!isset($this->cacheNumLines[$file])) {
            $buffer = file_get_contents($file);
            $lines  = substr_count($buffer, "\n");

            if (substr($buffer, -1) !== "\n") {
                $lines++;
            }

            $this->cacheNumLines[$file] = $lines;
        }

        return $this->cacheNumLines[$file];
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage;

/**
 * Filter for whitelisting of code coverage information.
 */
class Filter
{
    /**
     * Source files that are whitelisted.
     *
     * @var array
     */
    private $whitelistedFiles = [];

    /**
     * Adds a directory to the whitelist (recursively).
     *
     * @param string $directory
     * @param string $suffix
     * @param string $prefix
     */
    public function addDirectoryToWhitelist($directory, $suffix = '.php', $prefix = '')
    {
        $facade = new \File_Iterator_Facade;
        $files  = $facade->getFilesAsArray($directory, $suffix, $prefix);

        foreach ($files as $file) {
            $this->addFileToWhitelist($file);
        }
    }

    /**
     * Adds a file to the whitelist.
     *
     * @param string $filename
     */
    public function addFileToWhitelist($filename)
    {
        $this->whitelistedFiles[realpath($filename)] = true;
    }

    /**
     * Adds files to the whitelist.
     *
     * @param array $files
     */
    public function addFilesToWhitelist(array $files)
    {
        foreach ($files as $file) {
            $this->addFileToWhitelist($file);
        }
    }

    /**
     * Removes a directory from the whitelist (recursively).
     *
     * @param string $directory
     * @param string $suffix
     * @param string $prefix
     */
    public function removeDirectoryFromWhitelist($directory, $suffix = '.php', $prefix = '')
    {
        $facade = new \File_Iterator_Facade;
        $files  = $facade->getFilesAsArray($directory, $suffix, $prefix);

        foreach ($files as $file) {
            $this->removeFileFromWhitelist($file);
        }
    }

    /**
     * Removes a file from the whitelist.
     *
     * @param string $filename
     */
    public function removeFileFromWhitelist($filename)
    {
        $filename = realpath($filename);

        unset($this->whitelistedFiles[$filename]);
    }

    /**
     * Checks whether a filename is a real filename.
     *
     * @param string $filename
     *
     * @return bool
     */
    public function isFile($filename)
    {
        if ($filename == '-' ||
            strpos($filename, 'vfs://') === 0 ||
            strpos($filename, 'xdebug://debug-eval') !== false ||
            strpos($filename, 'eval()\'d code') !== false ||
            strpos($filename, 'runtime-created function') !== false ||
            strpos($filename, 'runkit created function') !== false ||
            strpos($filename, 'assert code') !== false ||
            strpos($filename, 'regexp code') !== false) {
            return false;
        }

        return file_exists($filename);
    }

    /**
     * Checks whether or not a file is filtered.
     *
     * @param string $filename
     *
     * @return bool
     */
    public function isFiltered($filename)
    {
        if (!$this->isFile($filename)) {
            return true;
        }

        $filename = realpath($filename);

        return !isset($this->whitelistedFiles[$filename]);
    }

    /**
     * Returns the list of whitelisted files.
     *
     * @return array
     */
    public function getWhitelist()
    {
        return array_keys($this->whitelistedFiles);
    }

    /**
     * Returns whether this filter has a whitelist.
     *
     * @return bool
     */
    public function hasWhitelist()
    {
        return !empty($this->whitelistedFiles);
    }

    /**
     * Returns the whitelisted files.
     *
     * @return array
     */
    public function getWhitelistedFiles()
    {
        return $this->whitelistedFiles;
    }

    /**
     * Sets the whitelisted files.
     *
     * @param array $whitelistedFiles
     */
    public function setWhitelistedFiles($whitelistedFiles)
    {
        $this->whitelistedFiles = $whitelistedFiles;
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage\Report;

use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\Node\File;

/**
 * Generates a Clover XML logfile from a code coverage object.
 */
class Clover
{
    /**
     * @param CodeCoverage $coverage
     * @param string       $target
     * @param string       $name
     *
     * @return string
     */
    public function process(CodeCoverage $coverage, $target = null, $name = null)
    {
        $xmlDocument               = new \DOMDocument('1.0', 'UTF-8');
        $xmlDocument->formatOutput = true;

        $xmlCoverage = $xmlDocument->createElement('coverage');
        $xmlCoverage->setAttribute('generated', (int) $_SERVER['REQUEST_TIME']);
        $xmlDocument->appendChild($xmlCoverage);

        $xmlProject = $xmlDocument->createElement('project');
        $xmlProject->setAttribute('timestamp', (int) $_SERVER['REQUEST_TIME']);

        if (is_string($name)) {
            $xmlProject->setAttribute('name', $name);
        }

        $xmlCoverage->appendChild($xmlProject);

        $packages = [];
        $report   = $coverage->getReport();
        unset($coverage);

        foreach ($report as $item) {
            if (!$item instanceof File) {
                continue;
            }

            /* @var File $item */

            $xmlFile = $xmlDocument->createElement('file');
            $xmlFile->setAttribute('name', $item->getPath());

            $classes   = $item->getClassesAndTraits();
            $coverage  = $item->getCoverageData();
            $lines     = [];
            $namespace = 'global';

            foreach ($classes as $className => $class) {
                $classStatements        = 0;
                $coveredClassStatements = 0;
                $coveredMethods         = 0;
                $classMethods           = 0;

                foreach ($class['methods'] as $methodName => $method) {
                    if ($method['executableLines']  == 0) {
                        continue;
                    }

                    $classMethods++;
                    $classStatements        += $method['executableLines'];
                    $coveredClassStatements += $method['executedLines'];

                    if ($method['coverage'] == 100) {
                        $coveredMethods++;
                    }

                    $methodCount = 0;

                    foreach (range($method['startLine'], $method['endLine']) as $line) {
                        if (isset($coverage[$line]) && ($coverage[$line] !== null)) {
                            $methodCount = max($methodCount, count($coverage[$line]));
                        }
                    }

                    $lines[$method['startLine']] = [
                        'ccn'         => $method['ccn'],
                        'count'       => $methodCount,
                        'crap'        => $method['crap'],
                        'type'        => 'method',
                        'visibility'  => $method['visibility'],
                        'name'        => $methodName
                    ];
                }

                if (!empty($class['package']['namespace'])) {
                    $namespace = $class['package']['namespace'];
                }

                $xmlClass = $xmlDocument->createElement('class');
                $xmlClass->setAttribute('name', $className);
                $xmlClass->setAttribute('namespace', $namespace);

                if (!empty($class['package']['fullPackage'])) {
                    $xmlClass->setAttribute(
                        'fullPackage',
                        $class['package']['fullPackage']
                    );
                }

                if (!empty($class['package']['category'])) {
                    $xmlClass->setAttribute(
                        'category',
                        $class['package']['category']
                    );
                }

                if (!empty($class['package']['package'])) {
                    $xmlClass->setAttribute(
                        'package',
                        $class['package']['package']
                    );
                }

                if (!empty($class['package']['subpackage'])) {
                    $xmlClass->setAttribute(
                        'subpackage',
                        $class['package']['subpackage']
                    );
                }

                $xmlFile->appendChild($xmlClass);

                $xmlMetrics = $xmlDocument->createElement('metrics');
                $xmlMetrics->setAttribute('complexity', $class['ccn']);
                $xmlMetrics->setAttribute('methods', $classMethods);
                $xmlMetrics->setAttribute('coveredmethods', $coveredMethods);
                $xmlMetrics->setAttribute('conditionals', 0);
                $xmlMetrics->setAttribute('coveredconditionals', 0);
                $xmlMetrics->setAttribute('statements', $classStatements);
                $xmlMetrics->setAttribute('coveredstatements', $coveredClassStatements);
                $xmlMetrics->setAttribute('elements', $classMethods + $classStatements /* + conditionals */);
                $xmlMetrics->setAttribute('coveredelements', $coveredMethods + $coveredClassStatements /* + coveredconditionals */);
                $xmlClass->appendChild($xmlMetrics);
            }

            foreach ($coverage as $line => $data) {
                if ($data === null || isset($lines[$line])) {
                    continue;
                }

                $lines[$line] = [
                    'count' => count($data), 'type' => 'stmt'
                ];
            }

            ksort($lines);

            foreach ($lines as $line => $data) {
                $xmlLine = $xmlDocument->createElement('line');
                $xmlLine->setAttribute('num', $line);
                $xmlLine->setAttribute('type', $data['type']);

                if (isset($data['name'])) {
                    $xmlLine->setAttribute('name', $data['name']);
                }

                if (isset($data['visibility'])) {
                    $xmlLine->setAttribute('visibility', $data['visibility']);
                }

                if (isset($data['ccn'])) {
                    $xmlLine->setAttribute('complexity', $data['ccn']);
                }

                if (isset($data['crap'])) {
                    $xmlLine->setAttribute('crap', $data['crap']);
                }

                $xmlLine->setAttribute('count', $data['count']);
                $xmlFile->appendChild($xmlLine);
            }

            $linesOfCode = $item->getLinesOfCode();

            $xmlMetrics = $xmlDocument->createElement('metrics');
            $xmlMetrics->setAttribute('loc', $linesOfCode['loc']);
            $xmlMetrics->setAttribute('ncloc', $linesOfCode['ncloc']);
            $xmlMetrics->setAttribute('classes', $item->getNumClassesAndTraits());
            $xmlMetrics->setAttribute('methods', $item->getNumMethods());
            $xmlMetrics->setAttribute('coveredmethods', $item->getNumTestedMethods());
            $xmlMetrics->setAttribute('conditionals', 0);
            $xmlMetrics->setAttribute('coveredconditionals', 0);
            $xmlMetrics->setAttribute('statements', $item->getNumExecutableLines());
            $xmlMetrics->setAttribute('coveredstatements', $item->getNumExecutedLines());
            $xmlMetrics->setAttribute('elements', $item->getNumMethods() + $item->getNumExecutableLines() /* + conditionals */);
            $xmlMetrics->setAttribute('coveredelements', $item->getNumTestedMethods() + $item->getNumExecutedLines() /* + coveredconditionals */);
            $xmlFile->appendChild($xmlMetrics);

            if ($namespace == 'global') {
                $xmlProject->appendChild($xmlFile);
            } else {
                if (!isset($packages[$namespace])) {
                    $packages[$namespace] = $xmlDocument->createElement(
                        'package'
                    );

                    $packages[$namespace]->setAttribute('name', $namespace);
                    $xmlProject->appendChild($packages[$namespace]);
                }

                $packages[$namespace]->appendChild($xmlFile);
            }
        }

        $linesOfCode = $report->getLinesOfCode();

        $xmlMetrics = $xmlDocument->createElement('metrics');
        $xmlMetrics->setAttribute('files', count($report));
        $xmlMetrics->setAttribute('loc', $linesOfCode['loc']);
        $xmlMetrics->setAttribute('ncloc', $linesOfCode['ncloc']);
        $xmlMetrics->setAttribute('classes', $report->getNumClassesAndTraits());
        $xmlMetrics->setAttribute('methods', $report->getNumMethods());
        $xmlMetrics->setAttribute('coveredmethods', $report->getNumTestedMethods());
        $xmlMetrics->setAttribute('conditionals', 0);
        $xmlMetrics->setAttribute('coveredconditionals', 0);
        $xmlMetrics->setAttribute('statements', $report->getNumExecutableLines());
        $xmlMetrics->setAttribute('coveredstatements', $report->getNumExecutedLines());
        $xmlMetrics->setAttribute('elements', $report->getNumMethods() + $report->getNumExecutableLines() /* + conditionals */);
        $xmlMetrics->setAttribute('coveredelements', $report->getNumTestedMethods() + $report->getNumExecutedLines() /* + coveredconditionals */);
        $xmlProject->appendChild($xmlMetrics);

        $buffer = $xmlDocument->saveXML();

        if ($target !== null) {
            if (!is_dir(dirname($target))) {
                mkdir(dirname($target), 0777, true);
            }

            file_put_contents($target, $buffer);
        }

        return $buffer;
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage\Report\Html;

use SebastianBergmann\CodeCoverage\Node\AbstractNode;
use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode;

/**
 * Renders the dashboard for a directory node.
 */
class Dashboard extends Renderer
{
    /**
     * @param DirectoryNode $node
     * @param string        $file
     */
    public function render(DirectoryNode $node, $file)
    {
        $classes  = $node->getClassesAndTraits();
        $template = new \Text_Template(
            $this->templatePath . 'dashboard.html',
            '{{',
            '}}'
        );

        $this->setCommonTemplateVariables($template, $node);

        $baseLink             = $node->getId() . '/';
        $complexity           = $this->complexity($classes, $baseLink);
        $coverageDistribution = $this->coverageDistribution($classes);
        $insufficientCoverage = $this->insufficientCoverage($classes, $baseLink);
        $projectRisks         = $this->projectRisks($classes, $baseLink);

        $template->setVar(
            [
                'insufficient_coverage_classes' => $insufficientCoverage['class'],
                'insufficient_coverage_methods' => $insufficientCoverage['method'],
                'project_risks_classes'         => $projectRisks['class'],
                'project_risks_methods'         => $projectRisks['method'],
                'complexity_class'              => $complexity['class'],
                'complexity_method'             => $complexity['method'],
                'class_coverage_distribution'   => $coverageDistribution['class'],
                'method_coverage_distribution'  => $coverageDistribution['method']
            ]
        );

        $template->renderTo($file);
    }

    /**
     * Returns the data for the Class/Method Complexity charts.
     *
     * @param array  $classes
     * @param string $baseLink
     *
     * @return array
     */
    protected function complexity(array $classes, $baseLink)
    {
        $result = ['class' => [], 'method' => []];

        foreach ($classes as $className => $class) {
            foreach ($class['methods'] as $methodName => $method) {
                if ($className != '*') {
                    $methodName = $className . '::' . $methodName;
                }

                $result['method'][] = [
                    $method['coverage'],
                    $method['ccn'],
                    sprintf(
                        '<a href="%s">%s</a>',
                        str_replace($baseLink, '', $method['link']),
                        $methodName
                    )
                ];
            }

            $result['class'][] = [
                $class['coverage'],
                $class['ccn'],
                sprintf(
                    '<a href="%s">%s</a>',
                    str_replace($baseLink, '', $class['link']),
                    $className
                )
            ];
        }

        return [
            'class'  => json_encode($result['class']),
            'method' => json_encode($result['method'])
        ];
    }

    /**
     * Returns the data for the Class / Method Coverage Distribution chart.
     *
     * @param array $classes
     *
     * @return array
     */
    protected function coverageDistribution(array $classes)
    {
        $result = [
            'class' => [
                '0%'      => 0,
                '0-10%'   => 0,
                '10-20%'  => 0,
                '20-30%'  => 0,
                '30-40%'  => 0,
                '40-50%'  => 0,
                '50-60%'  => 0,
                '60-70%'  => 0,
                '70-80%'  => 0,
                '80-90%'  => 0,
                '90-100%' => 0,
                '100%'    => 0
            ],
            'method' => [
                '0%'      => 0,
                '0-10%'   => 0,
                '10-20%'  => 0,
                '20-30%'  => 0,
                '30-40%'  => 0,
                '40-50%'  => 0,
                '50-60%'  => 0,
                '60-70%'  => 0,
                '70-80%'  => 0,
                '80-90%'  => 0,
                '90-100%' => 0,
                '100%'    => 0
            ]
        ];

        foreach ($classes as $class) {
            foreach ($class['methods'] as $methodName => $method) {
                if ($method['coverage'] == 0) {
                    $result['method']['0%']++;
                } elseif ($method['coverage'] == 100) {
                    $result['method']['100%']++;
                } else {
                    $key = floor($method['coverage'] / 10) * 10;
                    $key = $key . '-' . ($key + 10) . '%';
                    $result['method'][$key]++;
                }
            }

            if ($class['coverage'] == 0) {
                $result['class']['0%']++;
            } elseif ($class['coverage'] == 100) {
                $result['class']['100%']++;
            } else {
                $key = floor($class['coverage'] / 10) * 10;
                $key = $key . '-' . ($key + 10) . '%';
                $result['class'][$key]++;
            }
        }

        return [
            'class'  => json_encode(array_values($result['class'])),
            'method' => json_encode(array_values($result['method']))
        ];
    }

    /**
     * Returns the classes / methods with insufficient coverage.
     *
     * @param array  $classes
     * @param string $baseLink
     *
     * @return array
     */
    protected function insufficientCoverage(array $classes, $baseLink)
    {
        $leastTestedClasses = [];
        $leastTestedMethods = [];
        $result             = ['class' => '', 'method' => ''];

        foreach ($classes as $className => $class) {
            foreach ($class['methods'] as $methodName => $method) {
                if ($method['coverage'] < $this->highLowerBound) {
                    if ($className != '*') {
                        $key = $className . '::' . $methodName;
                    } else {
                        $key = $methodName;
                    }

                    $leastTestedMethods[$key] = $method['coverage'];
                }
            }

            if ($class['coverage'] < $this->highLowerBound) {
                $leastTestedClasses[$className] = $class['coverage'];
            }
        }

        asort($leastTestedClasses);
        asort($leastTestedMethods);

        foreach ($leastTestedClasses as $className => $coverage) {
            $result['class'] .= sprintf(
                '       <tr><td><a href="%s">%s</a></td><td class="text-right">%d%%</td></tr>' . "\n",
                str_replace($baseLink, '', $classes[$className]['link']),
                $className,
                $coverage
            );
        }

        foreach ($leastTestedMethods as $methodName => $coverage) {
            list($class, $method) = explode('::', $methodName);

            $result['method'] .= sprintf(
                '       <tr><td><a href="%s"><abbr title="%s">%s</abbr></a></td><td class="text-right">%d%%</td></tr>' . "\n",
                str_replace($baseLink, '', $classes[$class]['methods'][$method]['link']),
                $methodName,
                $method,
                $coverage
            );
        }

        return $result;
    }

    /**
     * Returns the project risks according to the CRAP index.
     *
     * @param array  $classes
     * @param string $baseLink
     *
     * @return array
     */
    protected function projectRisks(array $classes, $baseLink)
    {
        $classRisks  = [];
        $methodRisks = [];
        $result      = ['class' => '', 'method' => ''];

        foreach ($classes as $className => $class) {
            foreach ($class['methods'] as $methodName => $method) {
                if ($method['coverage'] < $this->highLowerBound &&
                    $method['ccn'] > 1) {
                    if ($className != '*') {
                        $key = $className . '::' . $methodName;
                    } else {
                        $key = $methodName;
                    }

                    $methodRisks[$key] = $method['crap'];
                }
            }

            if ($class['coverage'] < $this->highLowerBound &&
                $class['ccn'] > count($class['methods'])) {
                $classRisks[$className] = $class['crap'];
            }
        }

        arsort($classRisks);
        arsort($methodRisks);

        foreach ($classRisks as $className => $crap) {
            $result['class'] .= sprintf(
                '       <tr><td><a href="%s">%s</a></td><td class="text-right">%d</td></tr>' . "\n",
                str_replace($baseLink, '', $classes[$className]['link']),
                $className,
                $crap
            );
        }

        foreach ($methodRisks as $methodName => $crap) {
            list($class, $method) = explode('::', $methodName);

            $result['method'] .= sprintf(
                '       <tr><td><a href="%s"><abbr title="%s">%s</abbr></a></td><td class="text-right">%d</td></tr>' . "\n",
                str_replace($baseLink, '', $classes[$class]['methods'][$method]['link']),
                $methodName,
                $method,
                $crap
            );
        }

        return $result;
    }

    protected function getActiveBreadcrumb(AbstractNode $node)
    {
        return sprintf(
            '        <li><a href="index.html">%s</a></li>' . "\n" .
            '        <li class="active">(Dashboard)</li>' . "\n",
            $node->getName()
        );
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage\Report\Html;

use SebastianBergmann\CodeCoverage\Node\File as FileNode;
use SebastianBergmann\CodeCoverage\Util;

/**
 * Renders a file node.
 */
class File extends Renderer
{
    /**
     * @var int
     */
    private $htmlspecialcharsFlags;

    /**
     * Constructor.
     *
     * @param string $templatePath
     * @param string $generator
     * @param string $date
     * @param int    $lowUpperBound
     * @param int    $highLowerBound
     */
    public function __construct($templatePath, $generator, $date, $lowUpperBound, $highLowerBound)
    {
        parent::__construct(
            $templatePath,
            $generator,
            $date,
            $lowUpperBound,
            $highLowerBound
        );

        $this->htmlspecialcharsFlags = ENT_COMPAT;

        $this->htmlspecialcharsFlags = $this->htmlspecialcharsFlags | ENT_HTML401 | ENT_SUBSTITUTE;
    }

    /**
     * @param FileNode $node
     * @param string   $file
     */
    public function render(FileNode $node, $file)
    {
        $template = new \Text_Template($this->templatePath . 'file.html', '{{', '}}');

        $template->setVar(
            [
                'items' => $this->renderItems($node),
                'lines' => $this->renderSource($node)
            ]
        );

        $this->setCommonTemplateVariables($template, $node);

        $template->renderTo($file);
    }

    /**
     * @param FileNode $node
     *
     * @return string
     */
    protected function renderItems(FileNode $node)
    {
        $template = new \Text_Template($this->templatePath . 'file_item.html', '{{', '}}');

        $methodItemTemplate = new \Text_Template(
            $this->templatePath . 'method_item.html',
            '{{',
            '}}'
        );

        $items = $this->renderItemTemplate(
            $template,
            [
                'name'                         => 'Total',
                'numClasses'                   => $node->getNumClassesAndTraits(),
                'numTestedClasses'             => $node->getNumTestedClassesAndTraits(),
                'numMethods'                   => $node->getNumMethods(),
                'numTestedMethods'             => $node->getNumTestedMethods(),
                'linesExecutedPercent'         => $node->getLineExecutedPercent(false),
                'linesExecutedPercentAsString' => $node->getLineExecutedPercent(),
                'numExecutedLines'             => $node->getNumExecutedLines(),
                'numExecutableLines'           => $node->getNumExecutableLines(),
                'testedMethodsPercent'         => $node->getTestedMethodsPercent(false),
                'testedMethodsPercentAsString' => $node->getTestedMethodsPercent(),
                'testedClassesPercent'         => $node->getTestedClassesAndTraitsPercent(false),
                'testedClassesPercentAsString' => $node->getTestedClassesAndTraitsPercent(),
                'crap'                         => '<abbr title="Change Risk Anti-Patterns (CRAP) Index">CRAP</abbr>'
            ]
        );

        $items .= $this->renderFunctionItems(
            $node->getFunctions(),
            $methodItemTemplate
        );

        $items .= $this->renderTraitOrClassItems(
            $node->getTraits(),
            $template,
            $methodItemTemplate
        );

        $items .= $this->renderTraitOrClassItems(
            $node->getClasses(),
            $template,
            $methodItemTemplate
        );

        return $items;
    }

    /**
     * @param array          $items
     * @param \Text_Template $template
     * @param \Text_Template $methodItemTemplate
     *
     * @return string
     */
    protected function renderTraitOrClassItems(array $items, \Text_Template $template, \Text_Template $methodItemTemplate)
    {
        if (empty($items)) {
            return '';
        }

        $buffer = '';

        foreach ($items as $name => $item) {
            $numMethods       = count($item['methods']);
            $numTestedMethods = 0;

            foreach ($item['methods'] as $method) {
                if ($method['executedLines'] == $method['executableLines']) {
                    $numTestedMethods++;
                }
            }

            if ($item['executableLines'] > 0) {
                $numClasses                   = 1;
                $numTestedClasses             = $numTestedMethods == $numMethods ? 1 : 0;
                $linesExecutedPercentAsString = Util::percent(
                    $item['executedLines'],
                    $item['executableLines'],
                    true
                );
            } else {
                $numClasses                   = 'n/a';
                $numTestedClasses             = 'n/a';
                $linesExecutedPercentAsString = 'n/a';
            }

            $buffer .= $this->renderItemTemplate(
                $template,
                [
                    'name'                         => $name,
                    'numClasses'                   => $numClasses,
                    'numTestedClasses'             => $numTestedClasses,
                    'numMethods'                   => $numMethods,
                    'numTestedMethods'             => $numTestedMethods,
                    'linesExecutedPercent'         => Util::percent(
                        $item['executedLines'],
                        $item['executableLines'],
                        false
                    ),
                    'linesExecutedPercentAsString' => $linesExecutedPercentAsString,
                    'numExecutedLines'             => $item['executedLines'],
                    'numExecutableLines'           => $item['executableLines'],
                    'testedMethodsPercent'         => Util::percent(
                        $numTestedMethods,
                        $numMethods,
                        false
                    ),
                    'testedMethodsPercentAsString' => Util::percent(
                        $numTestedMethods,
                        $numMethods,
                        true
                    ),
                    'testedClassesPercent'         => Util::percent(
                        $numTestedMethods == $numMethods ? 1 : 0,
                        1,
                        false
                    ),
                    'testedClassesPercentAsString' => Util::percent(
                        $numTestedMethods == $numMethods ? 1 : 0,
                        1,
                        true
                    ),
                    'crap'                         => $item['crap']
                ]
            );

            foreach ($item['methods'] as $method) {
                $buffer .= $this->renderFunctionOrMethodItem(
                    $methodItemTemplate,
                    $method,
                    '&nbsp;'
                );
            }
        }

        return $buffer;
    }

    /**
     * @param array          $functions
     * @param \Text_Template $template
     *
     * @return string
     */
    protected function renderFunctionItems(array $functions, \Text_Template $template)
    {
        if (empty($functions)) {
            return '';
        }

        $buffer = '';

        foreach ($functions as $function) {
            $buffer .= $this->renderFunctionOrMethodItem(
                $template,
                $function
            );
        }

        return $buffer;
    }

    /**
     * @param \Text_Template $template
     *
     * @return string
     */
    protected function renderFunctionOrMethodItem(\Text_Template $template, array $item, $indent = '')
    {
        $numTestedItems = $item['executedLines'] == $item['executableLines'] ? 1 : 0;

        return $this->renderItemTemplate(
            $template,
            [
                'name'                         => sprintf(
                    '%s<a href="#%d"><abbr title="%s">%s</abbr></a>',
                    $indent,
                    $item['startLine'],
                    htmlspecialchars($item['signature']),
                    isset($item['functionName']) ? $item['functionName'] : $item['methodName']
                ),
                'numMethods'                   => 1,
                'numTestedMethods'             => $numTestedItems,
                'linesExecutedPercent'         => Util::percent(
                    $item['executedLines'],
                    $item['executableLines'],
                    false
                ),
                'linesExecutedPercentAsString' => Util::percent(
                    $item['executedLines'],
                    $item['executableLines'],
                    true
                ),
                'numExecutedLines'             => $item['executedLines'],
                'numExecutableLines'           => $item['executableLines'],
                'testedMethodsPercent'         => Util::percent(
                    $numTestedItems,
                    1,
                    false
                ),
                'testedMethodsPercentAsString' => Util::percent(
                    $numTestedItems,
                    1,
                    true
                ),
                'crap'                         => $item['crap']
            ]
        );
    }

    /**
     * @param FileNode $node
     *
     * @return string
     */
    protected function renderSource(FileNode $node)
    {
        $coverageData = $node->getCoverageData();
        $testData     = $node->getTestData();
        $codeLines    = $this->loadFile($node->getPath());
        $lines        = '';
        $i            = 1;

        foreach ($codeLines as $line) {
            $trClass        = '';
            $popoverContent = '';
            $popoverTitle   = '';

            if (array_key_exists($i, $coverageData)) {
                $numTests = count($coverageData[$i]);

                if ($coverageData[$i] === null) {
                    $trClass = ' class="warning"';
                } elseif ($numTests == 0) {
                    $trClass = ' class="danger"';
                } else {
                    $lineCss        = 'covered-by-large-tests';
                    $popoverContent = '<ul>';

                    if ($numTests > 1) {
                        $popoverTitle = $numTests . ' tests cover line ' . $i;
                    } else {
                        $popoverTitle = '1 test covers line ' . $i;
                    }

                    foreach ($coverageData[$i] as $test) {
                        if ($lineCss == 'covered-by-large-tests' && $testData[$test]['size'] == 'medium') {
                            $lineCss = 'covered-by-medium-tests';
                        } elseif ($testData[$test]['size'] == 'small') {
                            $lineCss = 'covered-by-small-tests';
                        }

                        switch ($testData[$test]['status']) {
                            case 0:
                                switch ($testData[$test]['size']) {
                                    case 'small':
                                        $testCSS = ' class="covered-by-small-tests"';
                                        break;

                                    case 'medium':
                                        $testCSS = ' class="covered-by-medium-tests"';
                                        break;

                                    default:
                                        $testCSS = ' class="covered-by-large-tests"';
                                        break;
                                }
                                break;

                            case 1:
                            case 2:
                                $testCSS = ' class="warning"';
                                break;

                            case 3:
                                $testCSS = ' class="danger"';
                                break;

                            case 4:
                                $testCSS = ' class="danger"';
                                break;

                            default:
                                $testCSS = '';
                        }

                        $popoverContent .= sprintf(
                            '<li%s>%s</li>',
                            $testCSS,
                            htmlspecialchars($test)
                        );
                    }

                    $popoverContent .= '</ul>';
                    $trClass         = ' class="' . $lineCss . ' popin"';
                }
            }

            if (!empty($popoverTitle)) {
                $popover = sprintf(
                    ' data-title="%s" data-content="%s" data-placement="bottom" data-html="true"',
                    $popoverTitle,
                    htmlspecialchars($popoverContent)
                );
            } else {
                $popover = '';
            }

            $lines .= sprintf(
                '     <tr%s%s><td><div align="right"><a name="%d"></a><a href="#%d">%d</a></div></td><td class="codeLine">%s</td></tr>' . "\n",
                $trClass,
                $popover,
                $i,
                $i,
                $i,
                $line
            );

            $i++;
        }

        return $lines;
    }

    /**
     * @param string $file
     *
     * @return array
     */
    protected function loadFile($file)
    {
        $buffer              = file_get_contents($file);
        $tokens              = token_get_all($buffer);
        $result              = [''];
        $i                   = 0;
        $stringFlag          = false;
        $fileEndsWithNewLine = substr($buffer, -1) == "\n";

        unset($buffer);

        foreach ($tokens as $j => $token) {
            if (is_string($token)) {
                if ($token === '"' && $tokens[$j - 1] !== '\\') {
                    $result[$i] .= sprintf(
                        '<span class="string">%s</span>',
                        htmlspecialchars($token)
                    );

                    $stringFlag = !$stringFlag;
                } else {
                    $result[$i] .= sprintf(
                        '<span class="keyword">%s</span>',
                        htmlspecialchars($token)
                    );
                }

                continue;
            }

            list($token, $value) = $token;

            $value = str_replace(
                ["\t", ' '],
                ['&nbsp;&nbsp;&nbsp;&nbsp;', '&nbsp;'],
                htmlspecialchars($value, $this->htmlspecialcharsFlags)
            );

            if ($value === "\n") {
                $result[++$i] = '';
            } else {
                $lines = explode("\n", $value);

                foreach ($lines as $jj => $line) {
                    $line = trim($line);

                    if ($line !== '') {
                        if ($stringFlag) {
                            $colour = 'string';
                        } else {
                            switch ($token) {
                                case T_INLINE_HTML:
                                    $colour = 'html';
                                    break;

                                case T_COMMENT:
                                case T_DOC_COMMENT:
                                    $colour = 'comment';
                                    break;

                                case T_ABSTRACT:
                                case T_ARRAY:
                                case T_AS:
                                case T_BREAK:
                                case T_CALLABLE:
                                case T_CASE:
                                case T_CATCH:
                                case T_CLASS:
                                case T_CLONE:
                                case T_CONTINUE:
                                case T_DEFAULT:
                                case T_ECHO:
                                case T_ELSE:
                                case T_ELSEIF:
                                case T_EMPTY:
                                case T_ENDDECLARE:
                                case T_ENDFOR:
                                case T_ENDFOREACH:
                                case T_ENDIF:
                                case T_ENDSWITCH:
                                case T_ENDWHILE:
                                case T_EXIT:
                                case T_EXTENDS:
                                case T_FINAL:
                                case T_FINALLY:
                                case T_FOREACH:
                                case T_FUNCTION:
                                case T_GLOBAL:
                                case T_IF:
                                case T_IMPLEMENTS:
                                case T_INCLUDE:
                                case T_INCLUDE_ONCE:
                                case T_INSTANCEOF:
                                case T_INSTEADOF:
                                case T_INTERFACE:
                                case T_ISSET:
                                case T_LOGICAL_AND:
                                case T_LOGICAL_OR:
                                case T_LOGICAL_XOR:
                                case T_NAMESPACE:
                                case T_NEW:
                                case T_PRIVATE:
                                case T_PROTECTED:
                                case T_PUBLIC:
                                case T_REQUIRE:
                                case T_REQUIRE_ONCE:
                                case T_RETURN:
                                case T_STATIC:
                                case T_THROW:
                                case T_TRAIT:
                                case T_TRY:
                                case T_UNSET:
                                case T_USE:
                                case T_VAR:
                                case T_WHILE:
                                case T_YIELD:
                                    $colour = 'keyword';
                                    break;

                                default:
                                    $colour = 'default';
                            }
                        }

                        $result[$i] .= sprintf(
                            '<span class="%s">%s</span>',
                            $colour,
                            $line
                        );
                    }

                    if (isset($lines[$jj + 1])) {
                        $result[++$i] = '';
                    }
                }
            }
        }

        if ($fileEndsWithNewLine) {
            unset($result[count($result)-1]);
        }

        return $result;
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage\Report\Html;

use SebastianBergmann\CodeCoverage\Node\AbstractNode as Node;
use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode;

/**
 * Renders a directory node.
 */
class Directory extends Renderer
{
    /**
     * @param DirectoryNode $node
     * @param string        $file
     */
    public function render(DirectoryNode $node, $file)
    {
        $template = new \Text_Template($this->templatePath . 'directory.html', '{{', '}}');

        $this->setCommonTemplateVariables($template, $node);

        $items = $this->renderItem($node, true);

        foreach ($node->getDirectories() as $item) {
            $items .= $this->renderItem($item);
        }

        foreach ($node->getFiles() as $item) {
            $items .= $this->renderItem($item);
        }

        $template->setVar(
            [
                'id'    => $node->getId(),
                'items' => $items
            ]
        );

        $template->renderTo($file);
    }

    /**
     * @param Node $node
     * @param bool $total
     *
     * @return string
     */
    protected function renderItem(Node $node, $total = false)
    {
        $data = [
            'numClasses'                   => $node->getNumClassesAndTraits(),
            'numTestedClasses'             => $node->getNumTestedClassesAndTraits(),
            'numMethods'                   => $node->getNumMethods(),
            'numTestedMethods'             => $node->getNumTestedMethods(),
            'linesExecutedPercent'         => $node->getLineExecutedPercent(false),
            'linesExecutedPercentAsString' => $node->getLineExecutedPercent(),
            'numExecutedLines'             => $node->getNumExecutedLines(),
            'numExecutableLines'           => $node->getNumExecutableLines(),
            'testedMethodsPercent'         => $node->getTestedMethodsPercent(false),
            'testedMethodsPercentAsString' => $node->getTestedMethodsPercent(),
            'testedClassesPercent'         => $node->getTestedClassesAndTraitsPercent(false),
            'testedClassesPercentAsString' => $node->getTestedClassesAndTraitsPercent()
        ];

        if ($total) {
            $data['name'] = 'Total';
        } else {
            if ($node instanceof DirectoryNode) {
                $data['name'] = sprintf(
                    '<a href="%s/index.html">%s</a>',
                    $node->getName(),
                    $node->getName()
                );

                $data['icon'] = '<span class="glyphicon glyphicon-folder-open"></span> ';
            } else {
                $data['name'] = sprintf(
                    '<a href="%s.html">%s</a>',
                    $node->getName(),
                    $node->getName()
                );

                $data['icon'] = '<span class="glyphicon glyphicon-file"></span> ';
            }
        }

        return $this->renderItemTemplate(
            new \Text_Template($this->templatePath . 'directory_item.html', '{{', '}}'),
            $data
        );
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage\Report\Html;

use SebastianBergmann\CodeCoverage\Node\AbstractNode;
use SebastianBergmann\CodeCoverage\Node\File as FileNode;
use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode;
use SebastianBergmann\Environment\Runtime;
use SebastianBergmann\Version;

/**
 * Base class for node renderers.
 */
abstract class Renderer
{
    /**
     * @var string
     */
    protected $templatePath;

    /**
     * @var string
     */
    protected $generator;

    /**
     * @var string
     */
    protected $date;

    /**
     * @var int
     */
    protected $lowUpperBound;

    /**
     * @var int
     */
    protected $highLowerBound;

    /**
     * @var string
     */
    protected $version;

    /**
     * Constructor.
     *
     * @param string $templatePath
     * @param string $generator
     * @param string $date
     * @param int    $lowUpperBound
     * @param int    $highLowerBound
     */
    public function __construct($templatePath, $generator, $date, $lowUpperBound, $highLowerBound)
    {
        $version = new Version('4.0.8', dirname(dirname(dirname(dirname(__DIR__)))));

        $this->templatePath   = $templatePath;
        $this->generator      = $generator;
        $this->date           = $date;
        $this->lowUpperBound  = $lowUpperBound;
        $this->highLowerBound = $highLowerBound;
        $this->version        = $version->getVersion();
    }

    /**
     * @param \Text_Template $template
     * @param array          $data
     *
     * @return string
     */
    protected function renderItemTemplate(\Text_Template $template, array $data)
    {
        $numSeparator  = '&nbsp;/&nbsp;';

        if (isset($data['numClasses']) && $data['numClasses'] > 0) {
            $classesLevel = $this->getColorLevel($data['testedClassesPercent']);

            $classesNumber = $data['numTestedClasses'] . $numSeparator .
                $data['numClasses'];

            $classesBar = $this->getCoverageBar(
                $data['testedClassesPercent']
            );
        } else {
            $classesLevel                         = '';
            $classesNumber                        = '0' . $numSeparator . '0';
            $classesBar                           = '';
            $data['testedClassesPercentAsString'] = 'n/a';
        }

        if ($data['numMethods'] > 0) {
            $methodsLevel = $this->getColorLevel($data['testedMethodsPercent']);

            $methodsNumber = $data['numTestedMethods'] . $numSeparator .
                $data['numMethods'];

            $methodsBar = $this->getCoverageBar(
                $data['testedMethodsPercent']
            );
        } else {
            $methodsLevel                         = '';
            $methodsNumber                        = '0' . $numSeparator . '0';
            $methodsBar                           = '';
            $data['testedMethodsPercentAsString'] = 'n/a';
        }

        if ($data['numExecutableLines'] > 0) {
            $linesLevel = $this->getColorLevel($data['linesExecutedPercent']);

            $linesNumber = $data['numExecutedLines'] . $numSeparator .
                $data['numExecutableLines'];

            $linesBar = $this->getCoverageBar(
                $data['linesExecutedPercent']
            );
        } else {
            $linesLevel                           = '';
            $linesNumber                          = '0' . $numSeparator . '0';
            $linesBar                             = '';
            $data['linesExecutedPercentAsString'] = 'n/a';
        }

        $template->setVar(
            [
                'icon'                   => isset($data['icon']) ? $data['icon'] : '',
                'crap'                   => isset($data['crap']) ? $data['crap'] : '',
                'name'                   => $data['name'],
                'lines_bar'              => $linesBar,
                'lines_executed_percent' => $data['linesExecutedPercentAsString'],
                'lines_level'            => $linesLevel,
                'lines_number'           => $linesNumber,
                'methods_bar'            => $methodsBar,
                'methods_tested_percent' => $data['testedMethodsPercentAsString'],
                'methods_level'          => $methodsLevel,
                'methods_number'         => $methodsNumber,
                'classes_bar'            => $classesBar,
                'classes_tested_percent' => isset($data['testedClassesPercentAsString']) ? $data['testedClassesPercentAsString'] : '',
                'classes_level'          => $classesLevel,
                'classes_number'         => $classesNumber
            ]
        );

        return $template->render();
    }

    /**
     * @param \Text_Template $template
     * @param AbstractNode   $node
     */
    protected function setCommonTemplateVariables(\Text_Template $template, AbstractNode $node)
    {
        $template->setVar(
            [
                'id'               => $node->getId(),
                'full_path'        => $node->getPath(),
                'path_to_root'     => $this->getPathToRoot($node),
                'breadcrumbs'      => $this->getBreadcrumbs($node),
                'date'             => $this->date,
                'version'          => $this->version,
                'runtime'          => $this->getRuntimeString(),
                'generator'        => $this->generator,
                'low_upper_bound'  => $this->lowUpperBound,
                'high_lower_bound' => $this->highLowerBound
            ]
        );
    }

    protected function getBreadcrumbs(AbstractNode $node)
    {
        $breadcrumbs = '';
        $path        = $node->getPathAsArray();
        $pathToRoot  = [];
        $max         = count($path);

        if ($node instanceof FileNode) {
            $max--;
        }

        for ($i = 0; $i < $max; $i++) {
            $pathToRoot[] = str_repeat('../', $i);
        }

        foreach ($path as $step) {
            if ($step !== $node) {
                $breadcrumbs .= $this->getInactiveBreadcrumb(
                    $step,
                    array_pop($pathToRoot)
                );
            } else {
                $breadcrumbs .= $this->getActiveBreadcrumb($step);
            }
        }

        return $breadcrumbs;
    }

    protected function getActiveBreadcrumb(AbstractNode $node)
    {
        $buffer = sprintf(
            '        <li class="active">%s</li>' . "\n",
            $node->getName()
        );

        if ($node instanceof DirectoryNode) {
            $buffer .= '        <li>(<a href="dashboard.html">Dashboard</a>)</li>' . "\n";
        }

        return $buffer;
    }

    protected function getInactiveBreadcrumb(AbstractNode $node, $pathToRoot)
    {
        return sprintf(
            '        <li><a href="%sindex.html">%s</a></li>' . "\n",
            $pathToRoot,
            $node->getName()
        );
    }

    protected function getPathToRoot(AbstractNode $node)
    {
        $id    = $node->getId();
        $depth = substr_count($id, '/');

        if ($id != 'index' &&
            $node instanceof DirectoryNode) {
            $depth++;
        }

        return str_repeat('../', $depth);
    }

    protected function getCoverageBar($percent)
    {
        $level = $this->getColorLevel($percent);

        $template = new \Text_Template(
            $this->templatePath . 'coverage_bar.html',
            '{{',
            '}}'
        );

        $template->setVar(['level' => $level, 'percent' => sprintf('%.2F', $percent)]);

        return $template->render();
    }

    /**
     * @param int $percent
     *
     * @return string
     */
    protected function getColorLevel($percent)
    {
        if ($percent <= $this->lowUpperBound) {
            return 'danger';
        } elseif ($percent > $this->lowUpperBound &&
            $percent <  $this->highLowerBound) {
            return 'warning';
        } else {
            return 'success';
        }
    }

    /**
     * @return string
     */
    private function getRuntimeString()
    {
        $runtime = new Runtime;

        $buffer = sprintf(
            '<a href="%s" target="_top">%s %s</a>',
            $runtime->getVendorUrl(),
            $runtime->getName(),
            $runtime->getVersion()
        );

        if ($runtime->hasXdebug() && !$runtime->hasPHPDBGCodeCoverage()) {
            $buffer .= sprintf(
                ' with <a href="https://xdebug.org/">Xdebug %s</a>',
                phpversion('xdebug')
            );
        }

        return $buffer;
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage\Report\Html;

use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode;
use SebastianBergmann\CodeCoverage\RuntimeException;

/**
 * Generates an HTML report from a code coverage object.
 */
class Facade
{
    /**
     * @var string
     */
    private $templatePath;

    /**
     * @var string
     */
    private $generator;

    /**
     * @var int
     */
    private $lowUpperBound;

    /**
     * @var int
     */
    private $highLowerBound;

    /**
     * Constructor.
     *
     * @param int    $lowUpperBound
     * @param int    $highLowerBound
     * @param string $generator
     */
    public function __construct($lowUpperBound = 50, $highLowerBound = 90, $generator = '')
    {
        $this->generator      = $generator;
        $this->highLowerBound = $highLowerBound;
        $this->lowUpperBound  = $lowUpperBound;
        $this->templatePath   = __DIR__ . '/Renderer/Template/';
    }

    /**
     * @param CodeCoverage $coverage
     * @param string       $target
     */
    public function process(CodeCoverage $coverage, $target)
    {
        $target = $this->getDirectory($target);
        $report = $coverage->getReport();
        unset($coverage);

        if (!isset($_SERVER['REQUEST_TIME'])) {
            $_SERVER['REQUEST_TIME'] = time();
        }

        $date = date('D M j G:i:s T Y', $_SERVER['REQUEST_TIME']);

        $dashboard = new Dashboard(
            $this->templatePath,
            $this->generator,
            $date,
            $this->lowUpperBound,
            $this->highLowerBound
        );

        $directory = new Directory(
            $this->templatePath,
            $this->generator,
            $date,
            $this->lowUpperBound,
            $this->highLowerBound
        );

        $file = new File(
            $this->templatePath,
            $this->generator,
            $date,
            $this->lowUpperBound,
            $this->highLowerBound
        );

        $directory->render($report, $target . 'index.html');
        $dashboard->render($report, $target . 'dashboard.html');

        foreach ($report as $node) {
            $id = $node->getId();

            if ($node instanceof DirectoryNode) {
                if (!file_exists($target . $id)) {
                    mkdir($target . $id, 0777, true);
                }

                $directory->render($node, $target . $id . '/index.html');
                $dashboard->render($node, $target . $id . '/dashboard.html');
            } else {
                $dir = dirname($target . $id);

                if (!file_exists($dir)) {
                    mkdir($dir, 0777, true);
                }

                $file->render($node, $target . $id . '.html');
            }
        }

        $this->copyFiles($target);
    }

    /**
     * @param string $target
     */
    private function copyFiles($target)
    {
        $dir = $this->getDirectory($target . 'css');
        copy($this->templatePath . 'css/bootstrap.min.css', $dir . 'bootstrap.min.css');
        copy($this->templatePath . 'css/nv.d3.min.css', $dir . 'nv.d3.min.css');
        copy($this->templatePath . 'css/style.css', $dir . 'style.css');

        $dir = $this->getDirectory($target . 'fonts');
        copy($this->templatePath . 'fonts/glyphicons-halflings-regular.eot', $dir . 'glyphicons-halflings-regular.eot');
        copy($this->templatePath . 'fonts/glyphicons-halflings-regular.svg', $dir . 'glyphicons-halflings-regular.svg');
        copy($this->templatePath . 'fonts/glyphicons-halflings-regular.ttf', $dir . 'glyphicons-halflings-regular.ttf');
        copy($this->templatePath . 'fonts/glyphicons-halflings-regular.woff', $dir . 'glyphicons-halflings-regular.woff');
        copy($this->templatePath . 'fonts/glyphicons-halflings-regular.woff2', $dir . 'glyphicons-halflings-regular.woff2');

        $dir = $this->getDirectory($target . 'js');
        copy($this->templatePath . 'js/bootstrap.min.js', $dir . 'bootstrap.min.js');
        copy($this->templatePath . 'js/d3.min.js', $dir . 'd3.min.js');
        copy($this->templatePath . 'js/holder.min.js', $dir . 'holder.min.js');
        copy($this->templatePath . 'js/html5shiv.min.js', $dir . 'html5shiv.min.js');
        copy($this->templatePath . 'js/jquery.min.js', $dir . 'jquery.min.js');
        copy($this->templatePath . 'js/nv.d3.min.js', $dir . 'nv.d3.min.js');
        copy($this->templatePath . 'js/respond.min.js', $dir . 'respond.min.js');
    }

    /**
     * @param string $directory
     *
     * @return string
     *
     * @throws RuntimeException
     */
    private function getDirectory($directory)
    {
        if (substr($directory, -1, 1) != DIRECTORY_SEPARATOR) {
            $directory .= DIRECTORY_SEPARATOR;
        }

        if (is_dir($directory)) {
            return $directory;
        }

        if (@mkdir($directory, 0777, true)) {
            return $directory;
        }

        throw new RuntimeException(
            sprintf(
                'Directory "%s" does not exist.',
                $directory
            )
        );
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage\Report\Xml;

use SebastianBergmann\CodeCoverage\Util;

class Totals
{
    /**
     * @var \DOMNode
     */
    private $container;

    /**
     * @var \DOMElement
     */
    private $linesNode;

    /**
     * @var \DOMElement
     */
    private $methodsNode;

    /**
     * @var \DOMElement
     */
    private $functionsNode;

    /**
     * @var \DOMElement
     */
    private $classesNode;

    /**
     * @var \DOMElement
     */
    private $traitsNode;

    public function __construct(\DOMElement $container)
    {
        $this->container = $container;
        $dom             = $container->ownerDocument;

        $this->linesNode = $dom->createElementNS(
            'http://schema.phpunit.de/coverage/1.0',
            'lines'
        );

        $this->methodsNode = $dom->createElementNS(
            'http://schema.phpunit.de/coverage/1.0',
            'methods'
        );

        $this->functionsNode = $dom->createElementNS(
            'http://schema.phpunit.de/coverage/1.0',
            'functions'
        );

        $this->classesNode = $dom->createElementNS(
            'http://schema.phpunit.de/coverage/1.0',
            'classes'
        );

        $this->traitsNode = $dom->createElementNS(
            'http://schema.phpunit.de/coverage/1.0',
            'traits'
        );

        $container->appendChild($this->linesNode);
        $container->appendChild($this->methodsNode);
        $container->appendChild($this->functionsNode);
        $container->appendChild($this->classesNode);
        $container->appendChild($this->traitsNode);
    }

    public function getContainer()
    {
        return $this->container;
    }

    public function setNumLines($loc, $cloc, $ncloc, $executable, $executed)
    {
        $this->linesNode->setAttribute('total', $loc);
        $this->linesNode->setAttribute('comments', $cloc);
        $this->linesNode->setAttribute('code', $ncloc);
        $this->linesNode->setAttribute('executable', $executable);
        $this->linesNode->setAttribute('executed', $executed);
        $this->linesNode->setAttribute(
            'percent',
            Util::percent($executed, $executable, true)
        );
    }

    public function setNumClasses($count, $tested)
    {
        $this->classesNode->setAttribute('count', $count);
        $this->classesNode->setAttribute('tested', $tested);
        $this->classesNode->setAttribute(
            'percent',
            Util::percent($tested, $count, true)
        );
    }

    public function setNumTraits($count, $tested)
    {
        $this->traitsNode->setAttribute('count', $count);
        $this->traitsNode->setAttribute('tested', $tested);
        $this->traitsNode->setAttribute(
            'percent',
            Util::percent($tested, $count, true)
        );
    }

    public function setNumMethods($count, $tested)
    {
        $this->methodsNode->setAttribute('count', $count);
        $this->methodsNode->setAttribute('tested', $tested);
        $this->methodsNode->setAttribute(
            'percent',
            Util::percent($tested, $count, true)
        );
    }

    public function setNumFunctions($count, $tested)
    {
        $this->functionsNode->setAttribute('count', $count);
        $this->functionsNode->setAttribute('tested', $tested);
        $this->functionsNode->setAttribute(
            'percent',
            Util::percent($tested, $count, true)
        );
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage\Report\Xml;

class File
{
    /**
     * @var \DOMDocument
     */
    protected $dom;

    /**
     * @var \DOMElement
     */
    protected $contextNode;

    public function __construct(\DOMElement $context)
    {
        $this->dom         = $context->ownerDocument;
        $this->contextNode = $context;
    }

    public function getTotals()
    {
        $totalsContainer = $this->contextNode->firstChild;

        if (!$totalsContainer) {
            $totalsContainer = $this->contextNode->appendChild(
                $this->dom->createElementNS(
                    'http://schema.phpunit.de/coverage/1.0',
                    'totals'
                )
            );
        }

        return new Totals($totalsContainer);
    }

    public function getLineCoverage($line)
    {
        $coverage = $this->contextNode->getElementsByTagNameNS(
            'http://schema.phpunit.de/coverage/1.0',
            'coverage'
        )->item(0);

        if (!$coverage) {
            $coverage = $this->contextNode->appendChild(
                $this->dom->createElementNS(
                    'http://schema.phpunit.de/coverage/1.0',
                    'coverage'
                )
            );
        }

        $lineNode = $coverage->appendChild(
            $this->dom->createElementNS(
                'http://schema.phpunit.de/coverage/1.0',
                'line'
            )
        );

        return new Coverage($lineNode, $line);
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage\Report\Xml;

class Method
{
    /**
     * @var \DOMElement
     */
    private $contextNode;

    public function __construct(\DOMElement $context, $name)
    {
        $this->contextNode = $context;

        $this->setName($name);
    }

    private function setName($name)
    {
        $this->contextNode->setAttribute('name', $name);
    }

    public function setSignature($signature)
    {
        $this->contextNode->setAttribute('signature', $signature);
    }

    public function setLines($start, $end = null)
    {
        $this->contextNode->setAttribute('start', $start);

        if ($end !== null) {
            $this->contextNode->setAttribute('end', $end);
        }
    }

    public function setTotals($executable, $executed, $coverage)
    {
        $this->contextNode->setAttribute('executable', $executable);
        $this->contextNode->setAttribute('executed', $executed);
        $this->contextNode->setAttribute('coverage', $coverage);
    }

    public function setCrap($crap)
    {
        $this->contextNode->setAttribute('crap', $crap);
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage\Report\Xml;

class Unit
{
    /**
     * @var \DOMElement
     */
    private $contextNode;

    public function __construct(\DOMElement $context, $name)
    {
        $this->contextNode = $context;

        $this->setName($name);
    }

    private function setName($name)
    {
        $this->contextNode->setAttribute('name', $name);
    }

    public function setLines($start, $executable, $executed)
    {
        $this->contextNode->setAttribute('start', $start);
        $this->contextNode->setAttribute('executable', $executable);
        $this->contextNode->setAttribute('executed', $executed);
    }

    public function setCrap($crap)
    {
        $this->contextNode->setAttribute('crap', $crap);
    }

    public function setPackage($full, $package, $sub, $category)
    {
        $node = $this->contextNode->getElementsByTagNameNS(
            'http://schema.phpunit.de/coverage/1.0',
            'package'
        )->item(0);

        if (!$node) {
            $node = $this->contextNode->appendChild(
                $this->contextNode->ownerDocument->createElementNS(
                    'http://schema.phpunit.de/coverage/1.0',
                    'package'
                )
            );
        }

        $node->setAttribute('full', $full);
        $node->setAttribute('name', $package);
        $node->setAttribute('sub', $sub);
        $node->setAttribute('category', $category);
    }

    public function setNamespace($namespace)
    {
        $node = $this->contextNode->getElementsByTagNameNS(
            'http://schema.phpunit.de/coverage/1.0',
            'namespace'
        )->item(0);

        if (!$node) {
            $node = $this->contextNode->appendChild(
                $this->contextNode->ownerDocument->createElementNS(
                    'http://schema.phpunit.de/coverage/1.0',
                    'namespace'
                )
            );
        }

        $node->setAttribute('name', $namespace);
    }

    public function addMethod($name)
    {
        $node = $this->contextNode->appendChild(
            $this->contextNode->ownerDocument->createElementNS(
                'http://schema.phpunit.de/coverage/1.0',
                'method'
            )
        );

        return new Method($node, $name);
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage\Report\Xml;

class Tests
{
    private $contextNode;

    private $codeMap = [
        0 => 'PASSED',     // PHPUnit_Runner_BaseTestRunner::STATUS_PASSED
        1 => 'SKIPPED',    // PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED
        2 => 'INCOMPLETE', // PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE
        3 => 'FAILURE',    // PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE
        4 => 'ERROR',      // PHPUnit_Runner_BaseTestRunner::STATUS_ERROR
        5 => 'RISKY',      // PHPUnit_Runner_BaseTestRunner::STATUS_RISKY
        6 => 'WARNING'     // PHPUnit_Runner_BaseTestRunner::STATUS_WARNING
    ];

    public function __construct(\DOMElement $context)
    {
        $this->contextNode = $context;
    }

    public function addTest($test, array $result)
    {
        $node = $this->contextNode->appendChild(
            $this->contextNode->ownerDocument->createElementNS(
                'http://schema.phpunit.de/coverage/1.0',
                'test'
            )
        );

        $node->setAttribute('name', $test);
        $node->setAttribute('size', $result['size']);
        $node->setAttribute('result', (int) $result['status']);
        $node->setAttribute('status', $this->codeMap[(int) $result['status']]);
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage\Report\Xml;

class Project extends Node
{
    public function __construct($name)
    {
        $this->init();
        $this->setProjectName($name);
    }

    private function init()
    {
        $dom = new \DOMDocument;
        $dom->loadXML('<?xml version="1.0" ?><phpunit xmlns="http://schema.phpunit.de/coverage/1.0"><project/></phpunit>');

        $this->setContextNode(
            $dom->getElementsByTagNameNS(
                'http://schema.phpunit.de/coverage/1.0',
                'project'
            )->item(0)
        );
    }

    private function setProjectName($name)
    {
        $this->getContextNode()->setAttribute('name', $name);
    }

    public function getTests()
    {
        $testsNode = $this->getContextNode()->getElementsByTagNameNS(
            'http://schema.phpunit.de/coverage/1.0',
            'tests'
        )->item(0);

        if (!$testsNode) {
            $testsNode = $this->getContextNode()->appendChild(
                $this->getDom()->createElementNS(
                    'http://schema.phpunit.de/coverage/1.0',
                    'tests'
                )
            );
        }

        return new Tests($testsNode);
    }

    public function asDom()
    {
        return $this->getDom();
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage\Report\Xml;

class Node
{
    /**
     * @var \DOMDocument
     */
    private $dom;

    /**
     * @var \DOMElement
     */
    private $contextNode;

    public function __construct(\DOMElement $context)
    {
        $this->setContextNode($context);
    }

    protected function setContextNode(\DOMElement $context)
    {
        $this->dom         = $context->ownerDocument;
        $this->contextNode = $context;
    }

    public function getDom()
    {
        return $this->dom;
    }

    protected function getContextNode()
    {
        return $this->contextNode;
    }

    public function getTotals()
    {
        $totalsContainer = $this->getContextNode()->firstChild;

        if (!$totalsContainer) {
            $totalsContainer = $this->getContextNode()->appendChild(
                $this->dom->createElementNS(
                    'http://schema.phpunit.de/coverage/1.0',
                    'totals'
                )
            );
        }

        return new Totals($totalsContainer);
    }

    public function addDirectory($name)
    {
        $dirNode = $this->getDom()->createElementNS(
            'http://schema.phpunit.de/coverage/1.0',
            'directory'
        );

        $dirNode->setAttribute('name', $name);
        $this->getContextNode()->appendChild($dirNode);

        return new Directory($dirNode);
    }

    public function addFile($name, $href)
    {
        $fileNode = $this->getDom()->createElementNS(
            'http://schema.phpunit.de/coverage/1.0',
            'file'
        );

        $fileNode->setAttribute('name', $name);
        $fileNode->setAttribute('href', $href);
        $this->getContextNode()->appendChild($fileNode);

        return new File($fileNode);
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage\Report\Xml;

class Directory extends Node
{
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage\Report\Xml;

use SebastianBergmann\CodeCoverage\RuntimeException;

class Coverage
{
    /**
     * @var \XMLWriter
     */
    private $writer;

    /**
     * @var \DOMElement
     */
    private $contextNode;

    /**
     * @var bool
     */
    private $finalized = false;

    public function __construct(\DOMElement $context, $line)
    {
        $this->contextNode = $context;

        $this->writer = new \XMLWriter;
        $this->writer->openMemory();
        $this->writer->startElementNs(null, $context->nodeName, 'http://schema.phpunit.de/coverage/1.0');
        $this->writer->writeAttribute('nr', $line);
    }

    public function addTest($test)
    {
        if ($this->finalized) {
            throw new RuntimeException('Coverage Report already finalized');
        }

        $this->writer->startElement('covered');
        $this->writer->writeAttribute('by', $test);
        $this->writer->endElement();
    }

    public function finalize()
    {
        $this->writer->endElement();

        $fragment = $this->contextNode->ownerDocument->createDocumentFragment();
        $fragment->appendXML($this->writer->outputMemory());

        $this->contextNode->parentNode->replaceChild(
            $fragment,
            $this->contextNode
        );

        $this->finalized = true;
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage\Report\Xml;

class Report extends File
{
    public function __construct($name)
    {
        $this->dom = new \DOMDocument;
        $this->dom->loadXML('<?xml version="1.0" ?><phpunit xmlns="http://schema.phpunit.de/coverage/1.0"><file /></phpunit>');

        $this->contextNode = $this->dom->getElementsByTagNameNS(
            'http://schema.phpunit.de/coverage/1.0',
            'file'
        )->item(0);

        $this->setName($name);
    }

    private function setName($name)
    {
        $this->contextNode->setAttribute('name', $name);
    }

    public function asDom()
    {
        return $this->dom;
    }

    public function getFunctionObject($name)
    {
        $node = $this->contextNode->appendChild(
            $this->dom->createElementNS(
                'http://schema.phpunit.de/coverage/1.0',
                'function'
            )
        );

        return new Method($node, $name);
    }

    public function getClassObject($name)
    {
        return $this->getUnitObject('class', $name);
    }

    public function getTraitObject($name)
    {
        return $this->getUnitObject('trait', $name);
    }

    private function getUnitObject($tagName, $name)
    {
        $node = $this->contextNode->appendChild(
            $this->dom->createElementNS(
                'http://schema.phpunit.de/coverage/1.0',
                $tagName
            )
        );

        return new Unit($node, $name);
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage\Report\Xml;

use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\Node\AbstractNode;
use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode;
use SebastianBergmann\CodeCoverage\Node\File as FileNode;
use SebastianBergmann\CodeCoverage\RuntimeException;

class Facade
{
    /**
     * @var string
     */
    private $target;

    /**
     * @var Project
     */
    private $project;

    /**
     * @param CodeCoverage $coverage
     * @param string       $target
     *
     * @throws RuntimeException
     */
    public function process(CodeCoverage $coverage, $target)
    {
        if (substr($target, -1, 1) != DIRECTORY_SEPARATOR) {
            $target .= DIRECTORY_SEPARATOR;
        }

        $this->target = $target;
        $this->initTargetDirectory($target);

        $report = $coverage->getReport();

        $this->project = new Project(
            $coverage->getReport()->getName()
        );

        $this->processTests($coverage->getTests());
        $this->processDirectory($report, $this->project);

        $index                     = $this->project->asDom();
        $index->formatOutput       = true;
        $index->preserveWhiteSpace = false;
        $index->save($target . '/index.xml');
    }

    /**
     * @param string $directory
     */
    private function initTargetDirectory($directory)
    {
        if (file_exists($directory)) {
            if (!is_dir($directory)) {
                throw new RuntimeException(
                    "'$directory' exists but is not a directory."
                );
            }

            if (!is_writable($directory)) {
                throw new RuntimeException(
                    "'$directory' exists but is not writable."
                );
            }
        } elseif (!@mkdir($directory, 0777, true)) {
            throw new RuntimeException(
                "'$directory' could not be created."
            );
        }
    }

    private function processDirectory(DirectoryNode $directory, Node $context)
    {
        $dirObject = $context->addDirectory($directory->getName());

        $this->setTotals($directory, $dirObject->getTotals());

        foreach ($directory->getDirectories() as $node) {
            $this->processDirectory($node, $dirObject);
        }

        foreach ($directory->getFiles() as $node) {
            $this->processFile($node, $dirObject);
        }
    }

    private function processFile(FileNode $file, Directory $context)
    {
        $fileObject = $context->addFile(
            $file->getName(),
            $file->getId() . '.xml'
        );

        $this->setTotals($file, $fileObject->getTotals());

        $fileReport = new Report($file->getName());

        $this->setTotals($file, $fileReport->getTotals());

        foreach ($file->getClassesAndTraits() as $unit) {
            $this->processUnit($unit, $fileReport);
        }

        foreach ($file->getFunctions() as $function) {
            $this->processFunction($function, $fileReport);
        }

        foreach ($file->getCoverageData() as $line => $tests) {
            if (!is_array($tests) || count($tests) == 0) {
                continue;
            }

            $coverage = $fileReport->getLineCoverage($line);

            foreach ($tests as $test) {
                $coverage->addTest($test);
            }

            $coverage->finalize();
        }

        $this->initTargetDirectory(
            $this->target . dirname($file->getId()) . '/'
        );

        $fileDom                     = $fileReport->asDom();
        $fileDom->formatOutput       = true;
        $fileDom->preserveWhiteSpace = false;
        $fileDom->save($this->target . $file->getId() . '.xml');
    }

    private function processUnit($unit, Report $report)
    {
        if (isset($unit['className'])) {
            $unitObject = $report->getClassObject($unit['className']);
        } else {
            $unitObject = $report->getTraitObject($unit['traitName']);
        }

        $unitObject->setLines(
            $unit['startLine'],
            $unit['executableLines'],
            $unit['executedLines']
        );

        $unitObject->setCrap($unit['crap']);

        $unitObject->setPackage(
            $unit['package']['fullPackage'],
            $unit['package']['package'],
            $unit['package']['subpackage'],
            $unit['package']['category']
        );

        $unitObject->setNamespace($unit['package']['namespace']);

        foreach ($unit['methods'] as $method) {
            $methodObject = $unitObject->addMethod($method['methodName']);
            $methodObject->setSignature($method['signature']);
            $methodObject->setLines($method['startLine'], $method['endLine']);
            $methodObject->setCrap($method['crap']);
            $methodObject->setTotals(
                $method['executableLines'],
                $method['executedLines'],
                $method['coverage']
            );
        }
    }

    private function processFunction($function, Report $report)
    {
        $functionObject = $report->getFunctionObject($function['functionName']);

        $functionObject->setSignature($function['signature']);
        $functionObject->setLines($function['startLine']);
        $functionObject->setCrap($function['crap']);
        $functionObject->setTotals($function['executableLines'], $function['executedLines'], $function['coverage']);
    }

    private function processTests(array $tests)
    {
        $testsObject = $this->project->getTests();

        foreach ($tests as $test => $result) {
            if ($test == 'UNCOVERED_FILES_FROM_WHITELIST') {
                continue;
            }

            $testsObject->addTest($test, $result);
        }
    }

    private function setTotals(AbstractNode $node, Totals $totals)
    {
        $loc = $node->getLinesOfCode();

        $totals->setNumLines(
            $loc['loc'],
            $loc['cloc'],
            $loc['ncloc'],
            $node->getNumExecutableLines(),
            $node->getNumExecutedLines()
        );

        $totals->setNumClasses(
            $node->getNumClasses(),
            $node->getNumTestedClasses()
        );

        $totals->setNumTraits(
            $node->getNumTraits(),
            $node->getNumTestedTraits()
        );

        $totals->setNumMethods(
            $node->getNumMethods(),
            $node->getNumTestedMethods()
        );

        $totals->setNumFunctions(
            $node->getNumFunctions(),
            $node->getNumTestedFunctions()
        );
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage\Report;

use SebastianBergmann\CodeCoverage\CodeCoverage;

/**
 * Uses var_export() to write a SebastianBergmann\CodeCoverage\CodeCoverage object to a file.
 */
class PHP
{
    /**
     * @param CodeCoverage $coverage
     * @param string       $target
     *
     * @return string
     */
    public function process(CodeCoverage $coverage, $target = null)
    {
        $filter = $coverage->filter();

        $output = sprintf(
            '<?php
$coverage = new SebastianBergmann\CodeCoverage\CodeCoverage;
$coverage->setData(%s);
$coverage->setTests(%s);

$filter = $coverage->filter();
$filter->setWhitelistedFiles(%s);

return $coverage;',
            var_export($coverage->getData(true), 1),
            var_export($coverage->getTests(), 1),
            var_export($filter->getWhitelistedFiles(), 1)
        );

        if ($target !== null) {
            return file_put_contents($target, $output);
        } else {
            return $output;
        }
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage\Report;

use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\Node\File;
use SebastianBergmann\CodeCoverage\Util;

/**
 * Generates human readable output from a code coverage object.
 *
 * The output gets put into a text file our written to the CLI.
 */
class Text
{
    private $lowUpperBound;
    private $highLowerBound;
    private $showUncoveredFiles;
    private $showOnlySummary;

    private $colors = [
        'green'  => "\x1b[30;42m",
        'yellow' => "\x1b[30;43m",
        'red'    => "\x1b[37;41m",
        'header' => "\x1b[1;37;40m",
        'reset'  => "\x1b[0m",
        'eol'    => "\x1b[2K",
    ];

    /**
     * @param int  $lowUpperBound
     * @param int  $highLowerBound
     * @param bool $showUncoveredFiles
     * @param bool $showOnlySummary
     */
    public function __construct($lowUpperBound = 50, $highLowerBound = 90, $showUncoveredFiles = false, $showOnlySummary = false)
    {
        $this->lowUpperBound      = $lowUpperBound;
        $this->highLowerBound     = $highLowerBound;
        $this->showUncoveredFiles = $showUncoveredFiles;
        $this->showOnlySummary    = $showOnlySummary;
    }

    /**
     * @param CodeCoverage $coverage
     * @param bool         $showColors
     *
     * @return string
     */
    public function process(CodeCoverage $coverage, $showColors = false)
    {
        $output = PHP_EOL . PHP_EOL;
        $report = $coverage->getReport();
        unset($coverage);

        $colors = [
            'header'  => '',
            'classes' => '',
            'methods' => '',
            'lines'   => '',
            'reset'   => '',
            'eol'     => ''
        ];

        if ($showColors) {
            $colors['classes'] = $this->getCoverageColor(
                $report->getNumTestedClassesAndTraits(),
                $report->getNumClassesAndTraits()
            );
            $colors['methods'] = $this->getCoverageColor(
                $report->getNumTestedMethods(),
                $report->getNumMethods()
            );
            $colors['lines']   = $this->getCoverageColor(
                $report->getNumExecutedLines(),
                $report->getNumExecutableLines()
            );
            $colors['reset']   = $this->colors['reset'];
            $colors['header']  = $this->colors['header'];
            $colors['eol']     = $this->colors['eol'];
        }

        $classes = sprintf(
            '  Classes: %6s (%d/%d)',
            Util::percent(
                $report->getNumTestedClassesAndTraits(),
                $report->getNumClassesAndTraits(),
                true
            ),
            $report->getNumTestedClassesAndTraits(),
            $report->getNumClassesAndTraits()
        );

        $methods = sprintf(
            '  Methods: %6s (%d/%d)',
            Util::percent(
                $report->getNumTestedMethods(),
                $report->getNumMethods(),
                true
            ),
            $report->getNumTestedMethods(),
            $report->getNumMethods()
        );

        $lines = sprintf(
            '  Lines:   %6s (%d/%d)',
            Util::percent(
                $report->getNumExecutedLines(),
                $report->getNumExecutableLines(),
                true
            ),
            $report->getNumExecutedLines(),
            $report->getNumExecutableLines()
        );

        $padding = max(array_map('strlen', [$classes, $methods, $lines]));

        if ($this->showOnlySummary) {
            $title   = 'Code Coverage Report Summary:';
            $padding = max($padding, strlen($title));

            $output .= $this->format($colors['header'], $padding, $title);
        } else {
            $date  = date('  Y-m-d H:i:s', $_SERVER['REQUEST_TIME']);
            $title = 'Code Coverage Report:';

            $output .= $this->format($colors['header'], $padding, $title);
            $output .= $this->format($colors['header'], $padding, $date);
            $output .= $this->format($colors['header'], $padding, '');
            $output .= $this->format($colors['header'], $padding, ' Summary:');
        }

        $output .= $this->format($colors['classes'], $padding, $classes);
        $output .= $this->format($colors['methods'], $padding, $methods);
        $output .= $this->format($colors['lines'], $padding, $lines);

        if ($this->showOnlySummary) {
            return $output . PHP_EOL;
        }

        $classCoverage = [];

        foreach ($report as $item) {
            if (!$item instanceof File) {
                continue;
            }

            $classes = $item->getClassesAndTraits();

            foreach ($classes as $className => $class) {
                $classStatements        = 0;
                $coveredClassStatements = 0;
                $coveredMethods         = 0;
                $classMethods           = 0;

                foreach ($class['methods'] as $method) {
                    if ($method['executableLines'] == 0) {
                        continue;
                    }

                    $classMethods++;
                    $classStatements        += $method['executableLines'];
                    $coveredClassStatements += $method['executedLines'];
                    if ($method['coverage'] == 100) {
                        $coveredMethods++;
                    }
                }

                if (!empty($class['package']['namespace'])) {
                    $namespace = '\\' . $class['package']['namespace'] . '::';
                } elseif (!empty($class['package']['fullPackage'])) {
                    $namespace = '@' . $class['package']['fullPackage'] . '::';
                } else {
                    $namespace = '';
                }

                $classCoverage[$namespace . $className] = [
                    'namespace'         => $namespace,
                    'className '        => $className,
                    'methodsCovered'    => $coveredMethods,
                    'methodCount'       => $classMethods,
                    'statementsCovered' => $coveredClassStatements,
                    'statementCount'    => $classStatements,
                ];
            }
        }

        ksort($classCoverage);

        $methodColor = '';
        $linesColor  = '';
        $resetColor  = '';

        foreach ($classCoverage as $fullQualifiedPath => $classInfo) {
            if ($classInfo['statementsCovered'] != 0 ||
                $this->showUncoveredFiles) {
                if ($showColors) {
                    $methodColor = $this->getCoverageColor($classInfo['methodsCovered'], $classInfo['methodCount']);
                    $linesColor  = $this->getCoverageColor($classInfo['statementsCovered'], $classInfo['statementCount']);
                    $resetColor  = $colors['reset'];
                }

                $output .= PHP_EOL . $fullQualifiedPath . PHP_EOL
                    . '  ' . $methodColor . 'Methods: ' . $this->printCoverageCounts($classInfo['methodsCovered'], $classInfo['methodCount'], 2) . $resetColor . ' '
                    . '  ' . $linesColor . 'Lines: ' . $this->printCoverageCounts($classInfo['statementsCovered'], $classInfo['statementCount'], 3) . $resetColor
                ;
            }
        }

        return $output . PHP_EOL;
    }

    protected function getCoverageColor($numberOfCoveredElements, $totalNumberOfElements)
    {
        $coverage = Util::percent(
            $numberOfCoveredElements,
            $totalNumberOfElements
        );

        if ($coverage >= $this->highLowerBound) {
            return $this->colors['green'];
        } elseif ($coverage > $this->lowUpperBound) {
            return $this->colors['yellow'];
        }

        return $this->colors['red'];
    }

    protected function printCoverageCounts($numberOfCoveredElements, $totalNumberOfElements, $precision)
    {
        $format = '%' . $precision . 's';

        return Util::percent(
            $numberOfCoveredElements,
            $totalNumberOfElements,
            true,
            true
        ) .
        ' (' . sprintf($format, $numberOfCoveredElements) . '/' .
        sprintf($format, $totalNumberOfElements) . ')';
    }

    private function format($color, $padding, $string)
    {
        $reset = $color ? $this->colors['reset'] : '';

        return $color . str_pad($string, $padding) . $reset . PHP_EOL;
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage\Report;

use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\Node\File;
use SebastianBergmann\CodeCoverage\InvalidArgumentException;

class Crap4j
{
    /**
     * @var int
     */
    private $threshold;

    /**
     * @param int $threshold
     */
    public function __construct($threshold = 30)
    {
        if (!is_int($threshold)) {
            throw InvalidArgumentException::create(
                1,
                'integer'
            );
        }

        $this->threshold = $threshold;
    }

    /**
     * @param CodeCoverage $coverage
     * @param string       $target
     * @param string       $name
     *
     * @return string
     */
    public function process(CodeCoverage $coverage, $target = null, $name = null)
    {
        $document               = new \DOMDocument('1.0', 'UTF-8');
        $document->formatOutput = true;

        $root = $document->createElement('crap_result');
        $document->appendChild($root);

        $project = $document->createElement('project', is_string($name) ? $name : '');
        $root->appendChild($project);
        $root->appendChild($document->createElement('timestamp', date('Y-m-d H:i:s', (int) $_SERVER['REQUEST_TIME'])));

        $stats       = $document->createElement('stats');
        $methodsNode = $document->createElement('methods');

        $report = $coverage->getReport();
        unset($coverage);

        $fullMethodCount     = 0;
        $fullCrapMethodCount = 0;
        $fullCrapLoad        = 0;
        $fullCrap            = 0;

        foreach ($report as $item) {
            $namespace = 'global';

            if (!$item instanceof File) {
                continue;
            }

            $file = $document->createElement('file');
            $file->setAttribute('name', $item->getPath());

            $classes = $item->getClassesAndTraits();

            foreach ($classes as $className => $class) {
                foreach ($class['methods'] as $methodName => $method) {
                    $crapLoad = $this->getCrapLoad($method['crap'], $method['ccn'], $method['coverage']);

                    $fullCrap     += $method['crap'];
                    $fullCrapLoad += $crapLoad;
                    $fullMethodCount++;

                    if ($method['crap'] >= $this->threshold) {
                        $fullCrapMethodCount++;
                    }

                    $methodNode = $document->createElement('method');

                    if (!empty($class['package']['namespace'])) {
                        $namespace = $class['package']['namespace'];
                    }

                    $methodNode->appendChild($document->createElement('package', $namespace));
                    $methodNode->appendChild($document->createElement('className', $className));
                    $methodNode->appendChild($document->createElement('methodName', $methodName));
                    $methodNode->appendChild($document->createElement('methodSignature', htmlspecialchars($method['signature'])));
                    $methodNode->appendChild($document->createElement('fullMethod', htmlspecialchars($method['signature'])));
                    $methodNode->appendChild($document->createElement('crap', $this->roundValue($method['crap'])));
                    $methodNode->appendChild($document->createElement('complexity', $method['ccn']));
                    $methodNode->appendChild($document->createElement('coverage', $this->roundValue($method['coverage'])));
                    $methodNode->appendChild($document->createElement('crapLoad', round($crapLoad)));

                    $methodsNode->appendChild($methodNode);
                }
            }
        }

        $stats->appendChild($document->createElement('name', 'Method Crap Stats'));
        $stats->appendChild($document->createElement('methodCount', $fullMethodCount));
        $stats->appendChild($document->createElement('crapMethodCount', $fullCrapMethodCount));
        $stats->appendChild($document->createElement('crapLoad', round($fullCrapLoad)));
        $stats->appendChild($document->createElement('totalCrap', $fullCrap));

        if ($fullMethodCount > 0) {
            $crapMethodPercent = $this->roundValue((100 * $fullCrapMethodCount) / $fullMethodCount);
        } else {
            $crapMethodPercent = 0;
        }

        $stats->appendChild($document->createElement('crapMethodPercent', $crapMethodPercent));

        $root->appendChild($stats);
        $root->appendChild($methodsNode);

        $buffer = $document->saveXML();

        if ($target !== null) {
            if (!is_dir(dirname($target))) {
                mkdir(dirname($target), 0777, true);
            }

            file_put_contents($target, $buffer);
        }

        return $buffer;
    }

    /**
     * @param float $crapValue
     * @param int   $cyclomaticComplexity
     * @param float $coveragePercent
     *
     * @return float
     */
    private function getCrapLoad($crapValue, $cyclomaticComplexity, $coveragePercent)
    {
        $crapLoad = 0;

        if ($crapValue >= $this->threshold) {
            $crapLoad += $cyclomaticComplexity * (1.0 - $coveragePercent / 100);
            $crapLoad += $cyclomaticComplexity / $this->threshold;
        }

        return $crapLoad;
    }

    /**
     * @param float $value
     *
     * @return float
     */
    private function roundValue($value)
    {
        return round($value, 2);
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage;

/**
 * Utility methods.
 */
class Util
{
    /**
     * @param float $a
     * @param float $b
     * @param bool  $asString
     * @param bool  $fixedWidth
     *
     * @return float|int|string
     */
    public static function percent($a, $b, $asString = false, $fixedWidth = false)
    {
        if ($asString && $b == 0) {
            return '';
        }

        if ($b > 0) {
            $percent = ($a / $b) * 100;
        } else {
            $percent = 100;
        }

        if ($asString) {
            if ($fixedWidth) {
                return sprintf('%6.2F%%', $percent);
            }

            return sprintf('%01.2F%%', $percent);
        } else {
            return $percent;
        }
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage\Node;

use SebastianBergmann\CodeCoverage\InvalidArgumentException;

/**
 * Represents a file in the code coverage information tree.
 */
class File extends AbstractNode
{
    /**
     * @var array
     */
    private $coverageData;

    /**
     * @var array
     */
    private $testData;

    /**
     * @var int
     */
    private $numExecutableLines = 0;

    /**
     * @var int
     */
    private $numExecutedLines = 0;

    /**
     * @var array
     */
    private $classes = [];

    /**
     * @var array
     */
    private $traits = [];

    /**
     * @var array
     */
    private $functions = [];

    /**
     * @var array
     */
    private $linesOfCode = [];

    /**
     * @var int
     */
    private $numClasses = null;

    /**
     * @var int
     */
    private $numTestedClasses = 0;

    /**
     * @var int
     */
    private $numTraits = null;

    /**
     * @var int
     */
    private $numTestedTraits = 0;

    /**
     * @var int
     */
    private $numMethods = null;

    /**
     * @var int
     */
    private $numTestedMethods = null;

    /**
     * @var int
     */
    private $numTestedFunctions = null;

    /**
     * @var array
     */
    private $startLines = [];

    /**
     * @var array
     */
    private $endLines = [];

    /**
     * @var bool
     */
    private $cacheTokens;

    /**
     * Constructor.
     *
     * @param string       $name
     * @param AbstractNode $parent
     * @param array        $coverageData
     * @param array        $testData
     * @param bool         $cacheTokens
     *
     * @throws InvalidArgumentException
     */
    public function __construct($name, AbstractNode $parent, array $coverageData, array $testData, $cacheTokens)
    {
        if (!is_bool($cacheTokens)) {
            throw InvalidArgumentException::create(
                1,
                'boolean'
            );
        }

        parent::__construct($name, $parent);

        $this->coverageData = $coverageData;
        $this->testData     = $testData;
        $this->cacheTokens  = $cacheTokens;

        $this->calculateStatistics();
    }

    /**
     * Returns the number of files in/under this node.
     *
     * @return int
     */
    public function count()
    {
        return 1;
    }

    /**
     * Returns the code coverage data of this node.
     *
     * @return array
     */
    public function getCoverageData()
    {
        return $this->coverageData;
    }

    /**
     * Returns the test data of this node.
     *
     * @return array
     */
    public function getTestData()
    {
        return $this->testData;
    }

    /**
     * Returns the classes of this node.
     *
     * @return array
     */
    public function getClasses()
    {
        return $this->classes;
    }

    /**
     * Returns the traits of this node.
     *
     * @return array
     */
    public function getTraits()
    {
        return $this->traits;
    }

    /**
     * Returns the functions of this node.
     *
     * @return array
     */
    public function getFunctions()
    {
        return $this->functions;
    }

    /**
     * Returns the LOC/CLOC/NCLOC of this node.
     *
     * @return array
     */
    public function getLinesOfCode()
    {
        return $this->linesOfCode;
    }

    /**
     * Returns the number of executable lines.
     *
     * @return int
     */
    public function getNumExecutableLines()
    {
        return $this->numExecutableLines;
    }

    /**
     * Returns the number of executed lines.
     *
     * @return int
     */
    public function getNumExecutedLines()
    {
        return $this->numExecutedLines;
    }

    /**
     * Returns the number of classes.
     *
     * @return int
     */
    public function getNumClasses()
    {
        if ($this->numClasses === null) {
            $this->numClasses = 0;

            foreach ($this->classes as $class) {
                foreach ($class['methods'] as $method) {
                    if ($method['executableLines'] > 0) {
                        $this->numClasses++;

                        continue 2;
                    }
                }
            }
        }

        return $this->numClasses;
    }

    /**
     * Returns the number of tested classes.
     *
     * @return int
     */
    public function getNumTestedClasses()
    {
        return $this->numTestedClasses;
    }

    /**
     * Returns the number of traits.
     *
     * @return int
     */
    public function getNumTraits()
    {
        if ($this->numTraits === null) {
            $this->numTraits = 0;

            foreach ($this->traits as $trait) {
                foreach ($trait['methods'] as $method) {
                    if ($method['executableLines'] > 0) {
                        $this->numTraits++;

                        continue 2;
                    }
                }
            }
        }

        return $this->numTraits;
    }

    /**
     * Returns the number of tested traits.
     *
     * @return int
     */
    public function getNumTestedTraits()
    {
        return $this->numTestedTraits;
    }

    /**
     * Returns the number of methods.
     *
     * @return int
     */
    public function getNumMethods()
    {
        if ($this->numMethods === null) {
            $this->numMethods = 0;

            foreach ($this->classes as $class) {
                foreach ($class['methods'] as $method) {
                    if ($method['executableLines'] > 0) {
                        $this->numMethods++;
                    }
                }
            }

            foreach ($this->traits as $trait) {
                foreach ($trait['methods'] as $method) {
                    if ($method['executableLines'] > 0) {
                        $this->numMethods++;
                    }
                }
            }
        }

        return $this->numMethods;
    }

    /**
     * Returns the number of tested methods.
     *
     * @return int
     */
    public function getNumTestedMethods()
    {
        if ($this->numTestedMethods === null) {
            $this->numTestedMethods = 0;

            foreach ($this->classes as $class) {
                foreach ($class['methods'] as $method) {
                    if ($method['executableLines'] > 0 &&
                        $method['coverage'] == 100) {
                        $this->numTestedMethods++;
                    }
                }
            }

            foreach ($this->traits as $trait) {
                foreach ($trait['methods'] as $method) {
                    if ($method['executableLines'] > 0 &&
                        $method['coverage'] == 100) {
                        $this->numTestedMethods++;
                    }
                }
            }
        }

        return $this->numTestedMethods;
    }

    /**
     * Returns the number of functions.
     *
     * @return int
     */
    public function getNumFunctions()
    {
        return count($this->functions);
    }

    /**
     * Returns the number of tested functions.
     *
     * @return int
     */
    public function getNumTestedFunctions()
    {
        if ($this->numTestedFunctions === null) {
            $this->numTestedFunctions = 0;

            foreach ($this->functions as $function) {
                if ($function['executableLines'] > 0 &&
                    $function['coverage'] == 100) {
                    $this->numTestedFunctions++;
                }
            }
        }

        return $this->numTestedFunctions;
    }

    /**
     * Calculates coverage statistics for the file.
     */
    protected function calculateStatistics()
    {
        $classStack = $functionStack = [];

        if ($this->cacheTokens) {
            $tokens = \PHP_Token_Stream_CachingFactory::get($this->getPath());
        } else {
            $tokens = new \PHP_Token_Stream($this->getPath());
        }

        $this->processClasses($tokens);
        $this->processTraits($tokens);
        $this->processFunctions($tokens);
        $this->linesOfCode = $tokens->getLinesOfCode();
        unset($tokens);

        for ($lineNumber = 1; $lineNumber <= $this->linesOfCode['loc']; $lineNumber++) {
            if (isset($this->startLines[$lineNumber])) {
                // Start line of a class.
                if (isset($this->startLines[$lineNumber]['className'])) {
                    if (isset($currentClass)) {
                        $classStack[] = &$currentClass;
                    }

                    $currentClass = &$this->startLines[$lineNumber];
                } // Start line of a trait.
                elseif (isset($this->startLines[$lineNumber]['traitName'])) {
                    $currentTrait = &$this->startLines[$lineNumber];
                } // Start line of a method.
                elseif (isset($this->startLines[$lineNumber]['methodName'])) {
                    $currentMethod = &$this->startLines[$lineNumber];
                } // Start line of a function.
                elseif (isset($this->startLines[$lineNumber]['functionName'])) {
                    if (isset($currentFunction)) {
                        $functionStack[] = &$currentFunction;
                    }

                    $currentFunction = &$this->startLines[$lineNumber];
                }
            }

            if (isset($this->coverageData[$lineNumber])) {
                if (isset($currentClass)) {
                    $currentClass['executableLines']++;
                }

                if (isset($currentTrait)) {
                    $currentTrait['executableLines']++;
                }

                if (isset($currentMethod)) {
                    $currentMethod['executableLines']++;
                }

                if (isset($currentFunction)) {
                    $currentFunction['executableLines']++;
                }

                $this->numExecutableLines++;

                if (count($this->coverageData[$lineNumber]) > 0) {
                    if (isset($currentClass)) {
                        $currentClass['executedLines']++;
                    }

                    if (isset($currentTrait)) {
                        $currentTrait['executedLines']++;
                    }

                    if (isset($currentMethod)) {
                        $currentMethod['executedLines']++;
                    }

                    if (isset($currentFunction)) {
                        $currentFunction['executedLines']++;
                    }

                    $this->numExecutedLines++;
                }
            }

            if (isset($this->endLines[$lineNumber])) {
                // End line of a class.
                if (isset($this->endLines[$lineNumber]['className'])) {
                    unset($currentClass);

                    if ($classStack) {
                        end($classStack);
                        $key          = key($classStack);
                        $currentClass = &$classStack[$key];
                        unset($classStack[$key]);
                    }
                } // End line of a trait.
                elseif (isset($this->endLines[$lineNumber]['traitName'])) {
                    unset($currentTrait);
                } // End line of a method.
                elseif (isset($this->endLines[$lineNumber]['methodName'])) {
                    unset($currentMethod);
                } // End line of a function.
                elseif (isset($this->endLines[$lineNumber]['functionName'])) {
                    unset($currentFunction);

                    if ($functionStack) {
                        end($functionStack);
                        $key             = key($functionStack);
                        $currentFunction = &$functionStack[$key];
                        unset($functionStack[$key]);
                    }
                }
            }
        }

        foreach ($this->traits as &$trait) {
            foreach ($trait['methods'] as &$method) {
                if ($method['executableLines'] > 0) {
                    $method['coverage'] = ($method['executedLines'] /
                            $method['executableLines']) * 100;
                } else {
                    $method['coverage'] = 100;
                }

                $method['crap'] = $this->crap(
                    $method['ccn'],
                    $method['coverage']
                );

                $trait['ccn'] += $method['ccn'];
            }

            if ($trait['executableLines'] > 0) {
                $trait['coverage'] = ($trait['executedLines'] /
                        $trait['executableLines']) * 100;

                if ($trait['coverage'] == 100) {
                    $this->numTestedClasses++;
                }
            } else {
                $trait['coverage'] = 100;
            }

            $trait['crap'] = $this->crap(
                $trait['ccn'],
                $trait['coverage']
            );
        }

        foreach ($this->classes as &$class) {
            foreach ($class['methods'] as &$method) {
                if ($method['executableLines'] > 0) {
                    $method['coverage'] = ($method['executedLines'] /
                            $method['executableLines']) * 100;
                } else {
                    $method['coverage'] = 100;
                }

                $method['crap'] = $this->crap(
                    $method['ccn'],
                    $method['coverage']
                );

                $class['ccn'] += $method['ccn'];
            }

            if ($class['executableLines'] > 0) {
                $class['coverage'] = ($class['executedLines'] /
                        $class['executableLines']) * 100;

                if ($class['coverage'] == 100) {
                    $this->numTestedClasses++;
                }
            } else {
                $class['coverage'] = 100;
            }

            $class['crap'] = $this->crap(
                $class['ccn'],
                $class['coverage']
            );
        }
    }

    /**
     * @param \PHP_Token_Stream $tokens
     */
    protected function processClasses(\PHP_Token_Stream $tokens)
    {
        $classes = $tokens->getClasses();
        unset($tokens);

        $link = $this->getId() . '.html#';

        foreach ($classes as $className => $class) {
            $this->classes[$className] = [
                'className'       => $className,
                'methods'         => [],
                'startLine'       => $class['startLine'],
                'executableLines' => 0,
                'executedLines'   => 0,
                'ccn'             => 0,
                'coverage'        => 0,
                'crap'            => 0,
                'package'         => $class['package'],
                'link'            => $link . $class['startLine']
            ];

            $this->startLines[$class['startLine']] = &$this->classes[$className];
            $this->endLines[$class['endLine']]     = &$this->classes[$className];

            foreach ($class['methods'] as $methodName => $method) {
                $this->classes[$className]['methods'][$methodName] = $this->newMethod($methodName, $method, $link);

                $this->startLines[$method['startLine']] = &$this->classes[$className]['methods'][$methodName];
                $this->endLines[$method['endLine']]     = &$this->classes[$className]['methods'][$methodName];
            }
        }
    }

    /**
     * @param \PHP_Token_Stream $tokens
     */
    protected function processTraits(\PHP_Token_Stream $tokens)
    {
        $traits = $tokens->getTraits();
        unset($tokens);

        $link = $this->getId() . '.html#';

        foreach ($traits as $traitName => $trait) {
            $this->traits[$traitName] = [
                'traitName'       => $traitName,
                'methods'         => [],
                'startLine'       => $trait['startLine'],
                'executableLines' => 0,
                'executedLines'   => 0,
                'ccn'             => 0,
                'coverage'        => 0,
                'crap'            => 0,
                'package'         => $trait['package'],
                'link'            => $link . $trait['startLine']
            ];

            $this->startLines[$trait['startLine']] = &$this->traits[$traitName];
            $this->endLines[$trait['endLine']]     = &$this->traits[$traitName];

            foreach ($trait['methods'] as $methodName => $method) {
                $this->traits[$traitName]['methods'][$methodName] = $this->newMethod($methodName, $method, $link);

                $this->startLines[$method['startLine']] = &$this->traits[$traitName]['methods'][$methodName];
                $this->endLines[$method['endLine']]     = &$this->traits[$traitName]['methods'][$methodName];
            }
        }
    }

    /**
     * @param \PHP_Token_Stream $tokens
     */
    protected function processFunctions(\PHP_Token_Stream $tokens)
    {
        $functions = $tokens->getFunctions();
        unset($tokens);

        $link = $this->getId() . '.html#';

        foreach ($functions as $functionName => $function) {
            $this->functions[$functionName] = [
                'functionName'    => $functionName,
                'signature'       => $function['signature'],
                'startLine'       => $function['startLine'],
                'executableLines' => 0,
                'executedLines'   => 0,
                'ccn'             => $function['ccn'],
                'coverage'        => 0,
                'crap'            => 0,
                'link'            => $link . $function['startLine']
            ];

            $this->startLines[$function['startLine']] = &$this->functions[$functionName];
            $this->endLines[$function['endLine']]     = &$this->functions[$functionName];
        }
    }

    /**
     * Calculates the Change Risk Anti-Patterns (CRAP) index for a unit of code
     * based on its cyclomatic complexity and percentage of code coverage.
     *
     * @param int   $ccn
     * @param float $coverage
     *
     * @return string
     */
    protected function crap($ccn, $coverage)
    {
        if ($coverage == 0) {
            return (string) (pow($ccn, 2) + $ccn);
        }

        if ($coverage >= 95) {
            return (string) $ccn;
        }

        return sprintf(
            '%01.2F',
            pow($ccn, 2) * pow(1 - $coverage/100, 3) + $ccn
        );
    }

    /**
     * @param string $methodName
     * @param array  $method
     * @param string $link
     *
     * @return array
     */
    private function newMethod($methodName, array $method, $link)
    {
        return [
            'methodName'      => $methodName,
            'visibility'      => $method['visibility'],
            'signature'       => $method['signature'],
            'startLine'       => $method['startLine'],
            'endLine'         => $method['endLine'],
            'executableLines' => 0,
            'executedLines'   => 0,
            'ccn'             => $method['ccn'],
            'coverage'        => 0,
            'crap'            => 0,
            'link'            => $link . $method['startLine'],
        ];
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage\Node;

use SebastianBergmann\CodeCoverage\CodeCoverage;

class Builder
{
    /**
     * @param CodeCoverage $coverage
     *
     * @return Directory
     */
    public function build(CodeCoverage $coverage)
    {
        $files      = $coverage->getData();
        $commonPath = $this->reducePaths($files);
        $root       = new Directory(
            $commonPath,
            null
        );

        $this->addItems(
            $root,
            $this->buildDirectoryStructure($files),
            $coverage->getTests(),
            $coverage->getCacheTokens()
        );

        return $root;
    }

    /**
     * @param Directory $root
     * @param array     $items
     * @param array     $tests
     * @param bool      $cacheTokens
     */
    private function addItems(Directory $root, array $items, array $tests, $cacheTokens)
    {
        foreach ($items as $key => $value) {
            if (substr($key, -2) == '/f') {
                $key = substr($key, 0, -2);

                if (file_exists($root->getPath() . DIRECTORY_SEPARATOR . $key)) {
                    $root->addFile($key, $value, $tests, $cacheTokens);
                }
            } else {
                $child = $root->addDirectory($key);
                $this->addItems($child, $value, $tests, $cacheTokens);
            }
        }
    }

    /**
     * Builds an array representation of the directory structure.
     *
     * For instance,
     *
     * <code>
     * Array
     * (
     *     [Money.php] => Array
     *         (
     *             ...
     *         )
     *
     *     [MoneyBag.php] => Array
     *         (
     *             ...
     *         )
     * )
     * </code>
     *
     * is transformed into
     *
     * <code>
     * Array
     * (
     *     [.] => Array
     *         (
     *             [Money.php] => Array
     *                 (
     *                     ...
     *                 )
     *
     *             [MoneyBag.php] => Array
     *                 (
     *                     ...
     *                 )
     *         )
     * )
     * </code>
     *
     * @param array $files
     *
     * @return array
     */
    private function buildDirectoryStructure($files)
    {
        $result = [];

        foreach ($files as $path => $file) {
            $path    = explode('/', $path);
            $pointer = &$result;
            $max     = count($path);

            for ($i = 0; $i < $max; $i++) {
                if ($i == ($max - 1)) {
                    $type = '/f';
                } else {
                    $type = '';
                }

                $pointer = &$pointer[$path[$i] . $type];
            }

            $pointer = $file;
        }

        return $result;
    }

    /**
     * Reduces the paths by cutting the longest common start path.
     *
     * For instance,
     *
     * <code>
     * Array
     * (
     *     [/home/sb/Money/Money.php] => Array
     *         (
     *             ...
     *         )
     *
     *     [/home/sb/Money/MoneyBag.php] => Array
     *         (
     *             ...
     *         )
     * )
     * </code>
     *
     * is reduced to
     *
     * <code>
     * Array
     * (
     *     [Money.php] => Array
     *         (
     *             ...
     *         )
     *
     *     [MoneyBag.php] => Array
     *         (
     *             ...
     *         )
     * )
     * </code>
     *
     * @param array $files
     *
     * @return string
     */
    private function reducePaths(&$files)
    {
        if (empty($files)) {
            return '.';
        }

        $commonPath = '';
        $paths      = array_keys($files);

        if (count($files) == 1) {
            $commonPath                 = dirname($paths[0]) . '/';
            $files[basename($paths[0])] = $files[$paths[0]];

            unset($files[$paths[0]]);

            return $commonPath;
        }

        $max = count($paths);

        for ($i = 0; $i < $max; $i++) {
            // strip phar:// prefixes
            if (strpos($paths[$i], 'phar://') === 0) {
                $paths[$i] = substr($paths[$i], 7);
                $paths[$i] = strtr($paths[$i], '/', DIRECTORY_SEPARATOR);
            }
            $paths[$i] = explode(DIRECTORY_SEPARATOR, $paths[$i]);

            if (empty($paths[$i][0])) {
                $paths[$i][0] = DIRECTORY_SEPARATOR;
            }
        }

        $done = false;
        $max  = count($paths);

        while (!$done) {
            for ($i = 0; $i < $max - 1; $i++) {
                if (!isset($paths[$i][0]) ||
                    !isset($paths[$i+1][0]) ||
                    $paths[$i][0] != $paths[$i+1][0]) {
                    $done = true;
                    break;
                }
            }

            if (!$done) {
                $commonPath .= $paths[0][0];

                if ($paths[0][0] != DIRECTORY_SEPARATOR) {
                    $commonPath .= DIRECTORY_SEPARATOR;
                }

                for ($i = 0; $i < $max; $i++) {
                    array_shift($paths[$i]);
                }
            }
        }

        $original = array_keys($files);
        $max      = count($original);

        for ($i = 0; $i < $max; $i++) {
            $files[implode('/', $paths[$i])] = $files[$original[$i]];
            unset($files[$original[$i]]);
        }

        ksort($files);

        return substr($commonPath, 0, -1);
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage\Node;

use SebastianBergmann\CodeCoverage\Util;

/**
 * Base class for nodes in the code coverage information tree.
 */
abstract class AbstractNode implements \Countable
{
    /**
     * @var string
     */
    private $name;

    /**
     * @var string
     */
    private $path;

    /**
     * @var array
     */
    private $pathArray;

    /**
     * @var AbstractNode
     */
    private $parent;

    /**
     * @var string
     */
    private $id;

    /**
     * Constructor.
     *
     * @param string       $name
     * @param AbstractNode $parent
     */
    public function __construct($name, AbstractNode $parent = null)
    {
        if (substr($name, -1) == '/') {
            $name = substr($name, 0, -1);
        }

        $this->name   = $name;
        $this->parent = $parent;
    }

    /**
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @return string
     */
    public function getId()
    {
        if ($this->id === null) {
            $parent = $this->getParent();

            if ($parent === null) {
                $this->id = 'index';
            } else {
                $parentId = $parent->getId();

                if ($parentId == 'index') {
                    $this->id = str_replace(':', '_', $this->name);
                } else {
                    $this->id = $parentId . '/' . $this->name;
                }
            }
        }

        return $this->id;
    }

    /**
     * @return string
     */
    public function getPath()
    {
        if ($this->path === null) {
            if ($this->parent === null || $this->parent->getPath() === null || $this->parent->getPath() === false) {
                $this->path = $this->name;
            } else {
                $this->path = $this->parent->getPath() . '/' . $this->name;
            }
        }

        return $this->path;
    }

    /**
     * @return array
     */
    public function getPathAsArray()
    {
        if ($this->pathArray === null) {
            if ($this->parent === null) {
                $this->pathArray = [];
            } else {
                $this->pathArray = $this->parent->getPathAsArray();
            }

            $this->pathArray[] = $this;
        }

        return $this->pathArray;
    }

    /**
     * @return AbstractNode
     */
    public function getParent()
    {
        return $this->parent;
    }

    /**
     * Returns the percentage of classes that has been tested.
     *
     * @param bool $asString
     *
     * @return int
     */
    public function getTestedClassesPercent($asString = true)
    {
        return Util::percent(
            $this->getNumTestedClasses(),
            $this->getNumClasses(),
            $asString
        );
    }

    /**
     * Returns the percentage of traits that has been tested.
     *
     * @param bool $asString
     *
     * @return int
     */
    public function getTestedTraitsPercent($asString = true)
    {
        return Util::percent(
            $this->getNumTestedTraits(),
            $this->getNumTraits(),
            $asString
        );
    }

    /**
     * Returns the percentage of traits that has been tested.
     *
     * @param bool $asString
     *
     * @return int
     */
    public function getTestedClassesAndTraitsPercent($asString = true)
    {
        return Util::percent(
            $this->getNumTestedClassesAndTraits(),
            $this->getNumClassesAndTraits(),
            $asString
        );
    }

    /**
     * Returns the percentage of methods that has been tested.
     *
     * @param bool $asString
     *
     * @return int
     */
    public function getTestedMethodsPercent($asString = true)
    {
        return Util::percent(
            $this->getNumTestedMethods(),
            $this->getNumMethods(),
            $asString
        );
    }

    /**
     * Returns the percentage of executed lines.
     *
     * @param bool $asString
     *
     * @return int
     */
    public function getLineExecutedPercent($asString = true)
    {
        return Util::percent(
            $this->getNumExecutedLines(),
            $this->getNumExecutableLines(),
            $asString
        );
    }

    /**
     * Returns the number of classes and traits.
     *
     * @return int
     */
    public function getNumClassesAndTraits()
    {
        return $this->getNumClasses() + $this->getNumTraits();
    }

    /**
     * Returns the number of tested classes and traits.
     *
     * @return int
     */
    public function getNumTestedClassesAndTraits()
    {
        return $this->getNumTestedClasses() + $this->getNumTestedTraits();
    }

    /**
     * Returns the classes and traits of this node.
     *
     * @return array
     */
    public function getClassesAndTraits()
    {
        return array_merge($this->getClasses(), $this->getTraits());
    }

    /**
     * Returns the classes of this node.
     *
     * @return array
     */
    abstract public function getClasses();

    /**
     * Returns the traits of this node.
     *
     * @return array
     */
    abstract public function getTraits();

    /**
     * Returns the functions of this node.
     *
     * @return array
     */
    abstract public function getFunctions();

    /**
     * Returns the LOC/CLOC/NCLOC of this node.
     *
     * @return array
     */
    abstract public function getLinesOfCode();

    /**
     * Returns the number of executable lines.
     *
     * @return int
     */
    abstract public function getNumExecutableLines();

    /**
     * Returns the number of executed lines.
     *
     * @return int
     */
    abstract public function getNumExecutedLines();

    /**
     * Returns the number of classes.
     *
     * @return int
     */
    abstract public function getNumClasses();

    /**
     * Returns the number of tested classes.
     *
     * @return int
     */
    abstract public function getNumTestedClasses();

    /**
     * Returns the number of traits.
     *
     * @return int
     */
    abstract public function getNumTraits();

    /**
     * Returns the number of tested traits.
     *
     * @return int
     */
    abstract public function getNumTestedTraits();

    /**
     * Returns the number of methods.
     *
     * @return int
     */
    abstract public function getNumMethods();

    /**
     * Returns the number of tested methods.
     *
     * @return int
     */
    abstract public function getNumTestedMethods();

    /**
     * Returns the number of functions.
     *
     * @return int
     */
    abstract public function getNumFunctions();

    /**
     * Returns the number of tested functions.
     *
     * @return int
     */
    abstract public function getNumTestedFunctions();
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage\Node;

use SebastianBergmann\CodeCoverage\InvalidArgumentException;

/**
 * Represents a directory in the code coverage information tree.
 */
class Directory extends AbstractNode implements \IteratorAggregate
{
    /**
     * @var AbstractNode[]
     */
    private $children = [];

    /**
     * @var Directory[]
     */
    private $directories = [];

    /**
     * @var File[]
     */
    private $files = [];

    /**
     * @var array
     */
    private $classes;

    /**
     * @var array
     */
    private $traits;

    /**
     * @var array
     */
    private $functions;

    /**
     * @var array
     */
    private $linesOfCode = null;

    /**
     * @var int
     */
    private $numFiles = -1;

    /**
     * @var int
     */
    private $numExecutableLines = -1;

    /**
     * @var int
     */
    private $numExecutedLines = -1;

    /**
     * @var int
     */
    private $numClasses = -1;

    /**
     * @var int
     */
    private $numTestedClasses = -1;

    /**
     * @var int
     */
    private $numTraits = -1;

    /**
     * @var int
     */
    private $numTestedTraits = -1;

    /**
     * @var int
     */
    private $numMethods = -1;

    /**
     * @var int
     */
    private $numTestedMethods = -1;

    /**
     * @var int
     */
    private $numFunctions = -1;

    /**
     * @var int
     */
    private $numTestedFunctions = -1;

    /**
     * Returns the number of files in/under this node.
     *
     * @return int
     */
    public function count()
    {
        if ($this->numFiles == -1) {
            $this->numFiles = 0;

            foreach ($this->children as $child) {
                $this->numFiles += count($child);
            }
        }

        return $this->numFiles;
    }

    /**
     * Returns an iterator for this node.
     *
     * @return \RecursiveIteratorIterator
     */
    public function getIterator()
    {
        return new \RecursiveIteratorIterator(
            new Iterator($this),
            \RecursiveIteratorIterator::SELF_FIRST
        );
    }

    /**
     * Adds a new directory.
     *
     * @param string $name
     *
     * @return Directory
     */
    public function addDirectory($name)
    {
        $directory = new self($name, $this);

        $this->children[]    = $directory;
        $this->directories[] = &$this->children[count($this->children) - 1];

        return $directory;
    }

    /**
     * Adds a new file.
     *
     * @param string $name
     * @param array  $coverageData
     * @param array  $testData
     * @param bool   $cacheTokens
     *
     * @return File
     *
     * @throws InvalidArgumentException
     */
    public function addFile($name, array $coverageData, array $testData, $cacheTokens)
    {
        $file = new File(
            $name,
            $this,
            $coverageData,
            $testData,
            $cacheTokens
        );

        $this->children[] = $file;
        $this->files[]    = &$this->children[count($this->children) - 1];

        $this->numExecutableLines = -1;
        $this->numExecutedLines   = -1;

        return $file;
    }

    /**
     * Returns the directories in this directory.
     *
     * @return array
     */
    public function getDirectories()
    {
        return $this->directories;
    }

    /**
     * Returns the files in this directory.
     *
     * @return array
     */
    public function getFiles()
    {
        return $this->files;
    }

    /**
     * Returns the child nodes of this node.
     *
     * @return array
     */
    public function getChildNodes()
    {
        return $this->children;
    }

    /**
     * Returns the classes of this node.
     *
     * @return array
     */
    public function getClasses()
    {
        if ($this->classes === null) {
            $this->classes = [];

            foreach ($this->children as $child) {
                $this->classes = array_merge(
                    $this->classes,
                    $child->getClasses()
                );
            }
        }

        return $this->classes;
    }

    /**
     * Returns the traits of this node.
     *
     * @return array
     */
    public function getTraits()
    {
        if ($this->traits === null) {
            $this->traits = [];

            foreach ($this->children as $child) {
                $this->traits = array_merge(
                    $this->traits,
                    $child->getTraits()
                );
            }
        }

        return $this->traits;
    }

    /**
     * Returns the functions of this node.
     *
     * @return array
     */
    public function getFunctions()
    {
        if ($this->functions === null) {
            $this->functions = [];

            foreach ($this->children as $child) {
                $this->functions = array_merge(
                    $this->functions,
                    $child->getFunctions()
                );
            }
        }

        return $this->functions;
    }

    /**
     * Returns the LOC/CLOC/NCLOC of this node.
     *
     * @return array
     */
    public function getLinesOfCode()
    {
        if ($this->linesOfCode === null) {
            $this->linesOfCode = ['loc' => 0, 'cloc' => 0, 'ncloc' => 0];

            foreach ($this->children as $child) {
                $linesOfCode = $child->getLinesOfCode();

                $this->linesOfCode['loc']   += $linesOfCode['loc'];
                $this->linesOfCode['cloc']  += $linesOfCode['cloc'];
                $this->linesOfCode['ncloc'] += $linesOfCode['ncloc'];
            }
        }

        return $this->linesOfCode;
    }

    /**
     * Returns the number of executable lines.
     *
     * @return int
     */
    public function getNumExecutableLines()
    {
        if ($this->numExecutableLines == -1) {
            $this->numExecutableLines = 0;

            foreach ($this->children as $child) {
                $this->numExecutableLines += $child->getNumExecutableLines();
            }
        }

        return $this->numExecutableLines;
    }

    /**
     * Returns the number of executed lines.
     *
     * @return int
     */
    public function getNumExecutedLines()
    {
        if ($this->numExecutedLines == -1) {
            $this->numExecutedLines = 0;

            foreach ($this->children as $child) {
                $this->numExecutedLines += $child->getNumExecutedLines();
            }
        }

        return $this->numExecutedLines;
    }

    /**
     * Returns the number of classes.
     *
     * @return int
     */
    public function getNumClasses()
    {
        if ($this->numClasses == -1) {
            $this->numClasses = 0;

            foreach ($this->children as $child) {
                $this->numClasses += $child->getNumClasses();
            }
        }

        return $this->numClasses;
    }

    /**
     * Returns the number of tested classes.
     *
     * @return int
     */
    public function getNumTestedClasses()
    {
        if ($this->numTestedClasses == -1) {
            $this->numTestedClasses = 0;

            foreach ($this->children as $child) {
                $this->numTestedClasses += $child->getNumTestedClasses();
            }
        }

        return $this->numTestedClasses;
    }

    /**
     * Returns the number of traits.
     *
     * @return int
     */
    public function getNumTraits()
    {
        if ($this->numTraits == -1) {
            $this->numTraits = 0;

            foreach ($this->children as $child) {
                $this->numTraits += $child->getNumTraits();
            }
        }

        return $this->numTraits;
    }

    /**
     * Returns the number of tested traits.
     *
     * @return int
     */
    public function getNumTestedTraits()
    {
        if ($this->numTestedTraits == -1) {
            $this->numTestedTraits = 0;

            foreach ($this->children as $child) {
                $this->numTestedTraits += $child->getNumTestedTraits();
            }
        }

        return $this->numTestedTraits;
    }

    /**
     * Returns the number of methods.
     *
     * @return int
     */
    public function getNumMethods()
    {
        if ($this->numMethods == -1) {
            $this->numMethods = 0;

            foreach ($this->children as $child) {
                $this->numMethods += $child->getNumMethods();
            }
        }

        return $this->numMethods;
    }

    /**
     * Returns the number of tested methods.
     *
     * @return int
     */
    public function getNumTestedMethods()
    {
        if ($this->numTestedMethods == -1) {
            $this->numTestedMethods = 0;

            foreach ($this->children as $child) {
                $this->numTestedMethods += $child->getNumTestedMethods();
            }
        }

        return $this->numTestedMethods;
    }

    /**
     * Returns the number of functions.
     *
     * @return int
     */
    public function getNumFunctions()
    {
        if ($this->numFunctions == -1) {
            $this->numFunctions = 0;

            foreach ($this->children as $child) {
                $this->numFunctions += $child->getNumFunctions();
            }
        }

        return $this->numFunctions;
    }

    /**
     * Returns the number of tested functions.
     *
     * @return int
     */
    public function getNumTestedFunctions()
    {
        if ($this->numTestedFunctions == -1) {
            $this->numTestedFunctions = 0;

            foreach ($this->children as $child) {
                $this->numTestedFunctions += $child->getNumTestedFunctions();
            }
        }

        return $this->numTestedFunctions;
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage\Node;

/**
 * Recursive iterator for node object graphs.
 */
class Iterator implements \RecursiveIterator
{
    /**
     * @var int
     */
    private $position;

    /**
     * @var AbstractNode[]
     */
    private $nodes;

    /**
     * @param Directory $node
     */
    public function __construct(Directory $node)
    {
        $this->nodes = $node->getChildNodes();
    }

    /**
     * Rewinds the Iterator to the first element.
     */
    public function rewind()
    {
        $this->position = 0;
    }

    /**
     * Checks if there is a current element after calls to rewind() or next().
     *
     * @return bool
     */
    public function valid()
    {
        return $this->position < count($this->nodes);
    }

    /**
     * Returns the key of the current element.
     *
     * @return int
     */
    public function key()
    {
        return $this->position;
    }

    /**
     * Returns the current element.
     *
     * @return \PHPUnit_Framework_Test
     */
    public function current()
    {
        return $this->valid() ? $this->nodes[$this->position] : null;
    }

    /**
     * Moves forward to next element.
     */
    public function next()
    {
        $this->position++;
    }

    /**
     * Returns the sub iterator for the current element.
     *
     * @return Iterator
     */
    public function getChildren()
    {
        return new self(
            $this->nodes[$this->position]
        );
    }

    /**
     * Checks whether the current element has children.
     *
     * @return bool
     */
    public function hasChildren()
    {
        return $this->nodes[$this->position] instanceof Directory;
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage;

/**
 * Exception that is raised when code is unintentionally covered.
 */
class UnintentionallyCoveredCodeException extends RuntimeException
{
    /**
     * @var array
     */
    private $unintentionallyCoveredUnits = [];

    /**
     * @param array $unintentionallyCoveredUnits
     */
    public function __construct(array $unintentionallyCoveredUnits)
    {
        $this->unintentionallyCoveredUnits = $unintentionallyCoveredUnits;

        parent::__construct($this->toString());
    }

    /**
     * @return array
     */
    public function getUnintentionallyCoveredUnits()
    {
        return $this->unintentionallyCoveredUnits;
    }

    /**
     * @return string
     */
    private function toString()
    {
        $message = '';

        foreach ($this->unintentionallyCoveredUnits as $unit) {
            $message .= '- ' . $unit . "\n";
        }

        return $message;
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage;

class RuntimeException extends \RuntimeException implements Exception
{
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage;

/**
 * Exception that is raised when covered code is not executed.
 */
class CoveredCodeNotExecutedException extends RuntimeException
{
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage;

class InvalidArgumentException extends \InvalidArgumentException implements Exception
{
    /**
     * @param int    $argument
     * @param string $type
     * @param mixed  $value
     *
     * @return InvalidArgumentException
     */
    public static function create($argument, $type, $value = null)
    {
        $stack = debug_backtrace(0);

        return new self(
            sprintf(
                'Argument #%d%sof %s::%s() must be a %s',
                $argument,
                $value !== null ? ' (' . gettype($value) . '#' . $value . ')' : ' (No Value) ',
                $stack[1]['class'],
                $stack[1]['function'],
                $type
            )
        );
    }
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage;

/**
 * Exception that is raised when @covers must be used but is not.
 */
class MissingCoversAnnotationException extends RuntimeException
{
}
<?php
/*
 * This file is part of the php-code-coverage package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeCoverage;

/**
 * Exception interface for php-code-coverage component.
 */
interface Exception
{
}
{
    "name": "phpunit/php-file-iterator",
    "description": "FilterIterator implementation that filters files based on a list of suffixes.",
    "type": "library",
    "keywords": [
        "iterator",
        "filesystem"
    ],
    "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
    "license": "BSD-3-Clause",
    "authors": [
        {
            "name": "Sebastian Bergmann",
            "email": "sb@sebastian-bergmann.de",
            "role": "lead"
        }
    ],
    "support": {
        "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
        "irc": "irc://irc.freenode.net/phpunit"
    },
    "require": {
        "php": ">=5.3.3"
    },
    "autoload": {
        "classmap": [
            "src/"
        ]
    },
    "extra": {
        "branch-alias": {
            "dev-master": "1.4.x-dev"
        }
    }
}

<?php
/*
 * This file is part of the File_Iterator package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Factory Method implementation that creates a File_Iterator that operates on
 * an AppendIterator that contains an RecursiveDirectoryIterator for each given
 * path.
 *
 * @since     Class available since Release 1.1.0
 */
class File_Iterator_Factory
{
    /**
     * @param  array|string   $paths
     * @param  array|string   $suffixes
     * @param  array|string   $prefixes
     * @param  array          $exclude
     * @return AppendIterator
     */
    public function getFileIterator($paths, $suffixes = '', $prefixes = '', array $exclude = array())
    {
        if (is_string($paths)) {
            $paths = array($paths);
        }

        $paths   = $this->getPathsAfterResolvingWildcards($paths);
        $exclude = $this->getPathsAfterResolvingWildcards($exclude);

        if (is_string($prefixes)) {
            if ($prefixes != '') {
                $prefixes = array($prefixes);
            } else {
                $prefixes = array();
            }
        }

        if (is_string($suffixes)) {
            if ($suffixes != '') {
                $suffixes = array($suffixes);
            } else {
                $suffixes = array();
            }
        }

        $iterator = new AppendIterator;

        foreach ($paths as $path) {
            if (is_dir($path)) {
                $iterator->append(
                  new File_Iterator(
                    new RecursiveIteratorIterator(
                      new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::FOLLOW_SYMLINKS)
                    ),
                    $suffixes,
                    $prefixes,
                    $exclude,
                    $path
                  )
                );
            }
        }

        return $iterator;
    }

    /**
     * @param  array $paths
     * @return array
     */
    protected function getPathsAfterResolvingWildcards(array $paths)
    {
        $_paths = array();

        foreach ($paths as $path) {
            if ($locals = glob($path, GLOB_ONLYDIR)) {
                $_paths = array_merge($_paths, array_map('realpath', $locals));
            } else {
                $_paths[] = realpath($path);
            }
        }

        return $_paths;
    }
}
<?php
/*
 * This file is part of the File_Iterator package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * FilterIterator implementation that filters files based on prefix(es) and/or
 * suffix(es). Hidden files and files from hidden directories are also filtered.
 *
 * @since     Class available since Release 1.0.0
 */
class File_Iterator extends FilterIterator
{
    const PREFIX = 0;
    const SUFFIX = 1;

    /**
     * @var array
     */
    protected $suffixes = array();

    /**
     * @var array
     */
    protected $prefixes = array();

    /**
     * @var array
     */
    protected $exclude = array();

    /**
     * @var string
     */
    protected $basepath;

    /**
     * @param Iterator $iterator
     * @param array    $suffixes
     * @param array    $prefixes
     * @param array    $exclude
     * @param string   $basepath
     */
    public function __construct(Iterator $iterator, array $suffixes = array(), array $prefixes = array(), array $exclude = array(), $basepath = NULL)
    {
        $exclude = array_filter(array_map('realpath', $exclude));

        if ($basepath !== NULL) {
            $basepath = realpath($basepath);
        }

        if ($basepath === FALSE) {
            $basepath = NULL;
        } else {
            foreach ($exclude as &$_exclude) {
                $_exclude = str_replace($basepath, '', $_exclude);
            }
        }

        $this->prefixes = $prefixes;
        $this->suffixes = $suffixes;
        $this->exclude  = $exclude;
        $this->basepath = $basepath;

        parent::__construct($iterator);
    }

    /**
     * @return bool
     */
    public function accept()
    {
        $current  = $this->getInnerIterator()->current();
        $filename = $current->getFilename();
        $realpath = $current->getRealPath();

        if ($this->basepath !== NULL) {
            $realpath = str_replace($this->basepath, '', $realpath);
        }

        // Filter files in hidden directories.
        if (preg_match('=/\.[^/]*/=', $realpath)) {
            return FALSE;
        }

        return $this->acceptPath($realpath) &&
               $this->acceptPrefix($filename) &&
               $this->acceptSuffix($filename);
    }

    /**
     * @param  string $path
     * @return bool
     * @since  Method available since Release 1.1.0
     */
    protected function acceptPath($path)
    {
        foreach ($this->exclude as $exclude) {
            if (strpos($path, $exclude) === 0) {
                return FALSE;
            }
        }

        return TRUE;
    }

    /**
     * @param  string $filename
     * @return bool
     * @since  Method available since Release 1.1.0
     */
    protected function acceptPrefix($filename)
    {
        return $this->acceptSubString($filename, $this->prefixes, self::PREFIX);
    }

    /**
     * @param  string $filename
     * @return bool
     * @since  Method available since Release 1.1.0
     */
    protected function acceptSuffix($filename)
    {
        return $this->acceptSubString($filename, $this->suffixes, self::SUFFIX);
    }

    /**
     * @param  string $filename
     * @param  array  $subStrings
     * @param  int    $type
     * @return bool
     * @since  Method available since Release 1.1.0
     */
    protected function acceptSubString($filename, array $subStrings, $type)
    {
        if (empty($subStrings)) {
            return TRUE;
        }

        $matched = FALSE;

        foreach ($subStrings as $string) {
            if (($type == self::PREFIX && strpos($filename, $string) === 0) ||
                ($type == self::SUFFIX &&
                 substr($filename, -1 * strlen($string)) == $string)) {
                $matched = TRUE;
                break;
            }
        }

        return $matched;
    }
}
<?php
/*
 * This file is part of the File_Iterator package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * FaÃ§ade implementation that uses File_Iterator_Factory to create a
 * File_Iterator that operates on an AppendIterator that contains an
 * RecursiveDirectoryIterator for each given path. The list of unique
 * files is returned as an array.
 *
 * @since     Class available since Release 1.3.0
 */
class File_Iterator_Facade
{
    /**
     * @param  array|string $paths
     * @param  array|string $suffixes
     * @param  array|string $prefixes
     * @param  array        $exclude
     * @param  bool         $commonPath
     * @return array
     */
    public function getFilesAsArray($paths, $suffixes = '', $prefixes = '', array $exclude = array(), $commonPath = FALSE)
    {
        if (is_string($paths)) {
            $paths = array($paths);
        }

        $factory  = new File_Iterator_Factory;
        $iterator = $factory->getFileIterator(
          $paths, $suffixes, $prefixes, $exclude
        );

        $files = array();

        foreach ($iterator as $file) {
            $file = $file->getRealPath();

            if ($file) {
                $files[] = $file;
            }
        }

        foreach ($paths as $path) {
            if (is_file($path)) {
                $files[] = realpath($path);
            }
        }

        $files = array_unique($files);
        sort($files);

        if ($commonPath) {
            return array(
              'commonPath' => $this->getCommonPath($files),
              'files'      => $files
            );
        } else {
            return $files;
        }
    }

    /**
     * Returns the common path of a set of files.
     *
     * @param  array  $files
     * @return string
     */
    protected function getCommonPath(array $files)
    {
        $count = count($files);

        if ($count == 0) {
            return '';
        }

        if ($count == 1) {
            return dirname($files[0]) . DIRECTORY_SEPARATOR;
        }

        $_files = array();

        foreach ($files as $file) {
            $_files[] = $_fileParts = explode(DIRECTORY_SEPARATOR, $file);

            if (empty($_fileParts[0])) {
                $_fileParts[0] = DIRECTORY_SEPARATOR;
            }
        }

        $common = '';
        $done   = FALSE;
        $j      = 0;
        $count--;

        while (!$done) {
            for ($i = 0; $i < $count; $i++) {
                if ($_files[$i][$j] != $_files[$i+1][$j]) {
                    $done = TRUE;
                    break;
                }
            }

            if (!$done) {
                $common .= $_files[0][$j];

                if ($j > 0) {
                    $common .= DIRECTORY_SEPARATOR;
                }
            }

            $j++;
        }

        return DIRECTORY_SEPARATOR . $common;
    }
}
<?php

namespace Psr\Log;

/**
 * Describes a logger-aware instance.
 */
interface LoggerAwareInterface
{
    /**
     * Sets a logger instance on the object.
     *
     * @param LoggerInterface $logger
     *
     * @return void
     */
    public function setLogger(LoggerInterface $logger);
}
<?php

namespace Psr\Log;

/**
 * Describes log levels.
 */
class LogLevel
{
    const EMERGENCY = 'emergency';
    const ALERT     = 'alert';
    const CRITICAL  = 'critical';
    const ERROR     = 'error';
    const WARNING   = 'warning';
    const NOTICE    = 'notice';
    const INFO      = 'info';
    const DEBUG     = 'debug';
}
<?php

namespace Psr\Log\Test;

use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;

/**
 * Provides a base test class for ensuring compliance with the LoggerInterface.
 *
 * Implementors can extend the class and implement abstract methods to run this
 * as part of their test suite.
 */
abstract class LoggerInterfaceTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @return LoggerInterface
     */
    abstract public function getLogger();

    /**
     * This must return the log messages in order.
     *
     * The simple formatting of the messages is: "<LOG LEVEL> <MESSAGE>".
     *
     * Example ->error('Foo') would yield "error Foo".
     *
     * @return string[]
     */
    abstract public function getLogs();

    public function testImplements()
    {
        $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger());
    }

    /**
     * @dataProvider provideLevelsAndMessages
     */
    public function testLogsAtAllLevels($level, $message)
    {
        $logger = $this->getLogger();
        $logger->{$level}($message, array('user' => 'Bob'));
        $logger->log($level, $message, array('user' => 'Bob'));

        $expected = array(
            $level.' message of level '.$level.' with context: Bob',
            $level.' message of level '.$level.' with context: Bob',
        );
        $this->assertEquals($expected, $this->getLogs());
    }

    public function provideLevelsAndMessages()
    {
        return array(
            LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'),
            LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'),
            LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'),
            LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'),
            LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'),
            LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'),
            LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'),
            LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'),
        );
    }

    /**
     * @expectedException \Psr\Log\InvalidArgumentException
     */
    public function testThrowsOnInvalidLevel()
    {
        $logger = $this->getLogger();
        $logger->log('invalid level', 'Foo');
    }

    public function testContextReplacement()
    {
        $logger = $this->getLogger();
        $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar'));

        $expected = array('info {Message {nothing} Bob Bar a}');
        $this->assertEquals($expected, $this->getLogs());
    }

    public function testObjectCastToString()
    {
        if (method_exists($this, 'createPartialMock')) {
            $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString'));
        } else {
            $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString'));
        }
        $dummy->expects($this->once())
            ->method('__toString')
            ->will($this->returnValue('DUMMY'));

        $this->getLogger()->warning($dummy);

        $expected = array('warning DUMMY');
        $this->assertEquals($expected, $this->getLogs());
    }

    public function testContextCanContainAnything()
    {
        $closed = fopen('php://memory', 'r');
        fclose($closed);

        $context = array(
            'bool' => true,
            'null' => null,
            'string' => 'Foo',
            'int' => 0,
            'float' => 0.5,
            'nested' => array('with object' => new DummyTest),
            'object' => new \DateTime,
            'resource' => fopen('php://memory', 'r'),
            'closed' => $closed,
        );

        $this->getLogger()->warning('Crazy context data', $context);

        $expected = array('warning Crazy context data');
        $this->assertEquals($expected, $this->getLogs());
    }

    public function testContextExceptionKeyCanBeExceptionOrOtherValues()
    {
        $logger = $this->getLogger();
        $logger->warning('Random message', array('exception' => 'oops'));
        $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail')));

        $expected = array(
            'warning Random message',
            'critical Uncaught Exception!'
        );
        $this->assertEquals($expected, $this->getLogs());
    }
}

class DummyTest
{
    public function __toString()
    {
    }
}
<?php

namespace Psr\Log\Test;

use Psr\Log\AbstractLogger;

/**
 * Used for testing purposes.
 *
 * It records all records and gives you access to them for verification.
 *
 * @method bool hasEmergency($record)
 * @method bool hasAlert($record)
 * @method bool hasCritical($record)
 * @method bool hasError($record)
 * @method bool hasWarning($record)
 * @method bool hasNotice($record)
 * @method bool hasInfo($record)
 * @method bool hasDebug($record)
 *
 * @method bool hasEmergencyRecords()
 * @method bool hasAlertRecords()
 * @method bool hasCriticalRecords()
 * @method bool hasErrorRecords()
 * @method bool hasWarningRecords()
 * @method bool hasNoticeRecords()
 * @method bool hasInfoRecords()
 * @method bool hasDebugRecords()
 *
 * @method bool hasEmergencyThatContains($message)
 * @method bool hasAlertThatContains($message)
 * @method bool hasCriticalThatContains($message)
 * @method bool hasErrorThatContains($message)
 * @method bool hasWarningThatContains($message)
 * @method bool hasNoticeThatContains($message)
 * @method bool hasInfoThatContains($message)
 * @method bool hasDebugThatContains($message)
 *
 * @method bool hasEmergencyThatMatches($message)
 * @method bool hasAlertThatMatches($message)
 * @method bool hasCriticalThatMatches($message)
 * @method bool hasErrorThatMatches($message)
 * @method bool hasWarningThatMatches($message)
 * @method bool hasNoticeThatMatches($message)
 * @method bool hasInfoThatMatches($message)
 * @method bool hasDebugThatMatches($message)
 *
 * @method bool hasEmergencyThatPasses($message)
 * @method bool hasAlertThatPasses($message)
 * @method bool hasCriticalThatPasses($message)
 * @method bool hasErrorThatPasses($message)
 * @method bool hasWarningThatPasses($message)
 * @method bool hasNoticeThatPasses($message)
 * @method bool hasInfoThatPasses($message)
 * @method bool hasDebugThatPasses($message)
 */
class TestLogger extends AbstractLogger
{
    /**
     * @var array
     */
    public $records = [];

    public $recordsByLevel = [];

    /**
     * @inheritdoc
     */
    public function log($level, $message, array $context = [])
    {
        $record = [
            'level' => $level,
            'message' => $message,
            'context' => $context,
        ];

        $this->recordsByLevel[$record['level']][] = $record;
        $this->records[] = $record;
    }

    public function hasRecords($level)
    {
        return isset($this->recordsByLevel[$level]);
    }

    public function hasRecord($record, $level)
    {
        if (is_string($record)) {
            $record = ['message' => $record];
        }
        return $this->hasRecordThatPasses(function ($rec) use ($record) {
            if ($rec['message'] !== $record['message']) {
                return false;
            }
            if (isset($record['context']) && $rec['context'] !== $record['context']) {
                return false;
            }
            return true;
        }, $level);
    }

    public function hasRecordThatContains($message, $level)
    {
        return $this->hasRecordThatPasses(function ($rec) use ($message) {
            return strpos($rec['message'], $message) !== false;
        }, $level);
    }

    public function hasRecordThatMatches($regex, $level)
    {
        return $this->hasRecordThatPasses(function ($rec) use ($regex) {
            return preg_match($regex, $rec['message']) > 0;
        }, $level);
    }

    public function hasRecordThatPasses(callable $predicate, $level)
    {
        if (!isset($this->recordsByLevel[$level])) {
            return false;
        }
        foreach ($this->recordsByLevel[$level] as $i => $rec) {
            if (call_user_func($predicate, $rec, $i)) {
                return true;
            }
        }
        return false;
    }

    public function __call($method, $args)
    {
        if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) {
            $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3];
            $level = strtolower($matches[2]);
            if (method_exists($this, $genericMethod)) {
                $args[] = $level;
                return call_user_func_array([$this, $genericMethod], $args);
            }
        }
        throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()');
    }

    public function reset()
    {
        $this->records = [];
    }
}
<?php

namespace Psr\Log;

/**
 * Basic Implementation of LoggerAwareInterface.
 */
trait LoggerAwareTrait
{
    /**
     * The logger instance.
     *
     * @var LoggerInterface
     */
    protected $logger;

    /**
     * Sets a logger.
     *
     * @param LoggerInterface $logger
     */
    public function setLogger(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }
}
<?php

namespace Psr\Log;

class InvalidArgumentException extends \InvalidArgumentException
{
}
<?php

namespace Psr\Log;

/**
 * This Logger can be used to avoid conditional log calls.
 *
 * Logging should always be optional, and if no logger is provided to your
 * library creating a NullLogger instance to have something to throw logs at
 * is a good way to avoid littering your code with `if ($this->logger) { }`
 * blocks.
 */
class NullLogger extends AbstractLogger
{
    /**
     * Logs with an arbitrary level.
     *
     * @param mixed  $level
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function log($level, $message, array $context = array())
    {
        // noop
    }
}
<?php

namespace Psr\Log;

/**
 * Describes a logger instance.
 *
 * The message MUST be a string or object implementing __toString().
 *
 * The message MAY contain placeholders in the form: {foo} where foo
 * will be replaced by the context data in key "foo".
 *
 * The context array can contain arbitrary data. The only assumption that
 * can be made by implementors is that if an Exception instance is given
 * to produce a stack trace, it MUST be in a key named "exception".
 *
 * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
 * for the full interface specification.
 */
interface LoggerInterface
{
    /**
     * System is unusable.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function emergency($message, array $context = array());

    /**
     * Action must be taken immediately.
     *
     * Example: Entire website down, database unavailable, etc. This should
     * trigger the SMS alerts and wake you up.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function alert($message, array $context = array());

    /**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function critical($message, array $context = array());

    /**
     * Runtime errors that do not require immediate action but should typically
     * be logged and monitored.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function error($message, array $context = array());

    /**
     * Exceptional occurrences that are not errors.
     *
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
     * that are not necessarily wrong.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function warning($message, array $context = array());

    /**
     * Normal but significant events.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function notice($message, array $context = array());

    /**
     * Interesting events.
     *
     * Example: User logs in, SQL logs.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function info($message, array $context = array());

    /**
     * Detailed debug information.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function debug($message, array $context = array());

    /**
     * Logs with an arbitrary level.
     *
     * @param mixed  $level
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function log($level, $message, array $context = array());
}
<?php

namespace Psr\Log;

/**
 * This is a simple Logger trait that classes unable to extend AbstractLogger
 * (because they extend another class, etc) can include.
 *
 * It simply delegates all log-level-specific methods to the `log` method to
 * reduce boilerplate code that a simple Logger that does the same thing with
 * messages regardless of the error level has to implement.
 */
trait LoggerTrait
{
    /**
     * System is unusable.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function emergency($message, array $context = array())
    {
        $this->log(LogLevel::EMERGENCY, $message, $context);
    }

    /**
     * Action must be taken immediately.
     *
     * Example: Entire website down, database unavailable, etc. This should
     * trigger the SMS alerts and wake you up.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function alert($message, array $context = array())
    {
        $this->log(LogLevel::ALERT, $message, $context);
    }

    /**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function critical($message, array $context = array())
    {
        $this->log(LogLevel::CRITICAL, $message, $context);
    }

    /**
     * Runtime errors that do not require immediate action but should typically
     * be logged and monitored.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function error($message, array $context = array())
    {
        $this->log(LogLevel::ERROR, $message, $context);
    }

    /**
     * Exceptional occurrences that are not errors.
     *
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
     * that are not necessarily wrong.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function warning($message, array $context = array())
    {
        $this->log(LogLevel::WARNING, $message, $context);
    }

    /**
     * Normal but significant events.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function notice($message, array $context = array())
    {
        $this->log(LogLevel::NOTICE, $message, $context);
    }

    /**
     * Interesting events.
     *
     * Example: User logs in, SQL logs.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function info($message, array $context = array())
    {
        $this->log(LogLevel::INFO, $message, $context);
    }

    /**
     * Detailed debug information.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function debug($message, array $context = array())
    {
        $this->log(LogLevel::DEBUG, $message, $context);
    }

    /**
     * Logs with an arbitrary level.
     *
     * @param mixed  $level
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    abstract public function log($level, $message, array $context = array());
}
<?php

namespace Psr\Log;

/**
 * This is a simple Logger implementation that other Loggers can inherit from.
 *
 * It simply delegates all log-level-specific methods to the `log` method to
 * reduce boilerplate code that a simple Logger that does the same thing with
 * messages regardless of the error level has to implement.
 */
abstract class AbstractLogger implements LoggerInterface
{
    /**
     * System is unusable.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function emergency($message, array $context = array())
    {
        $this->log(LogLevel::EMERGENCY, $message, $context);
    }

    /**
     * Action must be taken immediately.
     *
     * Example: Entire website down, database unavailable, etc. This should
     * trigger the SMS alerts and wake you up.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function alert($message, array $context = array())
    {
        $this->log(LogLevel::ALERT, $message, $context);
    }

    /**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function critical($message, array $context = array())
    {
        $this->log(LogLevel::CRITICAL, $message, $context);
    }

    /**
     * Runtime errors that do not require immediate action but should typically
     * be logged and monitored.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function error($message, array $context = array())
    {
        $this->log(LogLevel::ERROR, $message, $context);
    }

    /**
     * Exceptional occurrences that are not errors.
     *
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
     * that are not necessarily wrong.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function warning($message, array $context = array())
    {
        $this->log(LogLevel::WARNING, $message, $context);
    }

    /**
     * Normal but significant events.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function notice($message, array $context = array())
    {
        $this->log(LogLevel::NOTICE, $message, $context);
    }

    /**
     * Interesting events.
     *
     * Example: User logs in, SQL logs.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function info($message, array $context = array())
    {
        $this->log(LogLevel::INFO, $message, $context);
    }

    /**
     * Detailed debug information.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function debug($message, array $context = array())
    {
        $this->log(LogLevel::DEBUG, $message, $context);
    }
}
{
    "name": "psr/log",
    "description": "Common interface for logging libraries",
    "keywords": ["psr", "psr-3", "log"],
    "homepage": "https://github.com/php-fig/log",
    "license": "MIT",
    "authors": [
        {
            "name": "PHP-FIG",
            "homepage": "http://www.php-fig.org/"
        }
    ],
    "require": {
        "php": ">=5.3.0"
    },
    "autoload": {
        "psr-4": {
            "Psr\\Log\\": "Psr/Log/"
        }
    },
    "extra": {
        "branch-alias": {
            "dev-master": "1.0.x-dev"
        }
    }
}
{
    "name": "monolog/monolog",
    "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
    "keywords": ["log", "logging", "psr-3"],
    "homepage": "http://github.com/Seldaek/monolog",
    "type": "library",
    "license": "MIT",
    "authors": [
        {
            "name": "Jordi Boggiano",
            "email": "j.boggiano@seld.be",
            "homepage": "http://seld.be"
        }
    ],
    "require": {
        "php": ">=5.3.0",
        "psr/log": "~1.0"
    },
    "require-dev": {
        "phpunit/phpunit": "~4.5",
        "graylog2/gelf-php": "~1.0",
        "sentry/sentry": "^0.13",
        "ruflin/elastica": ">=0.90 <3.0",
        "doctrine/couchdb": "~1.0@dev",
        "aws/aws-sdk-php": "^2.4.9 || ^3.0",
        "php-amqplib/php-amqplib": "~2.4",
        "swiftmailer/swiftmailer": "^5.3|^6.0",
        "php-console/php-console": "^3.1.3",
        "phpunit/phpunit-mock-objects": "2.3.0",
        "jakub-onderka/php-parallel-lint": "0.9"
    },
    "_": "phpunit/phpunit-mock-objects required in 2.3.0 due to https://github.com/sebastianbergmann/phpunit-mock-objects/issues/223 - needs hhvm 3.8+ on travis",
    "suggest": {
        "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
        "sentry/sentry": "Allow sending log messages to a Sentry server",
        "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
        "ruflin/elastica": "Allow sending log messages to an Elastic Search server",
        "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
        "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
        "ext-mongo": "Allow sending log messages to a MongoDB server",
        "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
        "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
        "rollbar/rollbar": "Allow sending log messages to Rollbar",
        "php-console/php-console": "Allow sending log messages to Google Chrome"
    },
    "autoload": {
        "psr-4": {"Monolog\\": "src/Monolog"}
    },
    "autoload-dev": {
        "psr-4": {"Monolog\\": "tests/Monolog"}
    },
    "provide": {
        "psr/log-implementation": "1.0.0"
    },
    "extra": {
        "branch-alias": {
            "dev-master": "2.0.x-dev"
        }
    },
    "scripts": {
        "test": [
            "parallel-lint . --exclude vendor",
            "phpunit"
        ]
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

 namespace Monolog\Handler;
 
 use Monolog\Logger;

/**
 * Inspired on LogEntriesHandler.
 *
 * @author Robert Kaufmann III <rok3@rok3.me>
 * @author Gabriel Machado <gabriel.ms1@hotmail.com>
 */
class InsightOpsHandler extends SocketHandler
{
    /**
     * @var string
     */
    protected $logToken;

    /**
     * @param string $token  Log token supplied by InsightOps
     * @param string $region Region where InsightOps account is hosted. Could be 'us' or 'eu'.
     * @param bool   $useSSL Whether or not SSL encryption should be used
     * @param int    $level  The minimum logging level to trigger this handler
     * @param bool   $bubble Whether or not messages that are handled should bubble up the stack.
     *
     * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing
     */
    public function __construct($token, $region = 'us', $useSSL = true, $level = Logger::DEBUG, $bubble = true)
    {
        if ($useSSL && !extension_loaded('openssl')) {
            throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for LogEntriesHandler');
        }

        $endpoint = $useSSL
            ? 'ssl://' . $region . '.data.logs.insight.rapid7.com:443'
            : $region . '.data.logs.insight.rapid7.com:80';

        parent::__construct($endpoint, $level, $bubble);
        $this->logToken = $token;
    }

    /**
     * {@inheritdoc}
     *
     * @param  array  $record
     * @return string
     */
    protected function generateDataStream($record)
    {
        return $this->logToken . ' ' . $record['formatted'];
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

/**
 * Used for testing purposes.
 *
 * It records all records and gives you access to them for verification.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 *
 * @method bool hasEmergency($record)
 * @method bool hasAlert($record)
 * @method bool hasCritical($record)
 * @method bool hasError($record)
 * @method bool hasWarning($record)
 * @method bool hasNotice($record)
 * @method bool hasInfo($record)
 * @method bool hasDebug($record)
 *
 * @method bool hasEmergencyRecords()
 * @method bool hasAlertRecords()
 * @method bool hasCriticalRecords()
 * @method bool hasErrorRecords()
 * @method bool hasWarningRecords()
 * @method bool hasNoticeRecords()
 * @method bool hasInfoRecords()
 * @method bool hasDebugRecords()
 *
 * @method bool hasEmergencyThatContains($message)
 * @method bool hasAlertThatContains($message)
 * @method bool hasCriticalThatContains($message)
 * @method bool hasErrorThatContains($message)
 * @method bool hasWarningThatContains($message)
 * @method bool hasNoticeThatContains($message)
 * @method bool hasInfoThatContains($message)
 * @method bool hasDebugThatContains($message)
 *
 * @method bool hasEmergencyThatMatches($message)
 * @method bool hasAlertThatMatches($message)
 * @method bool hasCriticalThatMatches($message)
 * @method bool hasErrorThatMatches($message)
 * @method bool hasWarningThatMatches($message)
 * @method bool hasNoticeThatMatches($message)
 * @method bool hasInfoThatMatches($message)
 * @method bool hasDebugThatMatches($message)
 *
 * @method bool hasEmergencyThatPasses($message)
 * @method bool hasAlertThatPasses($message)
 * @method bool hasCriticalThatPasses($message)
 * @method bool hasErrorThatPasses($message)
 * @method bool hasWarningThatPasses($message)
 * @method bool hasNoticeThatPasses($message)
 * @method bool hasInfoThatPasses($message)
 * @method bool hasDebugThatPasses($message)
 */
class TestHandler extends AbstractProcessingHandler
{
    protected $records = array();
    protected $recordsByLevel = array();

    public function getRecords()
    {
        return $this->records;
    }

    public function clear()
    {
        $this->records = array();
        $this->recordsByLevel = array();
    }

    public function hasRecords($level)
    {
        return isset($this->recordsByLevel[$level]);
    }

    /**
     * @param string|array $record Either a message string or an array containing message and optionally context keys that will be checked against all records
     * @param int          $level  Logger::LEVEL constant value
     */
    public function hasRecord($record, $level)
    {
        if (is_string($record)) {
            $record = array('message' => $record);
        }

        return $this->hasRecordThatPasses(function ($rec) use ($record) {
            if ($rec['message'] !== $record['message']) {
                return false;
            }
            if (isset($record['context']) && $rec['context'] !== $record['context']) {
                return false;
            }
            return true;
        }, $level);
    }

    public function hasRecordThatContains($message, $level)
    {
        return $this->hasRecordThatPasses(function ($rec) use ($message) {
            return strpos($rec['message'], $message) !== false;
        }, $level);
    }

    public function hasRecordThatMatches($regex, $level)
    {
        return $this->hasRecordThatPasses(function ($rec) use ($regex) {
            return preg_match($regex, $rec['message']) > 0;
        }, $level);
    }

    public function hasRecordThatPasses($predicate, $level)
    {
        if (!is_callable($predicate)) {
            throw new \InvalidArgumentException("Expected a callable for hasRecordThatSucceeds");
        }

        if (!isset($this->recordsByLevel[$level])) {
            return false;
        }

        foreach ($this->recordsByLevel[$level] as $i => $rec) {
            if (call_user_func($predicate, $rec, $i)) {
                return true;
            }
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    protected function write(array $record)
    {
        $this->recordsByLevel[$record['level']][] = $record;
        $this->records[] = $record;
    }

    public function __call($method, $args)
    {
        if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) {
            $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3];
            $level = constant('Monolog\Logger::' . strtoupper($matches[2]));
            if (method_exists($this, $genericMethod)) {
                $args[] = $level;

                return call_user_func_array(array($this, $genericMethod), $args);
            }
        }

        throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()');
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;

/**
 * IFTTTHandler uses cURL to trigger IFTTT Maker actions
 *
 * Register a secret key and trigger/event name at https://ifttt.com/maker
 *
 * value1 will be the channel from monolog's Logger constructor,
 * value2 will be the level name (ERROR, WARNING, ..)
 * value3 will be the log record's message
 *
 * @author Nehal Patel <nehal@nehalpatel.me>
 */
class IFTTTHandler extends AbstractProcessingHandler
{
    private $eventName;
    private $secretKey;

    /**
     * @param string $eventName The name of the IFTTT Maker event that should be triggered
     * @param string $secretKey A valid IFTTT secret key
     * @param int    $level     The minimum logging level at which this handler will be triggered
     * @param bool   $bubble    Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct($eventName, $secretKey, $level = Logger::ERROR, $bubble = true)
    {
        $this->eventName = $eventName;
        $this->secretKey = $secretKey;

        parent::__construct($level, $bubble);
    }

    /**
     * {@inheritdoc}
     */
    public function write(array $record)
    {
        $postData = array(
            "value1" => $record["channel"],
            "value2" => $record["level_name"],
            "value3" => $record["message"],
        );
        $postString = json_encode($postData);

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, "https://maker.ifttt.com/trigger/" . $this->eventName . "/with/key/" . $this->secretKey);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postString);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
            "Content-Type: application/json",
        ));

        Curl\Util::execute($ch);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;

/**
 * Stores to any socket - uses fsockopen() or pfsockopen().
 *
 * @author Pablo de Leon Belloc <pablolb@gmail.com>
 * @see    http://php.net/manual/en/function.fsockopen.php
 */
class SocketHandler extends AbstractProcessingHandler
{
    private $connectionString;
    private $connectionTimeout;
    private $resource;
    private $timeout = 0;
    private $writingTimeout = 10;
    private $lastSentBytes = null;
    private $chunkSize = null;
    private $persistent = false;
    private $errno;
    private $errstr;
    private $lastWritingAt;

    /**
     * @param string $connectionString Socket connection string
     * @param int    $level            The minimum logging level at which this handler will be triggered
     * @param bool   $bubble           Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct($connectionString, $level = Logger::DEBUG, $bubble = true)
    {
        parent::__construct($level, $bubble);
        $this->connectionString = $connectionString;
        $this->connectionTimeout = (float) ini_get('default_socket_timeout');
    }

    /**
     * Connect (if necessary) and write to the socket
     *
     * @param array $record
     *
     * @throws \UnexpectedValueException
     * @throws \RuntimeException
     */
    protected function write(array $record)
    {
        $this->connectIfNotConnected();
        $data = $this->generateDataStream($record);
        $this->writeToSocket($data);
    }

    /**
     * We will not close a PersistentSocket instance so it can be reused in other requests.
     */
    public function close()
    {
        if (!$this->isPersistent()) {
            $this->closeSocket();
        }
    }

    /**
     * Close socket, if open
     */
    public function closeSocket()
    {
        if (is_resource($this->resource)) {
            fclose($this->resource);
            $this->resource = null;
        }
    }

    /**
     * Set socket connection to nbe persistent. It only has effect before the connection is initiated.
     *
     * @param bool $persistent
     */
    public function setPersistent($persistent)
    {
        $this->persistent = (bool) $persistent;
    }

    /**
     * Set connection timeout.  Only has effect before we connect.
     *
     * @param float $seconds
     *
     * @see http://php.net/manual/en/function.fsockopen.php
     */
    public function setConnectionTimeout($seconds)
    {
        $this->validateTimeout($seconds);
        $this->connectionTimeout = (float) $seconds;
    }

    /**
     * Set write timeout. Only has effect before we connect.
     *
     * @param float $seconds
     *
     * @see http://php.net/manual/en/function.stream-set-timeout.php
     */
    public function setTimeout($seconds)
    {
        $this->validateTimeout($seconds);
        $this->timeout = (float) $seconds;
    }

    /**
     * Set writing timeout. Only has effect during connection in the writing cycle.
     *
     * @param float $seconds 0 for no timeout
     */
    public function setWritingTimeout($seconds)
    {
        $this->validateTimeout($seconds);
        $this->writingTimeout = (float) $seconds;
    }

    /**
     * Set chunk size. Only has effect during connection in the writing cycle.
     *
     * @param float $bytes
     */
    public function setChunkSize($bytes)
    {
        $this->chunkSize = $bytes;
    }

    /**
     * Get current connection string
     *
     * @return string
     */
    public function getConnectionString()
    {
        return $this->connectionString;
    }

    /**
     * Get persistent setting
     *
     * @return bool
     */
    public function isPersistent()
    {
        return $this->persistent;
    }

    /**
     * Get current connection timeout setting
     *
     * @return float
     */
    public function getConnectionTimeout()
    {
        return $this->connectionTimeout;
    }

    /**
     * Get current in-transfer timeout
     *
     * @return float
     */
    public function getTimeout()
    {
        return $this->timeout;
    }

    /**
     * Get current local writing timeout
     *
     * @return float
     */
    public function getWritingTimeout()
    {
        return $this->writingTimeout;
    }

    /**
     * Get current chunk size
     *
     * @return float
     */
    public function getChunkSize()
    {
        return $this->chunkSize;
    }

    /**
     * Check to see if the socket is currently available.
     *
     * UDP might appear to be connected but might fail when writing.  See http://php.net/fsockopen for details.
     *
     * @return bool
     */
    public function isConnected()
    {
        return is_resource($this->resource)
            && !feof($this->resource);  // on TCP - other party can close connection.
    }

    /**
     * Wrapper to allow mocking
     */
    protected function pfsockopen()
    {
        return @pfsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout);
    }

    /**
     * Wrapper to allow mocking
     */
    protected function fsockopen()
    {
        return @fsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout);
    }

    /**
     * Wrapper to allow mocking
     *
     * @see http://php.net/manual/en/function.stream-set-timeout.php
     */
    protected function streamSetTimeout()
    {
        $seconds = floor($this->timeout);
        $microseconds = round(($this->timeout - $seconds) * 1e6);

        return stream_set_timeout($this->resource, $seconds, $microseconds);
    }

    /**
     * Wrapper to allow mocking
     *
     * @see http://php.net/manual/en/function.stream-set-chunk-size.php
     */
    protected function streamSetChunkSize()
    {
        return stream_set_chunk_size($this->resource, $this->chunkSize);
    }

    /**
     * Wrapper to allow mocking
     */
    protected function fwrite($data)
    {
        return @fwrite($this->resource, $data);
    }

    /**
     * Wrapper to allow mocking
     */
    protected function streamGetMetadata()
    {
        return stream_get_meta_data($this->resource);
    }

    private function validateTimeout($value)
    {
        $ok = filter_var($value, FILTER_VALIDATE_FLOAT);
        if ($ok === false || $value < 0) {
            throw new \InvalidArgumentException("Timeout must be 0 or a positive float (got $value)");
        }
    }

    private function connectIfNotConnected()
    {
        if ($this->isConnected()) {
            return;
        }
        $this->connect();
    }

    protected function generateDataStream($record)
    {
        return (string) $record['formatted'];
    }

    /**
     * @return resource|null
     */
    protected function getResource()
    {
        return $this->resource;
    }

    private function connect()
    {
        $this->createSocketResource();
        $this->setSocketTimeout();
        $this->setStreamChunkSize();
    }

    private function createSocketResource()
    {
        if ($this->isPersistent()) {
            $resource = $this->pfsockopen();
        } else {
            $resource = $this->fsockopen();
        }
        if (!$resource) {
            throw new \UnexpectedValueException("Failed connecting to $this->connectionString ($this->errno: $this->errstr)");
        }
        $this->resource = $resource;
    }

    private function setSocketTimeout()
    {
        if (!$this->streamSetTimeout()) {
            throw new \UnexpectedValueException("Failed setting timeout with stream_set_timeout()");
        }
    }

    private function setStreamChunkSize()
    {
        if ($this->chunkSize && !$this->streamSetChunkSize()) {
            throw new \UnexpectedValueException("Failed setting chunk size with stream_set_chunk_size()");
        }
    }

    private function writeToSocket($data)
    {
        $length = strlen($data);
        $sent = 0;
        $this->lastSentBytes = $sent;
        while ($this->isConnected() && $sent < $length) {
            if (0 == $sent) {
                $chunk = $this->fwrite($data);
            } else {
                $chunk = $this->fwrite(substr($data, $sent));
            }
            if ($chunk === false) {
                throw new \RuntimeException("Could not write to socket");
            }
            $sent += $chunk;
            $socketInfo = $this->streamGetMetadata();
            if ($socketInfo['timed_out']) {
                throw new \RuntimeException("Write timed-out");
            }

            if ($this->writingIsTimedOut($sent)) {
                throw new \RuntimeException("Write timed-out, no data sent for `{$this->writingTimeout}` seconds, probably we got disconnected (sent $sent of $length)");
            }
        }
        if (!$this->isConnected() && $sent < $length) {
            throw new \RuntimeException("End-of-file reached, probably we got disconnected (sent $sent of $length)");
        }
    }

    private function writingIsTimedOut($sent)
    {
        $writingTimeout = (int) floor($this->writingTimeout);
        if (0 === $writingTimeout) {
            return false;
        }

        if ($sent !== $this->lastSentBytes) {
            $this->lastWritingAt = time();
            $this->lastSentBytes = $sent;

            return false;
        } else {
            usleep(100);
        }

        if ((time() - $this->lastWritingAt) >= $writingTimeout) {
            $this->closeSocket();

            return true;
        }

        return false;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;

/**
 * Sends notifications through Slack's Slackbot
 *
 * @author Haralan Dobrev <hkdobrev@gmail.com>
 * @see    https://slack.com/apps/A0F81R8ET-slackbot
 */
class SlackbotHandler extends AbstractProcessingHandler
{
    /**
     * The slug of the Slack team
     * @var string
     */
    private $slackTeam;

    /**
     * Slackbot token
     * @var string
     */
    private $token;

    /**
     * Slack channel name
     * @var string
     */
    private $channel;

    /**
     * @param  string $slackTeam Slack team slug
     * @param  string $token     Slackbot token
     * @param  string $channel   Slack channel (encoded ID or name)
     * @param  int    $level     The minimum logging level at which this handler will be triggered
     * @param  bool   $bubble    Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct($slackTeam, $token, $channel, $level = Logger::CRITICAL, $bubble = true)
    {
        parent::__construct($level, $bubble);

        $this->slackTeam = $slackTeam;
        $this->token = $token;
        $this->channel = $channel;
    }

    /**
     * {@inheritdoc}
     *
     * @param array $record
     */
    protected function write(array $record)
    {
        $slackbotUrl = sprintf(
            'https://%s.slack.com/services/hooks/slackbot?token=%s&channel=%s',
            $this->slackTeam,
            $this->token,
            $this->channel
        );

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $slackbotUrl);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $record['message']);

        Curl\Util::execute($ch);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;
use Monolog\ResettableInterface;

/**
 * Buffers all records until closing the handler and then pass them as batch.
 *
 * This is useful for a MailHandler to send only one mail per request instead of
 * sending one per log message.
 *
 * @author Christophe Coevoet <stof@notk.org>
 */
class BufferHandler extends AbstractHandler
{
    protected $handler;
    protected $bufferSize = 0;
    protected $bufferLimit;
    protected $flushOnOverflow;
    protected $buffer = array();
    protected $initialized = false;

    /**
     * @param HandlerInterface $handler         Handler.
     * @param int              $bufferLimit     How many entries should be buffered at most, beyond that the oldest items are removed from the buffer.
     * @param int              $level           The minimum logging level at which this handler will be triggered
     * @param bool             $bubble          Whether the messages that are handled can bubble up the stack or not
     * @param bool             $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded
     */
    public function __construct(HandlerInterface $handler, $bufferLimit = 0, $level = Logger::DEBUG, $bubble = true, $flushOnOverflow = false)
    {
        parent::__construct($level, $bubble);
        $this->handler = $handler;
        $this->bufferLimit = (int) $bufferLimit;
        $this->flushOnOverflow = $flushOnOverflow;
    }

    /**
     * {@inheritdoc}
     */
    public function handle(array $record)
    {
        if ($record['level'] < $this->level) {
            return false;
        }

        if (!$this->initialized) {
            // __destructor() doesn't get called on Fatal errors
            register_shutdown_function(array($this, 'close'));
            $this->initialized = true;
        }

        if ($this->bufferLimit > 0 && $this->bufferSize === $this->bufferLimit) {
            if ($this->flushOnOverflow) {
                $this->flush();
            } else {
                array_shift($this->buffer);
                $this->bufferSize--;
            }
        }

        if ($this->processors) {
            foreach ($this->processors as $processor) {
                $record = call_user_func($processor, $record);
            }
        }

        $this->buffer[] = $record;
        $this->bufferSize++;

        return false === $this->bubble;
    }

    public function flush()
    {
        if ($this->bufferSize === 0) {
            return;
        }

        $this->handler->handleBatch($this->buffer);
        $this->clear();
    }

    public function __destruct()
    {
        // suppress the parent behavior since we already have register_shutdown_function()
        // to call close(), and the reference contained there will prevent this from being
        // GC'd until the end of the request
    }

    /**
     * {@inheritdoc}
     */
    public function close()
    {
        $this->flush();
    }

    /**
     * Clears the buffer without flushing any messages down to the wrapped handler.
     */
    public function clear()
    {
        $this->bufferSize = 0;
        $this->buffer = array();
    }

    public function reset()
    {
        $this->flush();

        parent::reset();

        if ($this->handler instanceof ResettableInterface) {
            $this->handler->reset();
        }
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;

/**
 * Logs to syslog service.
 *
 * usage example:
 *
 *   $log = new Logger('application');
 *   $syslog = new SyslogHandler('myfacility', 'local6');
 *   $formatter = new LineFormatter("%channel%.%level_name%: %message% %extra%");
 *   $syslog->setFormatter($formatter);
 *   $log->pushHandler($syslog);
 *
 * @author Sven Paulus <sven@karlsruhe.org>
 */
class SyslogHandler extends AbstractSyslogHandler
{
    protected $ident;
    protected $logopts;

    /**
     * @param string $ident
     * @param mixed  $facility
     * @param int    $level    The minimum logging level at which this handler will be triggered
     * @param bool   $bubble   Whether the messages that are handled can bubble up the stack or not
     * @param int    $logopts  Option flags for the openlog() call, defaults to LOG_PID
     */
    public function __construct($ident, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true, $logopts = LOG_PID)
    {
        parent::__construct($facility, $level, $bubble);

        $this->ident = $ident;
        $this->logopts = $logopts;
    }

    /**
     * {@inheritdoc}
     */
    public function close()
    {
        closelog();
    }

    /**
     * {@inheritdoc}
     */
    protected function write(array $record)
    {
        if (!openlog($this->ident, $this->logopts, $this->facility)) {
            throw new \LogicException('Can\'t open syslog for ident "'.$this->ident.'" and facility "'.$this->facility.'"');
        }
        syslog($this->logLevels[$record['level']], (string) $record['formatted']);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use RollbarNotifier;
use Exception;
use Monolog\Logger;

/**
 * Sends errors to Rollbar
 *
 * If the context data contains a `payload` key, that is used as an array
 * of payload options to RollbarNotifier's report_message/report_exception methods.
 *
 * Rollbar's context info will contain the context + extra keys from the log record
 * merged, and then on top of that a few keys:
 *
 *  - level (rollbar level name)
 *  - monolog_level (monolog level name, raw level, as rollbar only has 5 but monolog 8)
 *  - channel
 *  - datetime (unix timestamp)
 *
 * @author Paul Statezny <paulstatezny@gmail.com>
 */
class RollbarHandler extends AbstractProcessingHandler
{
    /**
     * Rollbar notifier
     *
     * @var RollbarNotifier
     */
    protected $rollbarNotifier;

    protected $levelMap = array(
        Logger::DEBUG     => 'debug',
        Logger::INFO      => 'info',
        Logger::NOTICE    => 'info',
        Logger::WARNING   => 'warning',
        Logger::ERROR     => 'error',
        Logger::CRITICAL  => 'critical',
        Logger::ALERT     => 'critical',
        Logger::EMERGENCY => 'critical',
    );

    /**
     * Records whether any log records have been added since the last flush of the rollbar notifier
     *
     * @var bool
     */
    private $hasRecords = false;

    protected $initialized = false;

    /**
     * @param RollbarNotifier $rollbarNotifier RollbarNotifier object constructed with valid token
     * @param int             $level           The minimum logging level at which this handler will be triggered
     * @param bool            $bubble          Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct(RollbarNotifier $rollbarNotifier, $level = Logger::ERROR, $bubble = true)
    {
        $this->rollbarNotifier = $rollbarNotifier;

        parent::__construct($level, $bubble);
    }

    /**
     * {@inheritdoc}
     */
    protected function write(array $record)
    {
        if (!$this->initialized) {
            // __destructor() doesn't get called on Fatal errors
            register_shutdown_function(array($this, 'close'));
            $this->initialized = true;
        }

        $context = $record['context'];
        $payload = array();
        if (isset($context['payload'])) {
            $payload = $context['payload'];
            unset($context['payload']);
        }
        $context = array_merge($context, $record['extra'], array(
            'level' => $this->levelMap[$record['level']],
            'monolog_level' => $record['level_name'],
            'channel' => $record['channel'],
            'datetime' => $record['datetime']->format('U'),
        ));

        if (isset($context['exception']) && $context['exception'] instanceof Exception) {
            $payload['level'] = $context['level'];
            $exception = $context['exception'];
            unset($context['exception']);

            $this->rollbarNotifier->report_exception($exception, $context, $payload);
        } else {
            $this->rollbarNotifier->report_message(
                $record['message'],
                $context['level'],
                $context,
                $payload
            );
        }

        $this->hasRecords = true;
    }

    public function flush()
    {
        if ($this->hasRecords) {
            $this->rollbarNotifier->flush();
            $this->hasRecords = false;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function close()
    {
        $this->flush();
    }

    /**
     * {@inheritdoc}
     */
    public function reset()
    {
        $this->flush();

        parent::reset();
    }


}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy;
use Monolog\Handler\FingersCrossed\ActivationStrategyInterface;
use Monolog\Logger;
use Monolog\ResettableInterface;

/**
 * Buffers all records until a certain level is reached
 *
 * The advantage of this approach is that you don't get any clutter in your log files.
 * Only requests which actually trigger an error (or whatever your actionLevel is) will be
 * in the logs, but they will contain all records, not only those above the level threshold.
 *
 * You can find the various activation strategies in the
 * Monolog\Handler\FingersCrossed\ namespace.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class FingersCrossedHandler extends AbstractHandler
{
    protected $handler;
    protected $activationStrategy;
    protected $buffering = true;
    protected $bufferSize;
    protected $buffer = array();
    protected $stopBuffering;
    protected $passthruLevel;

    /**
     * @param callable|HandlerInterface       $handler            Handler or factory callable($record, $fingersCrossedHandler).
     * @param int|ActivationStrategyInterface $activationStrategy Strategy which determines when this handler takes action
     * @param int                             $bufferSize         How many entries should be buffered at most, beyond that the oldest items are removed from the buffer.
     * @param bool                            $bubble             Whether the messages that are handled can bubble up the stack or not
     * @param bool                            $stopBuffering      Whether the handler should stop buffering after being triggered (default true)
     * @param int                             $passthruLevel      Minimum level to always flush to handler on close, even if strategy not triggered
     */
    public function __construct($handler, $activationStrategy = null, $bufferSize = 0, $bubble = true, $stopBuffering = true, $passthruLevel = null)
    {
        if (null === $activationStrategy) {
            $activationStrategy = new ErrorLevelActivationStrategy(Logger::WARNING);
        }

        // convert simple int activationStrategy to an object
        if (!$activationStrategy instanceof ActivationStrategyInterface) {
            $activationStrategy = new ErrorLevelActivationStrategy($activationStrategy);
        }

        $this->handler = $handler;
        $this->activationStrategy = $activationStrategy;
        $this->bufferSize = $bufferSize;
        $this->bubble = $bubble;
        $this->stopBuffering = $stopBuffering;

        if ($passthruLevel !== null) {
            $this->passthruLevel = Logger::toMonologLevel($passthruLevel);
        }

        if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) {
            throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object");
        }
    }

    /**
     * {@inheritdoc}
     */
    public function isHandling(array $record)
    {
        return true;
    }

    /**
     * Manually activate this logger regardless of the activation strategy
     */
    public function activate()
    {
        if ($this->stopBuffering) {
            $this->buffering = false;
        }
        if (!$this->handler instanceof HandlerInterface) {
            $record = end($this->buffer) ?: null;

            $this->handler = call_user_func($this->handler, $record, $this);
            if (!$this->handler instanceof HandlerInterface) {
                throw new \RuntimeException("The factory callable should return a HandlerInterface");
            }
        }
        $this->handler->handleBatch($this->buffer);
        $this->buffer = array();
    }

    /**
     * {@inheritdoc}
     */
    public function handle(array $record)
    {
        if ($this->processors) {
            foreach ($this->processors as $processor) {
                $record = call_user_func($processor, $record);
            }
        }

        if ($this->buffering) {
            $this->buffer[] = $record;
            if ($this->bufferSize > 0 && count($this->buffer) > $this->bufferSize) {
                array_shift($this->buffer);
            }
            if ($this->activationStrategy->isHandlerActivated($record)) {
                $this->activate();
            }
        } else {
            $this->handler->handle($record);
        }

        return false === $this->bubble;
    }

    /**
     * {@inheritdoc}
     */
    public function close()
    {
        $this->flushBuffer();
    }

    public function reset()
    {
        $this->flushBuffer();

        parent::reset();

        if ($this->handler instanceof ResettableInterface) {
            $this->handler->reset();
        }
    }

    /**
     * Clears the buffer without flushing any messages down to the wrapped handler.
     *
     * It also resets the handler to its initial buffering state.
     */
    public function clear()
    {
        $this->buffer = array();
        $this->reset();
    }

    /**
     * Resets the state of the handler. Stops forwarding records to the wrapped handler.
     */
    private function flushBuffer()
    {
        if (null !== $this->passthruLevel) {
            $level = $this->passthruLevel;
            $this->buffer = array_filter($this->buffer, function ($record) use ($level) {
                return $record['level'] >= $level;
            });
            if (count($this->buffer) > 0) {
                $this->handler->handleBatch($this->buffer);
            }
        }

        $this->buffer = array();
        $this->buffering = true;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\ResettableInterface;
use Monolog\Formatter\FormatterInterface;

/**
 * This simple wrapper class can be used to extend handlers functionality.
 *
 * Example: A custom filtering that can be applied to any handler.
 *
 * Inherit from this class and override handle() like this:
 *
 *   public function handle(array $record)
 *   {
 *        if ($record meets certain conditions) {
 *            return false;
 *        }
 *        return $this->handler->handle($record);
 *   }
 *
 * @author Alexey Karapetov <alexey@karapetov.com>
 */
class HandlerWrapper implements HandlerInterface, ResettableInterface
{
    /**
     * @var HandlerInterface
     */
    protected $handler;

    /**
     * HandlerWrapper constructor.
     * @param HandlerInterface $handler
     */
    public function __construct(HandlerInterface $handler)
    {
        $this->handler = $handler;
    }

    /**
     * {@inheritdoc}
     */
    public function isHandling(array $record)
    {
        return $this->handler->isHandling($record);
    }

    /**
     * {@inheritdoc}
     */
    public function handle(array $record)
    {
        return $this->handler->handle($record);
    }

    /**
     * {@inheritdoc}
     */
    public function handleBatch(array $records)
    {
        return $this->handler->handleBatch($records);
    }

    /**
     * {@inheritdoc}
     */
    public function pushProcessor($callback)
    {
        $this->handler->pushProcessor($callback);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function popProcessor()
    {
        return $this->handler->popProcessor();
    }

    /**
     * {@inheritdoc}
     */
    public function setFormatter(FormatterInterface $formatter)
    {
        $this->handler->setFormatter($formatter);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getFormatter()
    {
        return $this->handler->getFormatter();
    }

    public function reset()
    {
        if ($this->handler instanceof ResettableInterface) {
            return $this->handler->reset();
        }
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler\FingersCrossed;

/**
 * Interface for activation strategies for the FingersCrossedHandler.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
interface ActivationStrategyInterface
{
    /**
     * Returns whether the given record activates the handler.
     *
     * @param  array   $record
     * @return bool
     */
    public function isHandlerActivated(array $record);
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler\FingersCrossed;

use Monolog\Logger;

/**
 * Error level based activation strategy.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class ErrorLevelActivationStrategy implements ActivationStrategyInterface
{
    private $actionLevel;

    public function __construct($actionLevel)
    {
        $this->actionLevel = Logger::toMonologLevel($actionLevel);
    }

    public function isHandlerActivated(array $record)
    {
        return $record['level'] >= $this->actionLevel;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler\FingersCrossed;

use Monolog\Logger;

/**
 * Channel and Error level based monolog activation strategy. Allows to trigger activation
 * based on level per channel. e.g. trigger activation on level 'ERROR' by default, except
 * for records of the 'sql' channel; those should trigger activation on level 'WARN'.
 *
 * Example:
 *
 * <code>
 *   $activationStrategy = new ChannelLevelActivationStrategy(
 *       Logger::CRITICAL,
 *       array(
 *           'request' => Logger::ALERT,
 *           'sensitive' => Logger::ERROR,
 *       )
 *   );
 *   $handler = new FingersCrossedHandler(new StreamHandler('php://stderr'), $activationStrategy);
 * </code>
 *
 * @author Mike Meessen <netmikey@gmail.com>
 */
class ChannelLevelActivationStrategy implements ActivationStrategyInterface
{
    private $defaultActionLevel;
    private $channelToActionLevel;

    /**
     * @param int   $defaultActionLevel   The default action level to be used if the record's category doesn't match any
     * @param array $channelToActionLevel An array that maps channel names to action levels.
     */
    public function __construct($defaultActionLevel, $channelToActionLevel = array())
    {
        $this->defaultActionLevel = Logger::toMonologLevel($defaultActionLevel);
        $this->channelToActionLevel = array_map('Monolog\Logger::toMonologLevel', $channelToActionLevel);
    }

    public function isHandlerActivated(array $record)
    {
        if (isset($this->channelToActionLevel[$record['channel']])) {
            return $record['level'] >= $this->channelToActionLevel[$record['channel']];
        }

        return $record['level'] >= $this->defaultActionLevel;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\FormatterInterface;
use Monolog\ResettableInterface;

/**
 * Forwards records to multiple handlers
 *
 * @author Lenar LÃµhmus <lenar@city.ee>
 */
class GroupHandler extends AbstractHandler
{
    protected $handlers;

    /**
     * @param array $handlers Array of Handlers.
     * @param bool  $bubble   Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct(array $handlers, $bubble = true)
    {
        foreach ($handlers as $handler) {
            if (!$handler instanceof HandlerInterface) {
                throw new \InvalidArgumentException('The first argument of the GroupHandler must be an array of HandlerInterface instances.');
            }
        }

        $this->handlers = $handlers;
        $this->bubble = $bubble;
    }

    /**
     * {@inheritdoc}
     */
    public function isHandling(array $record)
    {
        foreach ($this->handlers as $handler) {
            if ($handler->isHandling($record)) {
                return true;
            }
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function handle(array $record)
    {
        if ($this->processors) {
            foreach ($this->processors as $processor) {
                $record = call_user_func($processor, $record);
            }
        }

        foreach ($this->handlers as $handler) {
            $handler->handle($record);
        }

        return false === $this->bubble;
    }

    /**
     * {@inheritdoc}
     */
    public function handleBatch(array $records)
    {
        if ($this->processors) {
            $processed = array();
            foreach ($records as $record) {
                foreach ($this->processors as $processor) {
                    $processed[] = call_user_func($processor, $record);
                }
            }
            $records = $processed;
        }

        foreach ($this->handlers as $handler) {
            $handler->handleBatch($records);
        }
    }

    public function reset()
    {
        parent::reset();

        foreach ($this->handlers as $handler) {
            if ($handler instanceof ResettableInterface) {
                $handler->reset();
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function setFormatter(FormatterInterface $formatter)
    {
        foreach ($this->handlers as $handler) {
            $handler->setFormatter($formatter);
        }

        return $this;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\FormatterInterface;
use Monolog\Formatter\ElasticaFormatter;
use Monolog\Logger;
use Elastica\Client;
use Elastica\Exception\ExceptionInterface;

/**
 * Elastic Search handler
 *
 * Usage example:
 *
 *    $client = new \Elastica\Client();
 *    $options = array(
 *        'index' => 'elastic_index_name',
 *        'type' => 'elastic_doc_type',
 *    );
 *    $handler = new ElasticSearchHandler($client, $options);
 *    $log = new Logger('application');
 *    $log->pushHandler($handler);
 *
 * @author Jelle Vink <jelle.vink@gmail.com>
 */
class ElasticSearchHandler extends AbstractProcessingHandler
{
    /**
     * @var Client
     */
    protected $client;

    /**
     * @var array Handler config options
     */
    protected $options = array();

    /**
     * @param Client $client  Elastica Client object
     * @param array  $options Handler configuration
     * @param int    $level   The minimum logging level at which this handler will be triggered
     * @param bool   $bubble  Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct(Client $client, array $options = array(), $level = Logger::DEBUG, $bubble = true)
    {
        parent::__construct($level, $bubble);
        $this->client = $client;
        $this->options = array_merge(
            array(
                'index'          => 'monolog',      // Elastic index name
                'type'           => 'record',       // Elastic document type
                'ignore_error'   => false,          // Suppress Elastica exceptions
            ),
            $options
        );
    }

    /**
     * {@inheritDoc}
     */
    protected function write(array $record)
    {
        $this->bulkSend(array($record['formatted']));
    }

    /**
     * {@inheritdoc}
     */
    public function setFormatter(FormatterInterface $formatter)
    {
        if ($formatter instanceof ElasticaFormatter) {
            return parent::setFormatter($formatter);
        }
        throw new \InvalidArgumentException('ElasticSearchHandler is only compatible with ElasticaFormatter');
    }

    /**
     * Getter options
     * @return array
     */
    public function getOptions()
    {
        return $this->options;
    }

    /**
     * {@inheritDoc}
     */
    protected function getDefaultFormatter()
    {
        return new ElasticaFormatter($this->options['index'], $this->options['type']);
    }

    /**
     * {@inheritdoc}
     */
    public function handleBatch(array $records)
    {
        $documents = $this->getFormatter()->formatBatch($records);
        $this->bulkSend($documents);
    }

    /**
     * Use Elasticsearch bulk API to send list of documents
     * @param  array             $documents
     * @throws \RuntimeException
     */
    protected function bulkSend(array $documents)
    {
        try {
            $this->client->addDocuments($documents);
        } catch (ExceptionInterface $e) {
            if (!$this->options['ignore_error']) {
                throw new \RuntimeException("Error sending messages to Elasticsearch", 0, $e);
            }
        }
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

/**
 * Sampling handler
 *
 * A sampled event stream can be useful for logging high frequency events in
 * a production environment where you only need an idea of what is happening
 * and are not concerned with capturing every occurrence. Since the decision to
 * handle or not handle a particular event is determined randomly, the
 * resulting sampled log is not guaranteed to contain 1/N of the events that
 * occurred in the application, but based on the Law of large numbers, it will
 * tend to be close to this ratio with a large number of attempts.
 *
 * @author Bryan Davis <bd808@wikimedia.org>
 * @author Kunal Mehta <legoktm@gmail.com>
 */
class SamplingHandler extends AbstractHandler
{
    /**
     * @var callable|HandlerInterface $handler
     */
    protected $handler;

    /**
     * @var int $factor
     */
    protected $factor;

    /**
     * @param callable|HandlerInterface $handler Handler or factory callable($record, $fingersCrossedHandler).
     * @param int                       $factor  Sample factor
     */
    public function __construct($handler, $factor)
    {
        parent::__construct();
        $this->handler = $handler;
        $this->factor = $factor;

        if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) {
            throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object");
        }
    }

    public function isHandling(array $record)
    {
        return $this->handler->isHandling($record);
    }

    public function handle(array $record)
    {
        if ($this->isHandling($record) && mt_rand(1, $this->factor) === 1) {
            // The same logic as in FingersCrossedHandler
            if (!$this->handler instanceof HandlerInterface) {
                $this->handler = call_user_func($this->handler, $record, $this);
                if (!$this->handler instanceof HandlerInterface) {
                    throw new \RuntimeException("The factory callable should return a HandlerInterface");
                }
            }

            if ($this->processors) {
                foreach ($this->processors as $processor) {
                    $record = call_user_func($processor, $record);
                }
            }

            $this->handler->handle($record);
        }

        return false === $this->bubble;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\FormatterInterface;
use Monolog\Formatter\LineFormatter;
use Monolog\Logger;
use Monolog\ResettableInterface;

/**
 * Base Handler class providing the Handler structure
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
abstract class AbstractHandler implements HandlerInterface, ResettableInterface
{
    protected $level = Logger::DEBUG;
    protected $bubble = true;

    /**
     * @var FormatterInterface
     */
    protected $formatter;
    protected $processors = array();

    /**
     * @param int  $level  The minimum logging level at which this handler will be triggered
     * @param bool $bubble Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct($level = Logger::DEBUG, $bubble = true)
    {
        $this->setLevel($level);
        $this->bubble = $bubble;
    }

    /**
     * {@inheritdoc}
     */
    public function isHandling(array $record)
    {
        return $record['level'] >= $this->level;
    }

    /**
     * {@inheritdoc}
     */
    public function handleBatch(array $records)
    {
        foreach ($records as $record) {
            $this->handle($record);
        }
    }

    /**
     * Closes the handler.
     *
     * This will be called automatically when the object is destroyed
     */
    public function close()
    {
    }

    /**
     * {@inheritdoc}
     */
    public function pushProcessor($callback)
    {
        if (!is_callable($callback)) {
            throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given');
        }
        array_unshift($this->processors, $callback);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function popProcessor()
    {
        if (!$this->processors) {
            throw new \LogicException('You tried to pop from an empty processor stack.');
        }

        return array_shift($this->processors);
    }

    /**
     * {@inheritdoc}
     */
    public function setFormatter(FormatterInterface $formatter)
    {
        $this->formatter = $formatter;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getFormatter()
    {
        if (!$this->formatter) {
            $this->formatter = $this->getDefaultFormatter();
        }

        return $this->formatter;
    }

    /**
     * Sets minimum logging level at which this handler will be triggered.
     *
     * @param  int|string $level Level or level name
     * @return self
     */
    public function setLevel($level)
    {
        $this->level = Logger::toMonologLevel($level);

        return $this;
    }

    /**
     * Gets minimum logging level at which this handler will be triggered.
     *
     * @return int
     */
    public function getLevel()
    {
        return $this->level;
    }

    /**
     * Sets the bubbling behavior.
     *
     * @param  bool $bubble true means that this handler allows bubbling.
     *                      false means that bubbling is not permitted.
     * @return self
     */
    public function setBubble($bubble)
    {
        $this->bubble = $bubble;

        return $this;
    }

    /**
     * Gets the bubbling behavior.
     *
     * @return bool true means that this handler allows bubbling.
     *              false means that bubbling is not permitted.
     */
    public function getBubble()
    {
        return $this->bubble;
    }

    public function __destruct()
    {
        try {
            $this->close();
        } catch (\Exception $e) {
            // do nothing
        } catch (\Throwable $e) {
            // do nothing
        }
    }

    public function reset()
    {
        foreach ($this->processors as $processor) {
            if ($processor instanceof ResettableInterface) {
                $processor->reset();
            }
        }
    }

    /**
     * Gets the default formatter.
     *
     * @return FormatterInterface
     */
    protected function getDefaultFormatter()
    {
        return new LineFormatter();
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;
use Monolog\Formatter\FormatterInterface;
use Monolog\Formatter\LineFormatter;
use Swift;

/**
 * SwiftMailerHandler uses Swift_Mailer to send the emails
 *
 * @author Gyula Sallai
 */
class SwiftMailerHandler extends MailHandler
{
    protected $mailer;
    private $messageTemplate;

    /**
     * @param \Swift_Mailer           $mailer  The mailer to use
     * @param callable|\Swift_Message $message An example message for real messages, only the body will be replaced
     * @param int                     $level   The minimum logging level at which this handler will be triggered
     * @param bool                    $bubble  Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct(\Swift_Mailer $mailer, $message, $level = Logger::ERROR, $bubble = true)
    {
        parent::__construct($level, $bubble);

        $this->mailer = $mailer;
        $this->messageTemplate = $message;
    }

    /**
     * {@inheritdoc}
     */
    protected function send($content, array $records)
    {
        $this->mailer->send($this->buildMessage($content, $records));
    }

    /**
     * Gets the formatter for the Swift_Message subject.
     *
     * @param  string             $format The format of the subject
     * @return FormatterInterface
     */
    protected function getSubjectFormatter($format)
    {
        return new LineFormatter($format);
    }

    /**
     * Creates instance of Swift_Message to be sent
     *
     * @param  string         $content formatted email body to be sent
     * @param  array          $records Log records that formed the content
     * @return \Swift_Message
     */
    protected function buildMessage($content, array $records)
    {
        $message = null;
        if ($this->messageTemplate instanceof \Swift_Message) {
            $message = clone $this->messageTemplate;
            $message->generateId();
        } elseif (is_callable($this->messageTemplate)) {
            $message = call_user_func($this->messageTemplate, $content, $records);
        }

        if (!$message instanceof \Swift_Message) {
            throw new \InvalidArgumentException('Could not resolve message as instance of Swift_Message or a callable returning it');
        }

        if ($records) {
            $subjectFormatter = $this->getSubjectFormatter($message->getSubject());
            $message->setSubject($subjectFormatter->format($this->getHighestRecord($records)));
        }

        $message->setBody($content);
        if (version_compare(Swift::VERSION, '6.0.0', '>=')) {
            $message->setDate(new \DateTimeImmutable());
        } else {
            $message->setDate(time());
        }

        return $message;
    }

    /**
     * BC getter, to be removed in 2.0
     */
    public function __get($name)
    {
        if ($name === 'message') {
            trigger_error('SwiftMailerHandler->message is deprecated, use ->buildMessage() instead to retrieve the message', E_USER_DEPRECATED);

            return $this->buildMessage(null, array());
        }

        throw new \InvalidArgumentException('Invalid property '.$name);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;
use Monolog\Formatter\FlowdockFormatter;
use Monolog\Formatter\FormatterInterface;

/**
 * Sends notifications through the Flowdock push API
 *
 * This must be configured with a FlowdockFormatter instance via setFormatter()
 *
 * Notes:
 * API token - Flowdock API token
 *
 * @author Dominik Liebler <liebler.dominik@gmail.com>
 * @see https://www.flowdock.com/api/push
 */
class FlowdockHandler extends SocketHandler
{
    /**
     * @var string
     */
    protected $apiToken;

    /**
     * @param string   $apiToken
     * @param bool|int $level    The minimum logging level at which this handler will be triggered
     * @param bool     $bubble   Whether the messages that are handled can bubble up the stack or not
     *
     * @throws MissingExtensionException if OpenSSL is missing
     */
    public function __construct($apiToken, $level = Logger::DEBUG, $bubble = true)
    {
        if (!extension_loaded('openssl')) {
            throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FlowdockHandler');
        }

        parent::__construct('ssl://api.flowdock.com:443', $level, $bubble);
        $this->apiToken = $apiToken;
    }

    /**
     * {@inheritdoc}
     */
    public function setFormatter(FormatterInterface $formatter)
    {
        if (!$formatter instanceof FlowdockFormatter) {
            throw new \InvalidArgumentException('The FlowdockHandler requires an instance of Monolog\Formatter\FlowdockFormatter to function correctly');
        }

        return parent::setFormatter($formatter);
    }

    /**
     * Gets the default formatter.
     *
     * @return FormatterInterface
     */
    protected function getDefaultFormatter()
    {
        throw new \InvalidArgumentException('The FlowdockHandler must be configured (via setFormatter) with an instance of Monolog\Formatter\FlowdockFormatter to function correctly');
    }

    /**
     * {@inheritdoc}
     *
     * @param array $record
     */
    protected function write(array $record)
    {
        parent::write($record);

        $this->closeSocket();
    }

    /**
     * {@inheritdoc}
     *
     * @param  array  $record
     * @return string
     */
    protected function generateDataStream($record)
    {
        $content = $this->buildContent($record);

        return $this->buildHeader($content) . $content;
    }

    /**
     * Builds the body of API call
     *
     * @param  array  $record
     * @return string
     */
    private function buildContent($record)
    {
        return json_encode($record['formatted']['flowdock']);
    }

    /**
     * Builds the header of the API Call
     *
     * @param  string $content
     * @return string
     */
    private function buildHeader($content)
    {
        $header = "POST /v1/messages/team_inbox/" . $this->apiToken . " HTTP/1.1\r\n";
        $header .= "Host: api.flowdock.com\r\n";
        $header .= "Content-Type: application/json\r\n";
        $header .= "Content-Length: " . strlen($content) . "\r\n";
        $header .= "\r\n";

        return $header;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;

/**
 * Sends notifications through the pushover api to mobile phones
 *
 * @author Sebastian GÃ¶ttschkes <sebastian.goettschkes@googlemail.com>
 * @see    https://www.pushover.net/api
 */
class PushoverHandler extends SocketHandler
{
    private $token;
    private $users;
    private $title;
    private $user;
    private $retry;
    private $expire;

    private $highPriorityLevel;
    private $emergencyLevel;
    private $useFormattedMessage = false;

    /**
     * All parameters that can be sent to Pushover
     * @see https://pushover.net/api
     * @var array
     */
    private $parameterNames = array(
        'token' => true,
        'user' => true,
        'message' => true,
        'device' => true,
        'title' => true,
        'url' => true,
        'url_title' => true,
        'priority' => true,
        'timestamp' => true,
        'sound' => true,
        'retry' => true,
        'expire' => true,
        'callback' => true,
    );

    /**
     * Sounds the api supports by default
     * @see https://pushover.net/api#sounds
     * @var array
     */
    private $sounds = array(
        'pushover', 'bike', 'bugle', 'cashregister', 'classical', 'cosmic', 'falling', 'gamelan', 'incoming',
        'intermission', 'magic', 'mechanical', 'pianobar', 'siren', 'spacealarm', 'tugboat', 'alien', 'climb',
        'persistent', 'echo', 'updown', 'none',
    );

    /**
     * @param string       $token             Pushover api token
     * @param string|array $users             Pushover user id or array of ids the message will be sent to
     * @param string       $title             Title sent to the Pushover API
     * @param int          $level             The minimum logging level at which this handler will be triggered
     * @param bool         $bubble            Whether the messages that are handled can bubble up the stack or not
     * @param bool         $useSSL            Whether to connect via SSL. Required when pushing messages to users that are not
     *                                        the pushover.net app owner. OpenSSL is required for this option.
     * @param int          $highPriorityLevel The minimum logging level at which this handler will start
     *                                        sending "high priority" requests to the Pushover API
     * @param int          $emergencyLevel    The minimum logging level at which this handler will start
     *                                        sending "emergency" requests to the Pushover API
     * @param int          $retry             The retry parameter specifies how often (in seconds) the Pushover servers will send the same notification to the user.
     * @param int          $expire            The expire parameter specifies how many seconds your notification will continue to be retried for (every retry seconds).
     */
    public function __construct($token, $users, $title = null, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $highPriorityLevel = Logger::CRITICAL, $emergencyLevel = Logger::EMERGENCY, $retry = 30, $expire = 25200)
    {
        $connectionString = $useSSL ? 'ssl://api.pushover.net:443' : 'api.pushover.net:80';
        parent::__construct($connectionString, $level, $bubble);

        $this->token = $token;
        $this->users = (array) $users;
        $this->title = $title ?: gethostname();
        $this->highPriorityLevel = Logger::toMonologLevel($highPriorityLevel);
        $this->emergencyLevel = Logger::toMonologLevel($emergencyLevel);
        $this->retry = $retry;
        $this->expire = $expire;
    }

    protected function generateDataStream($record)
    {
        $content = $this->buildContent($record);

        return $this->buildHeader($content) . $content;
    }

    private function buildContent($record)
    {
        // Pushover has a limit of 512 characters on title and message combined.
        $maxMessageLength = 512 - strlen($this->title);

        $message = ($this->useFormattedMessage) ? $record['formatted'] : $record['message'];
        $message = substr($message, 0, $maxMessageLength);

        $timestamp = $record['datetime']->getTimestamp();

        $dataArray = array(
            'token' => $this->token,
            'user' => $this->user,
            'message' => $message,
            'title' => $this->title,
            'timestamp' => $timestamp,
        );

        if (isset($record['level']) && $record['level'] >= $this->emergencyLevel) {
            $dataArray['priority'] = 2;
            $dataArray['retry'] = $this->retry;
            $dataArray['expire'] = $this->expire;
        } elseif (isset($record['level']) && $record['level'] >= $this->highPriorityLevel) {
            $dataArray['priority'] = 1;
        }

        // First determine the available parameters
        $context = array_intersect_key($record['context'], $this->parameterNames);
        $extra = array_intersect_key($record['extra'], $this->parameterNames);

        // Least important info should be merged with subsequent info
        $dataArray = array_merge($extra, $context, $dataArray);

        // Only pass sounds that are supported by the API
        if (isset($dataArray['sound']) && !in_array($dataArray['sound'], $this->sounds)) {
            unset($dataArray['sound']);
        }

        return http_build_query($dataArray);
    }

    private function buildHeader($content)
    {
        $header = "POST /1/messages.json HTTP/1.1\r\n";
        $header .= "Host: api.pushover.net\r\n";
        $header .= "Content-Type: application/x-www-form-urlencoded\r\n";
        $header .= "Content-Length: " . strlen($content) . "\r\n";
        $header .= "\r\n";

        return $header;
    }

    protected function write(array $record)
    {
        foreach ($this->users as $user) {
            $this->user = $user;

            parent::write($record);
            $this->closeSocket();
        }

        $this->user = null;
    }

    public function setHighPriorityLevel($value)
    {
        $this->highPriorityLevel = $value;
    }

    public function setEmergencyLevel($value)
    {
        $this->emergencyLevel = $value;
    }

    /**
     * Use the formatted message?
     * @param bool $value
     */
    public function useFormattedMessage($value)
    {
        $this->useFormattedMessage = (bool) $value;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

/**
 * Exception can be thrown if an extension for an handler is missing
 *
 * @author  Christian Bergau <cbergau86@gmail.com>
 */
class MissingExtensionException extends \Exception
{
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;
use Monolog\Formatter\LineFormatter;

/**
 * Common syslog functionality
 */
abstract class AbstractSyslogHandler extends AbstractProcessingHandler
{
    protected $facility;

    /**
     * Translates Monolog log levels to syslog log priorities.
     */
    protected $logLevels = array(
        Logger::DEBUG     => LOG_DEBUG,
        Logger::INFO      => LOG_INFO,
        Logger::NOTICE    => LOG_NOTICE,
        Logger::WARNING   => LOG_WARNING,
        Logger::ERROR     => LOG_ERR,
        Logger::CRITICAL  => LOG_CRIT,
        Logger::ALERT     => LOG_ALERT,
        Logger::EMERGENCY => LOG_EMERG,
    );

    /**
     * List of valid log facility names.
     */
    protected $facilities = array(
        'auth'     => LOG_AUTH,
        'authpriv' => LOG_AUTHPRIV,
        'cron'     => LOG_CRON,
        'daemon'   => LOG_DAEMON,
        'kern'     => LOG_KERN,
        'lpr'      => LOG_LPR,
        'mail'     => LOG_MAIL,
        'news'     => LOG_NEWS,
        'syslog'   => LOG_SYSLOG,
        'user'     => LOG_USER,
        'uucp'     => LOG_UUCP,
    );

    /**
     * @param mixed $facility
     * @param int   $level The minimum logging level at which this handler will be triggered
     * @param bool  $bubble Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct($facility = LOG_USER, $level = Logger::DEBUG, $bubble = true)
    {
        parent::__construct($level, $bubble);

        if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
            $this->facilities['local0'] = LOG_LOCAL0;
            $this->facilities['local1'] = LOG_LOCAL1;
            $this->facilities['local2'] = LOG_LOCAL2;
            $this->facilities['local3'] = LOG_LOCAL3;
            $this->facilities['local4'] = LOG_LOCAL4;
            $this->facilities['local5'] = LOG_LOCAL5;
            $this->facilities['local6'] = LOG_LOCAL6;
            $this->facilities['local7'] = LOG_LOCAL7;
        } else {
            $this->facilities['local0'] = 128; // LOG_LOCAL0
            $this->facilities['local1'] = 136; // LOG_LOCAL1
            $this->facilities['local2'] = 144; // LOG_LOCAL2
            $this->facilities['local3'] = 152; // LOG_LOCAL3
            $this->facilities['local4'] = 160; // LOG_LOCAL4
            $this->facilities['local5'] = 168; // LOG_LOCAL5
            $this->facilities['local6'] = 176; // LOG_LOCAL6
            $this->facilities['local7'] = 184; // LOG_LOCAL7
        }

        // convert textual description of facility to syslog constant
        if (array_key_exists(strtolower($facility), $this->facilities)) {
            $facility = $this->facilities[strtolower($facility)];
        } elseif (!in_array($facility, array_values($this->facilities), true)) {
            throw new \UnexpectedValueException('Unknown facility value "'.$facility.'" given');
        }

        $this->facility = $facility;
    }

    /**
     * {@inheritdoc}
     */
    protected function getDefaultFormatter()
    {
        return new LineFormatter('%channel%.%level_name%: %message% %context% %extra%');
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\LineFormatter;
use Monolog\Formatter\FormatterInterface;
use Monolog\Logger;
use Raven_Client;

/**
 * Handler to send messages to a Sentry (https://github.com/getsentry/sentry) server
 * using sentry-php (https://github.com/getsentry/sentry-php)
 *
 * @author Marc Abramowitz <marc@marc-abramowitz.com>
 */
class RavenHandler extends AbstractProcessingHandler
{
    /**
     * Translates Monolog log levels to Raven log levels.
     */
    protected $logLevels = array(
        Logger::DEBUG     => Raven_Client::DEBUG,
        Logger::INFO      => Raven_Client::INFO,
        Logger::NOTICE    => Raven_Client::INFO,
        Logger::WARNING   => Raven_Client::WARNING,
        Logger::ERROR     => Raven_Client::ERROR,
        Logger::CRITICAL  => Raven_Client::FATAL,
        Logger::ALERT     => Raven_Client::FATAL,
        Logger::EMERGENCY => Raven_Client::FATAL,
    );

    /**
     * @var string should represent the current version of the calling
     *             software. Can be any string (git commit, version number)
     */
    protected $release;

    /**
     * @var Raven_Client the client object that sends the message to the server
     */
    protected $ravenClient;

    /**
     * @var LineFormatter The formatter to use for the logs generated via handleBatch()
     */
    protected $batchFormatter;

    /**
     * @param Raven_Client $ravenClient
     * @param int          $level       The minimum logging level at which this handler will be triggered
     * @param bool         $bubble      Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, $bubble = true)
    {
        parent::__construct($level, $bubble);

        $this->ravenClient = $ravenClient;
    }

    /**
     * {@inheritdoc}
     */
    public function handleBatch(array $records)
    {
        $level = $this->level;

        // filter records based on their level
        $records = array_filter($records, function ($record) use ($level) {
            return $record['level'] >= $level;
        });

        if (!$records) {
            return;
        }

        // the record with the highest severity is the "main" one
        $record = array_reduce($records, function ($highest, $record) {
            if ($record['level'] > $highest['level']) {
                return $record;
            }

            return $highest;
        });

        // the other ones are added as a context item
        $logs = array();
        foreach ($records as $r) {
            $logs[] = $this->processRecord($r);
        }

        if ($logs) {
            $record['context']['logs'] = (string) $this->getBatchFormatter()->formatBatch($logs);
        }

        $this->handle($record);
    }

    /**
     * Sets the formatter for the logs generated by handleBatch().
     *
     * @param FormatterInterface $formatter
     */
    public function setBatchFormatter(FormatterInterface $formatter)
    {
        $this->batchFormatter = $formatter;
    }

    /**
     * Gets the formatter for the logs generated by handleBatch().
     *
     * @return FormatterInterface
     */
    public function getBatchFormatter()
    {
        if (!$this->batchFormatter) {
            $this->batchFormatter = $this->getDefaultBatchFormatter();
        }

        return $this->batchFormatter;
    }

    /**
     * {@inheritdoc}
     */
    protected function write(array $record)
    {
        $previousUserContext = false;
        $options = array();
        $options['level'] = $this->logLevels[$record['level']];
        $options['tags'] = array();
        if (!empty($record['extra']['tags'])) {
            $options['tags'] = array_merge($options['tags'], $record['extra']['tags']);
            unset($record['extra']['tags']);
        }
        if (!empty($record['context']['tags'])) {
            $options['tags'] = array_merge($options['tags'], $record['context']['tags']);
            unset($record['context']['tags']);
        }
        if (!empty($record['context']['fingerprint'])) {
            $options['fingerprint'] = $record['context']['fingerprint'];
            unset($record['context']['fingerprint']);
        }
        if (!empty($record['context']['logger'])) {
            $options['logger'] = $record['context']['logger'];
            unset($record['context']['logger']);
        } else {
            $options['logger'] = $record['channel'];
        }
        foreach ($this->getExtraParameters() as $key) {
            foreach (array('extra', 'context') as $source) {
                if (!empty($record[$source][$key])) {
                    $options[$key] = $record[$source][$key];
                    unset($record[$source][$key]);
                }
            }
        }
        if (!empty($record['context'])) {
            $options['extra']['context'] = $record['context'];
            if (!empty($record['context']['user'])) {
                $previousUserContext = $this->ravenClient->context->user;
                $this->ravenClient->user_context($record['context']['user']);
                unset($options['extra']['context']['user']);
            }
        }
        if (!empty($record['extra'])) {
            $options['extra']['extra'] = $record['extra'];
        }

        if (!empty($this->release) && !isset($options['release'])) {
            $options['release'] = $this->release;
        }

        if (isset($record['context']['exception']) && ($record['context']['exception'] instanceof \Exception || (PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable))) {
            $options['message'] = $record['formatted'];
            $this->ravenClient->captureException($record['context']['exception'], $options);
        } else {
            $this->ravenClient->captureMessage($record['formatted'], array(), $options);
        }

        if ($previousUserContext !== false) {
            $this->ravenClient->user_context($previousUserContext);
        }
    }

    /**
     * {@inheritDoc}
     */
    protected function getDefaultFormatter()
    {
        return new LineFormatter('[%channel%] %message%');
    }

    /**
     * Gets the default formatter for the logs generated by handleBatch().
     *
     * @return FormatterInterface
     */
    protected function getDefaultBatchFormatter()
    {
        return new LineFormatter();
    }

    /**
     * Gets extra parameters supported by Raven that can be found in "extra" and "context"
     *
     * @return array
     */
    protected function getExtraParameters()
    {
        return array('contexts', 'checksum', 'release', 'event_id');
    }

    /**
     * @param string $value
     * @return self
     */
    public function setRelease($value)
    {
        $this->release = $value;

        return $this;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;
use Monolog\Handler\SyslogUdp\UdpSocket;

/**
 * A Handler for logging to a remote syslogd server.
 *
 * @author Jesper Skovgaard Nielsen <nulpunkt@gmail.com>
 */
class SyslogUdpHandler extends AbstractSyslogHandler
{
    protected $socket;
    protected $ident;

    /**
     * @param string $host
     * @param int    $port
     * @param mixed  $facility
     * @param int    $level    The minimum logging level at which this handler will be triggered
     * @param bool   $bubble   Whether the messages that are handled can bubble up the stack or not
     * @param string $ident    Program name or tag for each log message.
     */
    public function __construct($host, $port = 514, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true, $ident = 'php')
    {
        parent::__construct($facility, $level, $bubble);

        $this->ident = $ident;

        $this->socket = new UdpSocket($host, $port ?: 514);
    }

    protected function write(array $record)
    {
        $lines = $this->splitMessageIntoLines($record['formatted']);

        $header = $this->makeCommonSyslogHeader($this->logLevels[$record['level']]);

        foreach ($lines as $line) {
            $this->socket->write($line, $header);
        }
    }

    public function close()
    {
        $this->socket->close();
    }

    private function splitMessageIntoLines($message)
    {
        if (is_array($message)) {
            $message = implode("\n", $message);
        }

        return preg_split('/$\R?^/m', $message, -1, PREG_SPLIT_NO_EMPTY);
    }

    /**
     * Make common syslog header (see rfc5424)
     */
    protected function makeCommonSyslogHeader($severity)
    {
        $priority = $severity + $this->facility;

        if (!$pid = getmypid()) {
            $pid = '-';
        }

        if (!$hostname = gethostname()) {
            $hostname = '-';
        }

        return "<$priority>1 " .
            $this->getDateTime() . " " .
            $hostname . " " .
            $this->ident . " " .
            $pid . " - - ";
    }

    protected function getDateTime()
    {
        return date(\DateTime::RFC3339);
    }

    /**
     * Inject your own socket, mainly used for testing
     */
    public function setSocket($socket)
    {
        $this->socket = $socket;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\LineFormatter;

/**
 * Handler sending logs to browser's javascript console with no browser extension required
 *
 * @author Olivier Poitrey <rs@dailymotion.com>
 */
class BrowserConsoleHandler extends AbstractProcessingHandler
{
    protected static $initialized = false;
    protected static $records = array();

    /**
     * {@inheritDoc}
     *
     * Formatted output may contain some formatting markers to be transferred to `console.log` using the %c format.
     *
     * Example of formatted string:
     *
     *     You can do [[blue text]]{color: blue} or [[green background]]{background-color: green; color: white}
     */
    protected function getDefaultFormatter()
    {
        return new LineFormatter('[[%channel%]]{macro: autolabel} [[%level_name%]]{font-weight: bold} %message%');
    }

    /**
     * {@inheritDoc}
     */
    protected function write(array $record)
    {
        // Accumulate records
        static::$records[] = $record;

        // Register shutdown handler if not already done
        if (!static::$initialized) {
            static::$initialized = true;
            $this->registerShutdownFunction();
        }
    }

    /**
     * Convert records to javascript console commands and send it to the browser.
     * This method is automatically called on PHP shutdown if output is HTML or Javascript.
     */
    public static function send()
    {
        $format = static::getResponseFormat();
        if ($format === 'unknown') {
            return;
        }

        if (count(static::$records)) {
            if ($format === 'html') {
                static::writeOutput('<script>' . static::generateScript() . '</script>');
            } elseif ($format === 'js') {
                static::writeOutput(static::generateScript());
            }
            static::resetStatic();
        }
    }

    public function close()
    {
        self::resetStatic();
    }

    public function reset()
    {
        self::resetStatic();
    }

    /**
     * Forget all logged records
     */
    public static function resetStatic()
    {
        static::$records = array();
    }

    /**
     * Wrapper for register_shutdown_function to allow overriding
     */
    protected function registerShutdownFunction()
    {
        if (PHP_SAPI !== 'cli') {
            register_shutdown_function(array('Monolog\Handler\BrowserConsoleHandler', 'send'));
        }
    }

    /**
     * Wrapper for echo to allow overriding
     *
     * @param string $str
     */
    protected static function writeOutput($str)
    {
        echo $str;
    }

    /**
     * Checks the format of the response
     *
     * If Content-Type is set to application/javascript or text/javascript -> js
     * If Content-Type is set to text/html, or is unset -> html
     * If Content-Type is anything else -> unknown
     *
     * @return string One of 'js', 'html' or 'unknown'
     */
    protected static function getResponseFormat()
    {
        // Check content type
        foreach (headers_list() as $header) {
            if (stripos($header, 'content-type:') === 0) {
                // This handler only works with HTML and javascript outputs
                // text/javascript is obsolete in favour of application/javascript, but still used
                if (stripos($header, 'application/javascript') !== false || stripos($header, 'text/javascript') !== false) {
                    return 'js';
                }
                if (stripos($header, 'text/html') === false) {
                    return 'unknown';
                }
                break;
            }
        }

        return 'html';
    }

    private static function generateScript()
    {
        $script = array();
        foreach (static::$records as $record) {
            $context = static::dump('Context', $record['context']);
            $extra = static::dump('Extra', $record['extra']);

            if (empty($context) && empty($extra)) {
                $script[] = static::call_array('log', static::handleStyles($record['formatted']));
            } else {
                $script = array_merge($script,
                    array(static::call_array('groupCollapsed', static::handleStyles($record['formatted']))),
                    $context,
                    $extra,
                    array(static::call('groupEnd'))
                );
            }
        }

        return "(function (c) {if (c && c.groupCollapsed) {\n" . implode("\n", $script) . "\n}})(console);";
    }

    private static function handleStyles($formatted)
    {
        $args = array(static::quote('font-weight: normal'));
        $format = '%c' . $formatted;
        preg_match_all('/\[\[(.*?)\]\]\{([^}]*)\}/s', $format, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);

        foreach (array_reverse($matches) as $match) {
            $args[] = static::quote(static::handleCustomStyles($match[2][0], $match[1][0]));
            $args[] = '"font-weight: normal"';

            $pos = $match[0][1];
            $format = substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . substr($format, $pos + strlen($match[0][0]));
        }

        array_unshift($args, static::quote($format));

        return $args;
    }

    private static function handleCustomStyles($style, $string)
    {
        static $colors = array('blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey');
        static $labels = array();

        return preg_replace_callback('/macro\s*:(.*?)(?:;|$)/', function ($m) use ($string, &$colors, &$labels) {
            if (trim($m[1]) === 'autolabel') {
                // Format the string as a label with consistent auto assigned background color
                if (!isset($labels[$string])) {
                    $labels[$string] = $colors[count($labels) % count($colors)];
                }
                $color = $labels[$string];

                return "background-color: $color; color: white; border-radius: 3px; padding: 0 2px 0 2px";
            }

            return $m[1];
        }, $style);
    }

    private static function dump($title, array $dict)
    {
        $script = array();
        $dict = array_filter($dict);
        if (empty($dict)) {
            return $script;
        }
        $script[] = static::call('log', static::quote('%c%s'), static::quote('font-weight: bold'), static::quote($title));
        foreach ($dict as $key => $value) {
            $value = json_encode($value);
            if (empty($value)) {
                $value = static::quote('');
            }
            $script[] = static::call('log', static::quote('%s: %o'), static::quote($key), $value);
        }

        return $script;
    }

    private static function quote($arg)
    {
        return '"' . addcslashes($arg, "\"\n\\") . '"';
    }

    private static function call()
    {
        $args = func_get_args();
        $method = array_shift($args);

        return static::call_array($method, $args);
    }

    private static function call_array($method, array $args)
    {
        return 'c.' . $method . '(' . implode(', ', $args) . ');';
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;

/**
 * Simple handler wrapper that filters records based on a list of levels
 *
 * It can be configured with an exact list of levels to allow, or a min/max level.
 *
 * @author Hennadiy Verkh
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class FilterHandler extends AbstractHandler
{
    /**
     * Handler or factory callable($record, $this)
     *
     * @var callable|\Monolog\Handler\HandlerInterface
     */
    protected $handler;

    /**
     * Minimum level for logs that are passed to handler
     *
     * @var int[]
     */
    protected $acceptedLevels;

    /**
     * Whether the messages that are handled can bubble up the stack or not
     *
     * @var bool
     */
    protected $bubble;

    /**
     * @param callable|HandlerInterface $handler        Handler or factory callable($record, $this).
     * @param int|array                 $minLevelOrList A list of levels to accept or a minimum level if maxLevel is provided
     * @param int                       $maxLevel       Maximum level to accept, only used if $minLevelOrList is not an array
     * @param bool                      $bubble         Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct($handler, $minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY, $bubble = true)
    {
        $this->handler  = $handler;
        $this->bubble   = $bubble;
        $this->setAcceptedLevels($minLevelOrList, $maxLevel);

        if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) {
            throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object");
        }
    }

    /**
     * @return array
     */
    public function getAcceptedLevels()
    {
        return array_flip($this->acceptedLevels);
    }

    /**
     * @param int|string|array $minLevelOrList A list of levels to accept or a minimum level or level name if maxLevel is provided
     * @param int|string       $maxLevel       Maximum level or level name to accept, only used if $minLevelOrList is not an array
     */
    public function setAcceptedLevels($minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY)
    {
        if (is_array($minLevelOrList)) {
            $acceptedLevels = array_map('Monolog\Logger::toMonologLevel', $minLevelOrList);
        } else {
            $minLevelOrList = Logger::toMonologLevel($minLevelOrList);
            $maxLevel = Logger::toMonologLevel($maxLevel);
            $acceptedLevels = array_values(array_filter(Logger::getLevels(), function ($level) use ($minLevelOrList, $maxLevel) {
                return $level >= $minLevelOrList && $level <= $maxLevel;
            }));
        }
        $this->acceptedLevels = array_flip($acceptedLevels);
    }

    /**
     * {@inheritdoc}
     */
    public function isHandling(array $record)
    {
        return isset($this->acceptedLevels[$record['level']]);
    }

    /**
     * {@inheritdoc}
     */
    public function handle(array $record)
    {
        if (!$this->isHandling($record)) {
            return false;
        }

        // The same logic as in FingersCrossedHandler
        if (!$this->handler instanceof HandlerInterface) {
            $this->handler = call_user_func($this->handler, $record, $this);
            if (!$this->handler instanceof HandlerInterface) {
                throw new \RuntimeException("The factory callable should return a HandlerInterface");
            }
        }

        if ($this->processors) {
            foreach ($this->processors as $processor) {
                $record = call_user_func($processor, $record);
            }
        }

        $this->handler->handle($record);

        return false === $this->bubble;
    }

    /**
     * {@inheritdoc}
     */
    public function handleBatch(array $records)
    {
        $filtered = array();
        foreach ($records as $record) {
            if ($this->isHandling($record)) {
                $filtered[] = $record;
            }
        }

        $this->handler->handleBatch($filtered);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;

/**
 * Stores logs to files that are rotated every day and a limited number of files are kept.
 *
 * This rotation is only intended to be used as a workaround. Using logrotate to
 * handle the rotation is strongly encouraged when you can use it.
 *
 * @author Christophe Coevoet <stof@notk.org>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class RotatingFileHandler extends StreamHandler
{
    const FILE_PER_DAY = 'Y-m-d';
    const FILE_PER_MONTH = 'Y-m';
    const FILE_PER_YEAR = 'Y';

    protected $filename;
    protected $maxFiles;
    protected $mustRotate;
    protected $nextRotation;
    protected $filenameFormat;
    protected $dateFormat;

    /**
     * @param string   $filename
     * @param int      $maxFiles       The maximal amount of files to keep (0 means unlimited)
     * @param int      $level          The minimum logging level at which this handler will be triggered
     * @param bool     $bubble         Whether the messages that are handled can bubble up the stack or not
     * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write)
     * @param bool     $useLocking     Try to lock log file before doing any writes
     */
    public function __construct($filename, $maxFiles = 0, $level = Logger::DEBUG, $bubble = true, $filePermission = null, $useLocking = false)
    {
        $this->filename = $filename;
        $this->maxFiles = (int) $maxFiles;
        $this->nextRotation = new \DateTime('tomorrow');
        $this->filenameFormat = '{filename}-{date}';
        $this->dateFormat = 'Y-m-d';

        parent::__construct($this->getTimedFilename(), $level, $bubble, $filePermission, $useLocking);
    }

    /**
     * {@inheritdoc}
     */
    public function close()
    {
        parent::close();

        if (true === $this->mustRotate) {
            $this->rotate();
        }
    }

    /**
     * {@inheritdoc}
     */
    public function reset()
    {
        parent::reset();

        if (true === $this->mustRotate) {
            $this->rotate();
        }
    }

    public function setFilenameFormat($filenameFormat, $dateFormat)
    {
        if (!preg_match('{^Y(([/_.-]?m)([/_.-]?d)?)?$}', $dateFormat)) {
            trigger_error(
                'Invalid date format - format must be one of '.
                'RotatingFileHandler::FILE_PER_DAY ("Y-m-d"), RotatingFileHandler::FILE_PER_MONTH ("Y-m") '.
                'or RotatingFileHandler::FILE_PER_YEAR ("Y"), or you can set one of the '.
                'date formats using slashes, underscores and/or dots instead of dashes.',
                E_USER_DEPRECATED
            );
        }
        if (substr_count($filenameFormat, '{date}') === 0) {
            trigger_error(
                'Invalid filename format - format should contain at least `{date}`, because otherwise rotating is impossible.',
                E_USER_DEPRECATED
            );
        }
        $this->filenameFormat = $filenameFormat;
        $this->dateFormat = $dateFormat;
        $this->url = $this->getTimedFilename();
        $this->close();
    }

    /**
     * {@inheritdoc}
     */
    protected function write(array $record)
    {
        // on the first record written, if the log is new, we should rotate (once per day)
        if (null === $this->mustRotate) {
            $this->mustRotate = !file_exists($this->url);
        }

        if ($this->nextRotation < $record['datetime']) {
            $this->mustRotate = true;
            $this->close();
        }

        parent::write($record);
    }

    /**
     * Rotates the files.
     */
    protected function rotate()
    {
        // update filename
        $this->url = $this->getTimedFilename();
        $this->nextRotation = new \DateTime('tomorrow');

        // skip GC of old logs if files are unlimited
        if (0 === $this->maxFiles) {
            return;
        }

        $logFiles = glob($this->getGlobPattern());
        if ($this->maxFiles >= count($logFiles)) {
            // no files to remove
            return;
        }

        // Sorting the files by name to remove the older ones
        usort($logFiles, function ($a, $b) {
            return strcmp($b, $a);
        });

        foreach (array_slice($logFiles, $this->maxFiles) as $file) {
            if (is_writable($file)) {
                // suppress errors here as unlink() might fail if two processes
                // are cleaning up/rotating at the same time
                set_error_handler(function ($errno, $errstr, $errfile, $errline) {});
                unlink($file);
                restore_error_handler();
            }
        }

        $this->mustRotate = false;
    }

    protected function getTimedFilename()
    {
        $fileInfo = pathinfo($this->filename);
        $timedFilename = str_replace(
            array('{filename}', '{date}'),
            array($fileInfo['filename'], date($this->dateFormat)),
            $fileInfo['dirname'] . '/' . $this->filenameFormat
        );

        if (!empty($fileInfo['extension'])) {
            $timedFilename .= '.'.$fileInfo['extension'];
        }

        return $timedFilename;
    }

    protected function getGlobPattern()
    {
        $fileInfo = pathinfo($this->filename);
        $glob = str_replace(
            array('{filename}', '{date}'),
            array($fileInfo['filename'], '[0-9][0-9][0-9][0-9]*'),
            $fileInfo['dirname'] . '/' . $this->filenameFormat
        );
        if (!empty($fileInfo['extension'])) {
            $glob .= '.'.$fileInfo['extension'];
        }

        return $glob;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;
use Monolog\Formatter\LineFormatter;

/**
 * NativeMailerHandler uses the mail() function to send the emails
 *
 * @author Christophe Coevoet <stof@notk.org>
 * @author Mark Garrett <mark@moderndeveloperllc.com>
 */
class NativeMailerHandler extends MailHandler
{
    /**
     * The email addresses to which the message will be sent
     * @var array
     */
    protected $to;

    /**
     * The subject of the email
     * @var string
     */
    protected $subject;

    /**
     * Optional headers for the message
     * @var array
     */
    protected $headers = array();

    /**
     * Optional parameters for the message
     * @var array
     */
    protected $parameters = array();

    /**
     * The wordwrap length for the message
     * @var int
     */
    protected $maxColumnWidth;

    /**
     * The Content-type for the message
     * @var string
     */
    protected $contentType = 'text/plain';

    /**
     * The encoding for the message
     * @var string
     */
    protected $encoding = 'utf-8';

    /**
     * @param string|array $to             The receiver of the mail
     * @param string       $subject        The subject of the mail
     * @param string       $from           The sender of the mail
     * @param int          $level          The minimum logging level at which this handler will be triggered
     * @param bool         $bubble         Whether the messages that are handled can bubble up the stack or not
     * @param int          $maxColumnWidth The maximum column width that the message lines will have
     */
    public function __construct($to, $subject, $from, $level = Logger::ERROR, $bubble = true, $maxColumnWidth = 70)
    {
        parent::__construct($level, $bubble);
        $this->to = is_array($to) ? $to : array($to);
        $this->subject = $subject;
        $this->addHeader(sprintf('From: %s', $from));
        $this->maxColumnWidth = $maxColumnWidth;
    }

    /**
     * Add headers to the message
     *
     * @param  string|array $headers Custom added headers
     * @return self
     */
    public function addHeader($headers)
    {
        foreach ((array) $headers as $header) {
            if (strpos($header, "\n") !== false || strpos($header, "\r") !== false) {
                throw new \InvalidArgumentException('Headers can not contain newline characters for security reasons');
            }
            $this->headers[] = $header;
        }

        return $this;
    }

    /**
     * Add parameters to the message
     *
     * @param  string|array $parameters Custom added parameters
     * @return self
     */
    public function addParameter($parameters)
    {
        $this->parameters = array_merge($this->parameters, (array) $parameters);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    protected function send($content, array $records)
    {
        $content = wordwrap($content, $this->maxColumnWidth);
        $headers = ltrim(implode("\r\n", $this->headers) . "\r\n", "\r\n");
        $headers .= 'Content-type: ' . $this->getContentType() . '; charset=' . $this->getEncoding() . "\r\n";
        if ($this->getContentType() == 'text/html' && false === strpos($headers, 'MIME-Version:')) {
            $headers .= 'MIME-Version: 1.0' . "\r\n";
        }

        $subject = $this->subject;
        if ($records) {
            $subjectFormatter = new LineFormatter($this->subject);
            $subject = $subjectFormatter->format($this->getHighestRecord($records));
        }

        $parameters = implode(' ', $this->parameters);
        foreach ($this->to as $to) {
            mail($to, $subject, $content, $headers, $parameters);
        }
    }

    /**
     * @return string $contentType
     */
    public function getContentType()
    {
        return $this->contentType;
    }

    /**
     * @return string $encoding
     */
    public function getEncoding()
    {
        return $this->encoding;
    }

    /**
     * @param  string $contentType The content type of the email - Defaults to text/plain. Use text/html for HTML
     *                             messages.
     * @return self
     */
    public function setContentType($contentType)
    {
        if (strpos($contentType, "\n") !== false || strpos($contentType, "\r") !== false) {
            throw new \InvalidArgumentException('The content type can not contain newline characters to prevent email header injection');
        }

        $this->contentType = $contentType;

        return $this;
    }

    /**
     * @param  string $encoding
     * @return self
     */
    public function setEncoding($encoding)
    {
        if (strpos($encoding, "\n") !== false || strpos($encoding, "\r") !== false) {
            throw new \InvalidArgumentException('The encoding can not contain newline characters to prevent email header injection');
        }

        $this->encoding = $encoding;

        return $this;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;
use Monolog\Formatter\NormalizerFormatter;

/**
 * Logs to a MongoDB database.
 *
 * usage example:
 *
 *   $log = new Logger('application');
 *   $mongodb = new MongoDBHandler(new \Mongo("mongodb://localhost:27017"), "logs", "prod");
 *   $log->pushHandler($mongodb);
 *
 * @author Thomas Tourlourat <thomas@tourlourat.com>
 */
class MongoDBHandler extends AbstractProcessingHandler
{
    protected $mongoCollection;

    public function __construct($mongo, $database, $collection, $level = Logger::DEBUG, $bubble = true)
    {
        if (!($mongo instanceof \MongoClient || $mongo instanceof \Mongo || $mongo instanceof \MongoDB\Client)) {
            throw new \InvalidArgumentException('MongoClient, Mongo or MongoDB\Client instance required');
        }

        $this->mongoCollection = $mongo->selectCollection($database, $collection);

        parent::__construct($level, $bubble);
    }

    protected function write(array $record)
    {
        if ($this->mongoCollection instanceof \MongoDB\Collection) {
            $this->mongoCollection->insertOne($record["formatted"]);
        } else {
            $this->mongoCollection->save($record["formatted"]);
        }
    }

    /**
     * {@inheritDoc}
     */
    protected function getDefaultFormatter()
    {
        return new NormalizerFormatter();
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Gelf\IMessagePublisher;
use Gelf\PublisherInterface;
use Gelf\Publisher;
use InvalidArgumentException;
use Monolog\Logger;
use Monolog\Formatter\GelfMessageFormatter;

/**
 * Handler to send messages to a Graylog2 (http://www.graylog2.org) server
 *
 * @author Matt Lehner <mlehner@gmail.com>
 * @author Benjamin Zikarsky <benjamin@zikarsky.de>
 */
class GelfHandler extends AbstractProcessingHandler
{
    /**
     * @var Publisher the publisher object that sends the message to the server
     */
    protected $publisher;

    /**
     * @param PublisherInterface|IMessagePublisher|Publisher $publisher a publisher object
     * @param int                                            $level     The minimum logging level at which this handler will be triggered
     * @param bool                                           $bubble    Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct($publisher, $level = Logger::DEBUG, $bubble = true)
    {
        parent::__construct($level, $bubble);

        if (!$publisher instanceof Publisher && !$publisher instanceof IMessagePublisher && !$publisher instanceof PublisherInterface) {
            throw new InvalidArgumentException('Invalid publisher, expected a Gelf\Publisher, Gelf\IMessagePublisher or Gelf\PublisherInterface instance');
        }

        $this->publisher = $publisher;
    }

    /**
     * {@inheritdoc}
     */
    protected function write(array $record)
    {
        $this->publisher->publish($record['formatted']);
    }

    /**
     * {@inheritDoc}
     */
    protected function getDefaultFormatter()
    {
        return new GelfMessageFormatter();
    }
}
<?php
/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\NormalizerFormatter;
use Monolog\Logger;

/**
 * Handler sending logs to Zend Monitor
 *
 * @author  Christian Bergau <cbergau86@gmail.com>
 */
class ZendMonitorHandler extends AbstractProcessingHandler
{
    /**
     * Monolog level / ZendMonitor Custom Event priority map
     *
     * @var array
     */
    protected $levelMap = array(
        Logger::DEBUG     => 1,
        Logger::INFO      => 2,
        Logger::NOTICE    => 3,
        Logger::WARNING   => 4,
        Logger::ERROR     => 5,
        Logger::CRITICAL  => 6,
        Logger::ALERT     => 7,
        Logger::EMERGENCY => 0,
    );

    /**
     * Construct
     *
     * @param  int                       $level
     * @param  bool                      $bubble
     * @throws MissingExtensionException
     */
    public function __construct($level = Logger::DEBUG, $bubble = true)
    {
        if (!function_exists('zend_monitor_custom_event')) {
            throw new MissingExtensionException('You must have Zend Server installed in order to use this handler');
        }
        parent::__construct($level, $bubble);
    }

    /**
     * {@inheritdoc}
     */
    protected function write(array $record)
    {
        $this->writeZendMonitorCustomEvent(
            $this->levelMap[$record['level']],
            $record['message'],
            $record['formatted']
        );
    }

    /**
     * Write a record to Zend Monitor
     *
     * @param int    $level
     * @param string $message
     * @param array  $formatted
     */
    protected function writeZendMonitorCustomEvent($level, $message, $formatted)
    {
        zend_monitor_custom_event($level, $message, $formatted);
    }

    /**
     * {@inheritdoc}
     */
    public function getDefaultFormatter()
    {
        return new NormalizerFormatter();
    }

    /**
     * Get the level map
     *
     * @return array
     */
    public function getLevelMap()
    {
        return $this->levelMap;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Exception;
use Monolog\Formatter\LineFormatter;
use Monolog\Logger;
use PhpConsole\Connector;
use PhpConsole\Handler;
use PhpConsole\Helper;

/**
 * Monolog handler for Google Chrome extension "PHP Console"
 *
 * Display PHP error/debug log messages in Google Chrome console and notification popups, executes PHP code remotely
 *
 * Usage:
 * 1. Install Google Chrome extension https://chrome.google.com/webstore/detail/php-console/nfhmhhlpfleoednkpnnnkolmclajemef
 * 2. See overview https://github.com/barbushin/php-console#overview
 * 3. Install PHP Console library https://github.com/barbushin/php-console#installation
 * 4. Example (result will looks like http://i.hizliresim.com/vg3Pz4.png)
 *
 *      $logger = new \Monolog\Logger('all', array(new \Monolog\Handler\PHPConsoleHandler()));
 *      \Monolog\ErrorHandler::register($logger);
 *      echo $undefinedVar;
 *      $logger->addDebug('SELECT * FROM users', array('db', 'time' => 0.012));
 *      PC::debug($_SERVER); // PHP Console debugger for any type of vars
 *
 * @author Sergey Barbushin https://www.linkedin.com/in/barbushin
 */
class PHPConsoleHandler extends AbstractProcessingHandler
{
    private $options = array(
        'enabled' => true, // bool Is PHP Console server enabled
        'classesPartialsTraceIgnore' => array('Monolog\\'), // array Hide calls of classes started with...
        'debugTagsKeysInContext' => array(0, 'tag'), // bool Is PHP Console server enabled
        'useOwnErrorsHandler' => false, // bool Enable errors handling
        'useOwnExceptionsHandler' => false, // bool Enable exceptions handling
        'sourcesBasePath' => null, // string Base path of all project sources to strip in errors source paths
        'registerHelper' => true, // bool Register PhpConsole\Helper that allows short debug calls like PC::debug($var, 'ta.g.s')
        'serverEncoding' => null, // string|null Server internal encoding
        'headersLimit' => null, // int|null Set headers size limit for your web-server
        'password' => null, // string|null Protect PHP Console connection by password
        'enableSslOnlyMode' => false, // bool Force connection by SSL for clients with PHP Console installed
        'ipMasks' => array(), // array Set IP masks of clients that will be allowed to connect to PHP Console: array('192.168.*.*', '127.0.0.1')
        'enableEvalListener' => false, // bool Enable eval request to be handled by eval dispatcher(if enabled, 'password' option is also required)
        'dumperDetectCallbacks' => false, // bool Convert callback items in dumper vars to (callback SomeClass::someMethod) strings
        'dumperLevelLimit' => 5, // int Maximum dumped vars array or object nested dump level
        'dumperItemsCountLimit' => 100, // int Maximum dumped var same level array items or object properties number
        'dumperItemSizeLimit' => 5000, // int Maximum length of any string or dumped array item
        'dumperDumpSizeLimit' => 500000, // int Maximum approximate size of dumped vars result formatted in JSON
        'detectDumpTraceAndSource' => false, // bool Autodetect and append trace data to debug
        'dataStorage' => null, // PhpConsole\Storage|null Fixes problem with custom $_SESSION handler(see http://goo.gl/Ne8juJ)
    );

    /** @var Connector */
    private $connector;

    /**
     * @param  array          $options   See \Monolog\Handler\PHPConsoleHandler::$options for more details
     * @param  Connector|null $connector Instance of \PhpConsole\Connector class (optional)
     * @param  int            $level
     * @param  bool           $bubble
     * @throws Exception
     */
    public function __construct(array $options = array(), Connector $connector = null, $level = Logger::DEBUG, $bubble = true)
    {
        if (!class_exists('PhpConsole\Connector')) {
            throw new Exception('PHP Console library not found. See https://github.com/barbushin/php-console#installation');
        }
        parent::__construct($level, $bubble);
        $this->options = $this->initOptions($options);
        $this->connector = $this->initConnector($connector);
    }

    private function initOptions(array $options)
    {
        $wrongOptions = array_diff(array_keys($options), array_keys($this->options));
        if ($wrongOptions) {
            throw new Exception('Unknown options: ' . implode(', ', $wrongOptions));
        }

        return array_replace($this->options, $options);
    }

    private function initConnector(Connector $connector = null)
    {
        if (!$connector) {
            if ($this->options['dataStorage']) {
                Connector::setPostponeStorage($this->options['dataStorage']);
            }
            $connector = Connector::getInstance();
        }

        if ($this->options['registerHelper'] && !Helper::isRegistered()) {
            Helper::register();
        }

        if ($this->options['enabled'] && $connector->isActiveClient()) {
            if ($this->options['useOwnErrorsHandler'] || $this->options['useOwnExceptionsHandler']) {
                $handler = Handler::getInstance();
                $handler->setHandleErrors($this->options['useOwnErrorsHandler']);
                $handler->setHandleExceptions($this->options['useOwnExceptionsHandler']);
                $handler->start();
            }
            if ($this->options['sourcesBasePath']) {
                $connector->setSourcesBasePath($this->options['sourcesBasePath']);
            }
            if ($this->options['serverEncoding']) {
                $connector->setServerEncoding($this->options['serverEncoding']);
            }
            if ($this->options['password']) {
                $connector->setPassword($this->options['password']);
            }
            if ($this->options['enableSslOnlyMode']) {
                $connector->enableSslOnlyMode();
            }
            if ($this->options['ipMasks']) {
                $connector->setAllowedIpMasks($this->options['ipMasks']);
            }
            if ($this->options['headersLimit']) {
                $connector->setHeadersLimit($this->options['headersLimit']);
            }
            if ($this->options['detectDumpTraceAndSource']) {
                $connector->getDebugDispatcher()->detectTraceAndSource = true;
            }
            $dumper = $connector->getDumper();
            $dumper->levelLimit = $this->options['dumperLevelLimit'];
            $dumper->itemsCountLimit = $this->options['dumperItemsCountLimit'];
            $dumper->itemSizeLimit = $this->options['dumperItemSizeLimit'];
            $dumper->dumpSizeLimit = $this->options['dumperDumpSizeLimit'];
            $dumper->detectCallbacks = $this->options['dumperDetectCallbacks'];
            if ($this->options['enableEvalListener']) {
                $connector->startEvalRequestsListener();
            }
        }

        return $connector;
    }

    public function getConnector()
    {
        return $this->connector;
    }

    public function getOptions()
    {
        return $this->options;
    }

    public function handle(array $record)
    {
        if ($this->options['enabled'] && $this->connector->isActiveClient()) {
            return parent::handle($record);
        }

        return !$this->bubble;
    }

    /**
     * Writes the record down to the log of the implementing handler
     *
     * @param  array $record
     * @return void
     */
    protected function write(array $record)
    {
        if ($record['level'] < Logger::NOTICE) {
            $this->handleDebugRecord($record);
        } elseif (isset($record['context']['exception']) && $record['context']['exception'] instanceof Exception) {
            $this->handleExceptionRecord($record);
        } else {
            $this->handleErrorRecord($record);
        }
    }

    private function handleDebugRecord(array $record)
    {
        $tags = $this->getRecordTags($record);
        $message = $record['message'];
        if ($record['context']) {
            $message .= ' ' . json_encode($this->connector->getDumper()->dump(array_filter($record['context'])));
        }
        $this->connector->getDebugDispatcher()->dispatchDebug($message, $tags, $this->options['classesPartialsTraceIgnore']);
    }

    private function handleExceptionRecord(array $record)
    {
        $this->connector->getErrorsDispatcher()->dispatchException($record['context']['exception']);
    }

    private function handleErrorRecord(array $record)
    {
        $context = $record['context'];

        $this->connector->getErrorsDispatcher()->dispatchError(
            isset($context['code']) ? $context['code'] : null,
            isset($context['message']) ? $context['message'] : $record['message'],
            isset($context['file']) ? $context['file'] : null,
            isset($context['line']) ? $context['line'] : null,
            $this->options['classesPartialsTraceIgnore']
        );
    }

    private function getRecordTags(array &$record)
    {
        $tags = null;
        if (!empty($record['context'])) {
            $context = & $record['context'];
            foreach ($this->options['debugTagsKeysInContext'] as $key) {
                if (!empty($context[$key])) {
                    $tags = $context[$key];
                    if ($key === 0) {
                        array_shift($context);
                    } else {
                        unset($context[$key]);
                    }
                    break;
                }
            }
        }

        return $tags ?: strtolower($record['level_name']);
    }

    /**
     * {@inheritDoc}
     */
    protected function getDefaultFormatter()
    {
        return new LineFormatter('%message%');
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\LineFormatter;
use Monolog\Logger;

/**
 * Stores to PHP error_log() handler.
 *
 * @author Elan RuusamÃ¤e <glen@delfi.ee>
 */
class ErrorLogHandler extends AbstractProcessingHandler
{
    const OPERATING_SYSTEM = 0;
    const SAPI = 4;

    protected $messageType;
    protected $expandNewlines;

    /**
     * @param int  $messageType    Says where the error should go.
     * @param int  $level          The minimum logging level at which this handler will be triggered
     * @param bool $bubble         Whether the messages that are handled can bubble up the stack or not
     * @param bool $expandNewlines If set to true, newlines in the message will be expanded to be take multiple log entries
     */
    public function __construct($messageType = self::OPERATING_SYSTEM, $level = Logger::DEBUG, $bubble = true, $expandNewlines = false)
    {
        parent::__construct($level, $bubble);

        if (false === in_array($messageType, self::getAvailableTypes())) {
            $message = sprintf('The given message type "%s" is not supported', print_r($messageType, true));
            throw new \InvalidArgumentException($message);
        }

        $this->messageType = $messageType;
        $this->expandNewlines = $expandNewlines;
    }

    /**
     * @return array With all available types
     */
    public static function getAvailableTypes()
    {
        return array(
            self::OPERATING_SYSTEM,
            self::SAPI,
        );
    }

    /**
     * {@inheritDoc}
     */
    protected function getDefaultFormatter()
    {
        return new LineFormatter('[%datetime%] %channel%.%level_name%: %message% %context% %extra%');
    }

    /**
     * {@inheritdoc}
     */
    protected function write(array $record)
    {
        if ($this->expandNewlines) {
            $lines = preg_split('{[\r\n]+}', (string) $record['formatted']);
            foreach ($lines as $line) {
                error_log($line, $this->messageType);
            }
        } else {
            error_log((string) $record['formatted'], $this->messageType);
        }
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;

/**
 * @author Robert Kaufmann III <rok3@rok3.me>
 */
class LogEntriesHandler extends SocketHandler
{
    /**
     * @var string
     */
    protected $logToken;

    /**
     * @param string $token  Log token supplied by LogEntries
     * @param bool   $useSSL Whether or not SSL encryption should be used.
     * @param int    $level  The minimum logging level to trigger this handler
     * @param bool   $bubble Whether or not messages that are handled should bubble up the stack.
     *
     * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing
     */
    public function __construct($token, $useSSL = true, $level = Logger::DEBUG, $bubble = true, $host = 'data.logentries.com')
    {
        if ($useSSL && !extension_loaded('openssl')) {
            throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for LogEntriesHandler');
        }

        $endpoint = $useSSL ? 'ssl://' . $host . ':443' : $host . ':80';
        parent::__construct($endpoint, $level, $bubble);
        $this->logToken = $token;
    }

    /**
     * {@inheritdoc}
     *
     * @param  array  $record
     * @return string
     */
    protected function generateDataStream($record)
    {
        return $this->logToken . ' ' . $record['formatted'];
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;

/**
 * Simple handler wrapper that deduplicates log records across multiple requests
 *
 * It also includes the BufferHandler functionality and will buffer
 * all messages until the end of the request or flush() is called.
 *
 * This works by storing all log records' messages above $deduplicationLevel
 * to the file specified by $deduplicationStore. When further logs come in at the end of the
 * request (or when flush() is called), all those above $deduplicationLevel are checked
 * against the existing stored logs. If they match and the timestamps in the stored log is
 * not older than $time seconds, the new log record is discarded. If no log record is new, the
 * whole data set is discarded.
 *
 * This is mainly useful in combination with Mail handlers or things like Slack or HipChat handlers
 * that send messages to people, to avoid spamming with the same message over and over in case of
 * a major component failure like a database server being down which makes all requests fail in the
 * same way.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class DeduplicationHandler extends BufferHandler
{
    /**
     * @var string
     */
    protected $deduplicationStore;

    /**
     * @var int
     */
    protected $deduplicationLevel;

    /**
     * @var int
     */
    protected $time;

    /**
     * @var bool
     */
    private $gc = false;

    /**
     * @param HandlerInterface $handler            Handler.
     * @param string           $deduplicationStore The file/path where the deduplication log should be kept
     * @param int              $deduplicationLevel The minimum logging level for log records to be looked at for deduplication purposes
     * @param int              $time               The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through
     * @param bool             $bubble             Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct(HandlerInterface $handler, $deduplicationStore = null, $deduplicationLevel = Logger::ERROR, $time = 60, $bubble = true)
    {
        parent::__construct($handler, 0, Logger::DEBUG, $bubble, false);

        $this->deduplicationStore = $deduplicationStore === null ? sys_get_temp_dir() . '/monolog-dedup-' . substr(md5(__FILE__), 0, 20) .'.log' : $deduplicationStore;
        $this->deduplicationLevel = Logger::toMonologLevel($deduplicationLevel);
        $this->time = $time;
    }

    public function flush()
    {
        if ($this->bufferSize === 0) {
            return;
        }

        $passthru = null;

        foreach ($this->buffer as $record) {
            if ($record['level'] >= $this->deduplicationLevel) {

                $passthru = $passthru || !$this->isDuplicate($record);
                if ($passthru) {
                    $this->appendRecord($record);
                }
            }
        }

        // default of null is valid as well as if no record matches duplicationLevel we just pass through
        if ($passthru === true || $passthru === null) {
            $this->handler->handleBatch($this->buffer);
        }

        $this->clear();

        if ($this->gc) {
            $this->collectLogs();
        }
    }

    private function isDuplicate(array $record)
    {
        if (!file_exists($this->deduplicationStore)) {
            return false;
        }

        $store = file($this->deduplicationStore, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        if (!is_array($store)) {
            return false;
        }

        $yesterday = time() - 86400;
        $timestampValidity = $record['datetime']->getTimestamp() - $this->time;
        $expectedMessage = preg_replace('{[\r\n].*}', '', $record['message']);

        for ($i = count($store) - 1; $i >= 0; $i--) {
            list($timestamp, $level, $message) = explode(':', $store[$i], 3);

            if ($level === $record['level_name'] && $message === $expectedMessage && $timestamp > $timestampValidity) {
                return true;
            }

            if ($timestamp < $yesterday) {
                $this->gc = true;
            }
        }

        return false;
    }

    private function collectLogs()
    {
        if (!file_exists($this->deduplicationStore)) {
            return false;
        }

        $handle = fopen($this->deduplicationStore, 'rw+');
        flock($handle, LOCK_EX);
        $validLogs = array();

        $timestampValidity = time() - $this->time;

        while (!feof($handle)) {
            $log = fgets($handle);
            if (substr($log, 0, 10) >= $timestampValidity) {
                $validLogs[] = $log;
            }
        }

        ftruncate($handle, 0);
        rewind($handle);
        foreach ($validLogs as $log) {
            fwrite($handle, $log);
        }

        flock($handle, LOCK_UN);
        fclose($handle);

        $this->gc = false;
    }

    private function appendRecord(array $record)
    {
        file_put_contents($this->deduplicationStore, $record['datetime']->getTimestamp() . ':' . $record['level_name'] . ':' . preg_replace('{[\r\n].*}', '', $record['message']) . "\n", FILE_APPEND);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\WildfireFormatter;

/**
 * Simple FirePHP Handler (http://www.firephp.org/), which uses the Wildfire protocol.
 *
 * @author Eric Clemmons (@ericclemmons) <eric@uxdriven.com>
 */
class FirePHPHandler extends AbstractProcessingHandler
{
    /**
     * WildFire JSON header message format
     */
    const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2';

    /**
     * FirePHP structure for parsing messages & their presentation
     */
    const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1';

    /**
     * Must reference a "known" plugin, otherwise headers won't display in FirePHP
     */
    const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3';

    /**
     * Header prefix for Wildfire to recognize & parse headers
     */
    const HEADER_PREFIX = 'X-Wf';

    /**
     * Whether or not Wildfire vendor-specific headers have been generated & sent yet
     */
    protected static $initialized = false;

    /**
     * Shared static message index between potentially multiple handlers
     * @var int
     */
    protected static $messageIndex = 1;

    protected static $sendHeaders = true;

    /**
     * Base header creation function used by init headers & record headers
     *
     * @param  array  $meta    Wildfire Plugin, Protocol & Structure Indexes
     * @param  string $message Log message
     * @return array  Complete header string ready for the client as key and message as value
     */
    protected function createHeader(array $meta, $message)
    {
        $header = sprintf('%s-%s', self::HEADER_PREFIX, join('-', $meta));

        return array($header => $message);
    }

    /**
     * Creates message header from record
     *
     * @see createHeader()
     * @param  array  $record
     * @return string
     */
    protected function createRecordHeader(array $record)
    {
        // Wildfire is extensible to support multiple protocols & plugins in a single request,
        // but we're not taking advantage of that (yet), so we're using "1" for simplicity's sake.
        return $this->createHeader(
            array(1, 1, 1, self::$messageIndex++),
            $record['formatted']
        );
    }

    /**
     * {@inheritDoc}
     */
    protected function getDefaultFormatter()
    {
        return new WildfireFormatter();
    }

    /**
     * Wildfire initialization headers to enable message parsing
     *
     * @see createHeader()
     * @see sendHeader()
     * @return array
     */
    protected function getInitHeaders()
    {
        // Initial payload consists of required headers for Wildfire
        return array_merge(
            $this->createHeader(array('Protocol', 1), self::PROTOCOL_URI),
            $this->createHeader(array(1, 'Structure', 1), self::STRUCTURE_URI),
            $this->createHeader(array(1, 'Plugin', 1), self::PLUGIN_URI)
        );
    }

    /**
     * Send header string to the client
     *
     * @param string $header
     * @param string $content
     */
    protected function sendHeader($header, $content)
    {
        if (!headers_sent() && self::$sendHeaders) {
            header(sprintf('%s: %s', $header, $content));
        }
    }

    /**
     * Creates & sends header for a record, ensuring init headers have been sent prior
     *
     * @see sendHeader()
     * @see sendInitHeaders()
     * @param array $record
     */
    protected function write(array $record)
    {
        if (!self::$sendHeaders) {
            return;
        }

        // WildFire-specific headers must be sent prior to any messages
        if (!self::$initialized) {
            self::$initialized = true;

            self::$sendHeaders = $this->headersAccepted();
            if (!self::$sendHeaders) {
                return;
            }

            foreach ($this->getInitHeaders() as $header => $content) {
                $this->sendHeader($header, $content);
            }
        }

        $header = $this->createRecordHeader($record);
        if (trim(current($header)) !== '') {
            $this->sendHeader(key($header), current($header));
        }
    }

    /**
     * Verifies if the headers are accepted by the current user agent
     *
     * @return bool
     */
    protected function headersAccepted()
    {
        if (!empty($_SERVER['HTTP_USER_AGENT']) && preg_match('{\bFirePHP/\d+\.\d+\b}', $_SERVER['HTTP_USER_AGENT'])) {
            return true;
        }

        return isset($_SERVER['HTTP_X_FIREPHP_VERSION']);
    }

    /**
     * BC getter for the sendHeaders property that has been made static
     */
    public function __get($property)
    {
        if ('sendHeaders' !== $property) {
            throw new \InvalidArgumentException('Undefined property '.$property);
        }

        return static::$sendHeaders;
    }

    /**
     * BC setter for the sendHeaders property that has been made static
     */
    public function __set($property, $value)
    {
        if ('sendHeaders' !== $property) {
            throw new \InvalidArgumentException('Undefined property '.$property);
        }

        static::$sendHeaders = $value;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler\Curl;

class Util
{
    private static $retriableErrorCodes = array(
        CURLE_COULDNT_RESOLVE_HOST,
        CURLE_COULDNT_CONNECT,
        CURLE_HTTP_NOT_FOUND,
        CURLE_READ_ERROR,
        CURLE_OPERATION_TIMEOUTED,
        CURLE_HTTP_POST_ERROR,
        CURLE_SSL_CONNECT_ERROR,
    );

    /**
     * Executes a CURL request with optional retries and exception on failure
     *
     * @param  resource          $ch curl handler
     * @throws \RuntimeException
     */
    public static function execute($ch, $retries = 5, $closeAfterDone = true)
    {
        while ($retries--) {
            if (curl_exec($ch) === false) {
                $curlErrno = curl_errno($ch);

                if (false === in_array($curlErrno, self::$retriableErrorCodes, true) || !$retries) {
                    $curlError = curl_error($ch);

                    if ($closeAfterDone) {
                        curl_close($ch);
                    }

                    throw new \RuntimeException(sprintf('Curl error (code %s): %s', $curlErrno, $curlError));
                }

                continue;
            }

            if ($closeAfterDone) {
                curl_close($ch);
            }
            break;
        }
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;
use Monolog\Formatter\NormalizerFormatter;
use Doctrine\CouchDB\CouchDBClient;

/**
 * CouchDB handler for Doctrine CouchDB ODM
 *
 * @author Markus Bachmann <markus.bachmann@bachi.biz>
 */
class DoctrineCouchDBHandler extends AbstractProcessingHandler
{
    private $client;

    public function __construct(CouchDBClient $client, $level = Logger::DEBUG, $bubble = true)
    {
        $this->client = $client;
        parent::__construct($level, $bubble);
    }

    /**
     * {@inheritDoc}
     */
    protected function write(array $record)
    {
        $this->client->postDocument($record['formatted']);
    }

    protected function getDefaultFormatter()
    {
        return new NormalizerFormatter;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Aws\Sdk;
use Aws\DynamoDb\DynamoDbClient;
use Aws\DynamoDb\Marshaler;
use Monolog\Formatter\ScalarFormatter;
use Monolog\Logger;

/**
 * Amazon DynamoDB handler (http://aws.amazon.com/dynamodb/)
 *
 * @link https://github.com/aws/aws-sdk-php/
 * @author Andrew Lawson <adlawson@gmail.com>
 */
class DynamoDbHandler extends AbstractProcessingHandler
{
    const DATE_FORMAT = 'Y-m-d\TH:i:s.uO';

    /**
     * @var DynamoDbClient
     */
    protected $client;

    /**
     * @var string
     */
    protected $table;

    /**
     * @var int
     */
    protected $version;

    /**
     * @var Marshaler
     */
    protected $marshaler;

    /**
     * @param DynamoDbClient $client
     * @param string         $table
     * @param int            $level
     * @param bool           $bubble
     */
    public function __construct(DynamoDbClient $client, $table, $level = Logger::DEBUG, $bubble = true)
    {
        if (defined('Aws\Sdk::VERSION') && version_compare(Sdk::VERSION, '3.0', '>=')) {
            $this->version = 3;
            $this->marshaler = new Marshaler;
        } else {
            $this->version = 2;
        }

        $this->client = $client;
        $this->table = $table;

        parent::__construct($level, $bubble);
    }

    /**
     * {@inheritdoc}
     */
    protected function write(array $record)
    {
        $filtered = $this->filterEmptyFields($record['formatted']);
        if ($this->version === 3) {
            $formatted = $this->marshaler->marshalItem($filtered);
        } else {
            $formatted = $this->client->formatAttributes($filtered);
        }

        $this->client->putItem(array(
            'TableName' => $this->table,
            'Item' => $formatted,
        ));
    }

    /**
     * @param  array $record
     * @return array
     */
    protected function filterEmptyFields(array $record)
    {
        return array_filter($record, function ($value) {
            return !empty($value) || false === $value || 0 === $value;
        });
    }

    /**
     * {@inheritdoc}
     */
    protected function getDefaultFormatter()
    {
        return new ScalarFormatter(self::DATE_FORMAT);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

/**
 * Base class for all mail handlers
 *
 * @author Gyula Sallai
 */
abstract class MailHandler extends AbstractProcessingHandler
{
    /**
     * {@inheritdoc}
     */
    public function handleBatch(array $records)
    {
        $messages = array();

        foreach ($records as $record) {
            if ($record['level'] < $this->level) {
                continue;
            }
            $messages[] = $this->processRecord($record);
        }

        if (!empty($messages)) {
            $this->send((string) $this->getFormatter()->formatBatch($messages), $messages);
        }
    }

    /**
     * Send a mail with the given content
     *
     * @param string $content formatted email body to be sent
     * @param array  $records the array of log records that formed this content
     */
    abstract protected function send($content, array $records);

    /**
     * {@inheritdoc}
     */
    protected function write(array $record)
    {
        $this->send((string) $record['formatted'], array($record));
    }

    protected function getHighestRecord(array $records)
    {
        $highestRecord = null;
        foreach ($records as $record) {
            if ($highestRecord === null || $highestRecord['level'] < $record['level']) {
                $highestRecord = $record;
            }
        }

        return $highestRecord;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;

/**
 * Stores to any stream resource
 *
 * Can be used to store into php://stderr, remote and local files, etc.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class StreamHandler extends AbstractProcessingHandler
{
    protected $stream;
    protected $url;
    private $errorMessage;
    protected $filePermission;
    protected $useLocking;
    private $dirCreated;

    /**
     * @param resource|string $stream
     * @param int             $level          The minimum logging level at which this handler will be triggered
     * @param bool            $bubble         Whether the messages that are handled can bubble up the stack or not
     * @param int|null        $filePermission Optional file permissions (default (0644) are only for owner read/write)
     * @param bool            $useLocking     Try to lock log file before doing any writes
     *
     * @throws \Exception                If a missing directory is not buildable
     * @throws \InvalidArgumentException If stream is not a resource or string
     */
    public function __construct($stream, $level = Logger::DEBUG, $bubble = true, $filePermission = null, $useLocking = false)
    {
        parent::__construct($level, $bubble);
        if (is_resource($stream)) {
            $this->stream = $stream;
        } elseif (is_string($stream)) {
            $this->url = $stream;
        } else {
            throw new \InvalidArgumentException('A stream must either be a resource or a string.');
        }

        $this->filePermission = $filePermission;
        $this->useLocking = $useLocking;
    }

    /**
     * {@inheritdoc}
     */
    public function close()
    {
        if ($this->url && is_resource($this->stream)) {
            fclose($this->stream);
        }
        $this->stream = null;
    }

    /**
     * Return the currently active stream if it is open
     *
     * @return resource|null
     */
    public function getStream()
    {
        return $this->stream;
    }

    /**
     * Return the stream URL if it was configured with a URL and not an active resource
     *
     * @return string|null
     */
    public function getUrl()
    {
        return $this->url;
    }

    /**
     * {@inheritdoc}
     */
    protected function write(array $record)
    {
        if (!is_resource($this->stream)) {
            if (null === $this->url || '' === $this->url) {
                throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().');
            }
            $this->createDir();
            $this->errorMessage = null;
            set_error_handler(array($this, 'customErrorHandler'));
            $this->stream = fopen($this->url, 'a');
            if ($this->filePermission !== null) {
                @chmod($this->url, $this->filePermission);
            }
            restore_error_handler();
            if (!is_resource($this->stream)) {
                $this->stream = null;
                throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened: '.$this->errorMessage, $this->url));
            }
        }

        if ($this->useLocking) {
            // ignoring errors here, there's not much we can do about them
            flock($this->stream, LOCK_EX);
        }

        $this->streamWrite($this->stream, $record);

        if ($this->useLocking) {
            flock($this->stream, LOCK_UN);
        }
    }

    /**
     * Write to stream
     * @param resource $stream
     * @param array $record
     */
    protected function streamWrite($stream, array $record)
    {
        fwrite($stream, (string) $record['formatted']);
    }

    private function customErrorHandler($code, $msg)
    {
        $this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg);
    }

    /**
     * @param string $stream
     *
     * @return null|string
     */
    private function getDirFromStream($stream)
    {
        $pos = strpos($stream, '://');
        if ($pos === false) {
            return dirname($stream);
        }

        if ('file://' === substr($stream, 0, 7)) {
            return dirname(substr($stream, 7));
        }

        return;
    }

    private function createDir()
    {
        // Do not try to create dir if it has already been tried.
        if ($this->dirCreated) {
            return;
        }

        $dir = $this->getDirFromStream($this->url);
        if (null !== $dir && !is_dir($dir)) {
            $this->errorMessage = null;
            set_error_handler(array($this, 'customErrorHandler'));
            $status = mkdir($dir, 0777, true);
            restore_error_handler();
            if (false === $status && !is_dir($dir)) {
                throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and its not buildable: '.$this->errorMessage, $dir));
            }
        }
        $this->dirCreated = true;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;
use Monolog\Formatter\LogglyFormatter;

/**
 * Sends errors to Loggly.
 *
 * @author Przemek Sobstel <przemek@sobstel.org>
 * @author Adam Pancutt <adam@pancutt.com>
 * @author Gregory Barchard <gregory@barchard.net>
 */
class LogglyHandler extends AbstractProcessingHandler
{
    const HOST = 'logs-01.loggly.com';
    const ENDPOINT_SINGLE = 'inputs';
    const ENDPOINT_BATCH = 'bulk';

    protected $token;

    protected $tag = array();

    public function __construct($token, $level = Logger::DEBUG, $bubble = true)
    {
        if (!extension_loaded('curl')) {
            throw new \LogicException('The curl extension is needed to use the LogglyHandler');
        }

        $this->token = $token;

        parent::__construct($level, $bubble);
    }

    public function setTag($tag)
    {
        $tag = !empty($tag) ? $tag : array();
        $this->tag = is_array($tag) ? $tag : array($tag);
    }

    public function addTag($tag)
    {
        if (!empty($tag)) {
            $tag = is_array($tag) ? $tag : array($tag);
            $this->tag = array_unique(array_merge($this->tag, $tag));
        }
    }

    protected function write(array $record)
    {
        $this->send($record["formatted"], self::ENDPOINT_SINGLE);
    }

    public function handleBatch(array $records)
    {
        $level = $this->level;

        $records = array_filter($records, function ($record) use ($level) {
            return ($record['level'] >= $level);
        });

        if ($records) {
            $this->send($this->getFormatter()->formatBatch($records), self::ENDPOINT_BATCH);
        }
    }

    protected function send($data, $endpoint)
    {
        $url = sprintf("https://%s/%s/%s/", self::HOST, $endpoint, $this->token);

        $headers = array('Content-Type: application/json');

        if (!empty($this->tag)) {
            $headers[] = 'X-LOGGLY-TAG: '.implode(',', $this->tag);
        }

        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        Curl\Util::execute($ch);
    }

    protected function getDefaultFormatter()
    {
        return new LogglyFormatter();
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\FormatterInterface;
use Monolog\Logger;
use Monolog\Handler\Slack\SlackRecord;

/**
 * Sends notifications through Slack Webhooks
 *
 * @author Haralan Dobrev <hkdobrev@gmail.com>
 * @see    https://api.slack.com/incoming-webhooks
 */
class SlackWebhookHandler extends AbstractProcessingHandler
{
    /**
     * Slack Webhook token
     * @var string
     */
    private $webhookUrl;

    /**
     * Instance of the SlackRecord util class preparing data for Slack API.
     * @var SlackRecord
     */
    private $slackRecord;

    /**
     * @param  string      $webhookUrl             Slack Webhook URL
     * @param  string|null $channel                Slack channel (encoded ID or name)
     * @param  string|null $username               Name of a bot
     * @param  bool        $useAttachment          Whether the message should be added to Slack as attachment (plain text otherwise)
     * @param  string|null $iconEmoji              The emoji name to use (or null)
     * @param  bool        $useShortAttachment     Whether the the context/extra messages added to Slack as attachments are in a short style
     * @param  bool        $includeContextAndExtra Whether the attachment should include context and extra data
     * @param  int         $level                  The minimum logging level at which this handler will be triggered
     * @param  bool        $bubble                 Whether the messages that are handled can bubble up the stack or not
     * @param  array       $excludeFields          Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2']
     */
    public function __construct($webhookUrl, $channel = null, $username = null, $useAttachment = true, $iconEmoji = null, $useShortAttachment = false, $includeContextAndExtra = false, $level = Logger::CRITICAL, $bubble = true, array $excludeFields = array())
    {
        parent::__construct($level, $bubble);

        $this->webhookUrl = $webhookUrl;

        $this->slackRecord = new SlackRecord(
            $channel,
            $username,
            $useAttachment,
            $iconEmoji,
            $useShortAttachment,
            $includeContextAndExtra,
            $excludeFields,
            $this->formatter
        );
    }

    public function getSlackRecord()
    {
        return $this->slackRecord;
    }

    public function getWebhookUrl()
    {
        return $this->webhookUrl;
    }

    /**
     * {@inheritdoc}
     *
     * @param array $record
     */
    protected function write(array $record)
    {
        $postData = $this->slackRecord->getSlackData($record);
        $postString = json_encode($postData);

        $ch = curl_init();
        $options = array(
            CURLOPT_URL => $this->webhookUrl,
            CURLOPT_POST => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => array('Content-type: application/json'),
            CURLOPT_POSTFIELDS => $postString
        );
        if (defined('CURLOPT_SAFE_UPLOAD')) {
            $options[CURLOPT_SAFE_UPLOAD] = true;
        }

        curl_setopt_array($ch, $options);

        Curl\Util::execute($ch);
    }

    public function setFormatter(FormatterInterface $formatter)
    {
        parent::setFormatter($formatter);
        $this->slackRecord->setFormatter($formatter);

        return $this;
    }

    public function getFormatter()
    {
        $formatter = parent::getFormatter();
        $this->slackRecord->setFormatter($formatter);

        return $formatter;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

/**
 * Forwards records to multiple handlers suppressing failures of each handler
 * and continuing through to give every handler a chance to succeed.
 *
 * @author Craig D'Amelio <craig@damelio.ca>
 */
class WhatFailureGroupHandler extends GroupHandler
{
    /**
     * {@inheritdoc}
     */
    public function handle(array $record)
    {
        if ($this->processors) {
            foreach ($this->processors as $processor) {
                $record = call_user_func($processor, $record);
            }
        }

        foreach ($this->handlers as $handler) {
            try {
                $handler->handle($record);
            } catch (\Exception $e) {
                // What failure?
            } catch (\Throwable $e) {
                // What failure?
            }
        }

        return false === $this->bubble;
    }

    /**
     * {@inheritdoc}
     */
    public function handleBatch(array $records)
    {
        if ($this->processors) {
            $processed = array();
            foreach ($records as $record) {
                foreach ($this->processors as $processor) {
                    $processed[] = call_user_func($processor, $record);
                }
            }
            $records = $processed;
        }

        foreach ($this->handlers as $handler) {
            try {
                $handler->handleBatch($records);
            } catch (\Exception $e) {
                // What failure?
            } catch (\Throwable $e) {
                // What failure?
            }
        }
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\ChromePHPFormatter;
use Monolog\Logger;

/**
 * Handler sending logs to the ChromePHP extension (http://www.chromephp.com/)
 *
 * This also works out of the box with Firefox 43+
 *
 * @author Christophe Coevoet <stof@notk.org>
 */
class ChromePHPHandler extends AbstractProcessingHandler
{
    /**
     * Version of the extension
     */
    const VERSION = '4.0';

    /**
     * Header name
     */
    const HEADER_NAME = 'X-ChromeLogger-Data';

    /**
     * Regular expression to detect supported browsers (matches any Chrome, or Firefox 43+)
     */
    const USER_AGENT_REGEX = '{\b(?:Chrome/\d+(?:\.\d+)*|HeadlessChrome|Firefox/(?:4[3-9]|[5-9]\d|\d{3,})(?:\.\d)*)\b}';

    protected static $initialized = false;

    /**
     * Tracks whether we sent too much data
     *
     * Chrome limits the headers to 256KB, so when we sent 240KB we stop sending
     *
     * @var bool
     */
    protected static $overflowed = false;

    protected static $json = array(
        'version' => self::VERSION,
        'columns' => array('label', 'log', 'backtrace', 'type'),
        'rows' => array(),
    );

    protected static $sendHeaders = true;

    /**
     * @param int  $level  The minimum logging level at which this handler will be triggered
     * @param bool $bubble Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct($level = Logger::DEBUG, $bubble = true)
    {
        parent::__construct($level, $bubble);
        if (!function_exists('json_encode')) {
            throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s ChromePHPHandler');
        }
    }

    /**
     * {@inheritdoc}
     */
    public function handleBatch(array $records)
    {
        $messages = array();

        foreach ($records as $record) {
            if ($record['level'] < $this->level) {
                continue;
            }
            $messages[] = $this->processRecord($record);
        }

        if (!empty($messages)) {
            $messages = $this->getFormatter()->formatBatch($messages);
            self::$json['rows'] = array_merge(self::$json['rows'], $messages);
            $this->send();
        }
    }

    /**
     * {@inheritDoc}
     */
    protected function getDefaultFormatter()
    {
        return new ChromePHPFormatter();
    }

    /**
     * Creates & sends header for a record
     *
     * @see sendHeader()
     * @see send()
     * @param array $record
     */
    protected function write(array $record)
    {
        self::$json['rows'][] = $record['formatted'];

        $this->send();
    }

    /**
     * Sends the log header
     *
     * @see sendHeader()
     */
    protected function send()
    {
        if (self::$overflowed || !self::$sendHeaders) {
            return;
        }

        if (!self::$initialized) {
            self::$initialized = true;

            self::$sendHeaders = $this->headersAccepted();
            if (!self::$sendHeaders) {
                return;
            }

            self::$json['request_uri'] = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
        }

        $json = @json_encode(self::$json);
        $data = base64_encode(utf8_encode($json));
        if (strlen($data) > 240 * 1024) {
            self::$overflowed = true;

            $record = array(
                'message' => 'Incomplete logs, chrome header size limit reached',
                'context' => array(),
                'level' => Logger::WARNING,
                'level_name' => Logger::getLevelName(Logger::WARNING),
                'channel' => 'monolog',
                'datetime' => new \DateTime(),
                'extra' => array(),
            );
            self::$json['rows'][count(self::$json['rows']) - 1] = $this->getFormatter()->format($record);
            $json = @json_encode(self::$json);
            $data = base64_encode(utf8_encode($json));
        }

        if (trim($data) !== '') {
            $this->sendHeader(self::HEADER_NAME, $data);
        }
    }

    /**
     * Send header string to the client
     *
     * @param string $header
     * @param string $content
     */
    protected function sendHeader($header, $content)
    {
        if (!headers_sent() && self::$sendHeaders) {
            header(sprintf('%s: %s', $header, $content));
        }
    }

    /**
     * Verifies if the headers are accepted by the current user agent
     *
     * @return bool
     */
    protected function headersAccepted()
    {
        if (empty($_SERVER['HTTP_USER_AGENT'])) {
            return false;
        }

        return preg_match(self::USER_AGENT_REGEX, $_SERVER['HTTP_USER_AGENT']);
    }

    /**
     * BC getter for the sendHeaders property that has been made static
     */
    public function __get($property)
    {
        if ('sendHeaders' !== $property) {
            throw new \InvalidArgumentException('Undefined property '.$property);
        }

        return static::$sendHeaders;
    }

    /**
     * BC setter for the sendHeaders property that has been made static
     */
    public function __set($property, $value)
    {
        if ('sendHeaders' !== $property) {
            throw new \InvalidArgumentException('Undefined property '.$property);
        }

        static::$sendHeaders = $value;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler\Slack;

use Monolog\Logger;
use Monolog\Formatter\NormalizerFormatter;
use Monolog\Formatter\FormatterInterface;

/**
 * Slack record utility helping to log to Slack webhooks or API.
 *
 * @author Greg Kedzierski <greg@gregkedzierski.com>
 * @author Haralan Dobrev <hkdobrev@gmail.com>
 * @see    https://api.slack.com/incoming-webhooks
 * @see    https://api.slack.com/docs/message-attachments
 */
class SlackRecord
{
    const COLOR_DANGER = 'danger';

    const COLOR_WARNING = 'warning';

    const COLOR_GOOD = 'good';

    const COLOR_DEFAULT = '#e3e4e6';

    /**
     * Slack channel (encoded ID or name)
     * @var string|null
     */
    private $channel;

    /**
     * Name of a bot
     * @var string|null
     */
    private $username;

    /**
     * User icon e.g. 'ghost', 'http://example.com/user.png'
     * @var string
     */
    private $userIcon;

    /**
     * Whether the message should be added to Slack as attachment (plain text otherwise)
     * @var bool
     */
    private $useAttachment;

    /**
     * Whether the the context/extra messages added to Slack as attachments are in a short style
     * @var bool
     */
    private $useShortAttachment;

    /**
     * Whether the attachment should include context and extra data
     * @var bool
     */
    private $includeContextAndExtra;

    /**
     * Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2']
     * @var array
     */
    private $excludeFields;

    /**
     * @var FormatterInterface
     */
    private $formatter;

    /**
     * @var NormalizerFormatter
     */
    private $normalizerFormatter;

    public function __construct($channel = null, $username = null, $useAttachment = true, $userIcon = null, $useShortAttachment = false, $includeContextAndExtra = false, array $excludeFields = array(), FormatterInterface $formatter = null)
    {
        $this->channel = $channel;
        $this->username = $username;
        $this->userIcon = trim($userIcon, ':');
        $this->useAttachment = $useAttachment;
        $this->useShortAttachment = $useShortAttachment;
        $this->includeContextAndExtra = $includeContextAndExtra;
        $this->excludeFields = $excludeFields;
        $this->formatter = $formatter;

        if ($this->includeContextAndExtra) {
            $this->normalizerFormatter = new NormalizerFormatter();
        }
    }

    public function getSlackData(array $record)
    {
        $dataArray = array();
        $record = $this->excludeFields($record);

        if ($this->username) {
            $dataArray['username'] = $this->username;
        }

        if ($this->channel) {
            $dataArray['channel'] = $this->channel;
        }

        if ($this->formatter && !$this->useAttachment) {
            $message = $this->formatter->format($record);
        } else {
            $message = $record['message'];
        }

        if ($this->useAttachment) {
            $attachment = array(
                'fallback'  => $message,
                'text'      => $message,
                'color'     => $this->getAttachmentColor($record['level']),
                'fields'    => array(),
                'mrkdwn_in' => array('fields'),
                'ts'        => $record['datetime']->getTimestamp()
            );

            if ($this->useShortAttachment) {
                $attachment['title'] = $record['level_name'];
            } else {
                $attachment['title'] = 'Message';
                $attachment['fields'][] = $this->generateAttachmentField('Level', $record['level_name']);
            }


            if ($this->includeContextAndExtra) {
                foreach (array('extra', 'context') as $key) {
                    if (empty($record[$key])) {
                        continue;
                    }

                    if ($this->useShortAttachment) {
                        $attachment['fields'][] = $this->generateAttachmentField(
                            $key,
                            $record[$key]
                        );
                    } else {
                        // Add all extra fields as individual fields in attachment
                        $attachment['fields'] = array_merge(
                            $attachment['fields'],
                            $this->generateAttachmentFields($record[$key])
                        );
                    }
                }
            }

            $dataArray['attachments'] = array($attachment);
        } else {
            $dataArray['text'] = $message;
        }

        if ($this->userIcon) {
            if (filter_var($this->userIcon, FILTER_VALIDATE_URL)) {
                $dataArray['icon_url'] = $this->userIcon;
            } else {
                $dataArray['icon_emoji'] = ":{$this->userIcon}:";
            }
        }

        return $dataArray;
    }

    /**
     * Returned a Slack message attachment color associated with
     * provided level.
     *
     * @param  int    $level
     * @return string
     */
    public function getAttachmentColor($level)
    {
        switch (true) {
            case $level >= Logger::ERROR:
                return self::COLOR_DANGER;
            case $level >= Logger::WARNING:
                return self::COLOR_WARNING;
            case $level >= Logger::INFO:
                return self::COLOR_GOOD;
            default:
                return self::COLOR_DEFAULT;
        }
    }

    /**
     * Stringifies an array of key/value pairs to be used in attachment fields
     *
     * @param array $fields
     *
     * @return string
     */
    public function stringify($fields)
    {
        $normalized = $this->normalizerFormatter->format($fields);
        $prettyPrintFlag = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 128;

        $hasSecondDimension = count(array_filter($normalized, 'is_array'));
        $hasNonNumericKeys = !count(array_filter(array_keys($normalized), 'is_numeric'));

        return $hasSecondDimension || $hasNonNumericKeys
            ? json_encode($normalized, $prettyPrintFlag)
            : json_encode($normalized);
    }

    /**
     * Sets the formatter
     *
     * @param FormatterInterface $formatter
     */
    public function setFormatter(FormatterInterface $formatter)
    {
        $this->formatter = $formatter;
    }

    /**
     * Generates attachment field
     *
     * @param string       $title
     * @param string|array $value
     *
     * @return array
     */
    private function generateAttachmentField($title, $value)
    {
        $value = is_array($value)
            ? sprintf('```%s```', $this->stringify($value))
            : $value;

        return array(
            'title' => ucfirst($title),
            'value' => $value,
            'short' => false
        );
    }

    /**
     * Generates a collection of attachment fields from array
     *
     * @param array $data
     *
     * @return array
     */
    private function generateAttachmentFields(array $data)
    {
        $fields = array();
        foreach ($this->normalizerFormatter->format($data) as $key => $value) {
            $fields[] = $this->generateAttachmentField($key, $value);
        }

        return $fields;
    }

    /**
     * Get a copy of record with fields excluded according to $this->excludeFields
     *
     * @param array $record
     *
     * @return array
     */
    private function excludeFields(array $record)
    {
        foreach ($this->excludeFields as $field) {
            $keys = explode('.', $field);
            $node = &$record;
            $lastKey = end($keys);
            foreach ($keys as $key) {
                if (!isset($node[$key])) {
                    break;
                }
                if ($lastKey === $key) {
                    unset($node[$key]);
                    break;
                }
                $node = &$node[$key];
            }
        }

        return $record;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;
use Psr\Log\LoggerInterface;

/**
 * Proxies log messages to an existing PSR-3 compliant logger.
 *
 * @author Michael Moussa <michael.moussa@gmail.com>
 */
class PsrHandler extends AbstractHandler
{
    /**
     * PSR-3 compliant logger
     *
     * @var LoggerInterface
     */
    protected $logger;

    /**
     * @param LoggerInterface $logger The underlying PSR-3 compliant logger to which messages will be proxied
     * @param int             $level  The minimum logging level at which this handler will be triggered
     * @param bool            $bubble Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct(LoggerInterface $logger, $level = Logger::DEBUG, $bubble = true)
    {
        parent::__construct($level, $bubble);

        $this->logger = $logger;
    }

    /**
     * {@inheritDoc}
     */
    public function handle(array $record)
    {
        if (!$this->isHandling($record)) {
            return false;
        }

        $this->logger->log(strtolower($record['level_name']), $record['message'], $record['context']);

        return false === $this->bubble;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;
use Monolog\Formatter\NormalizerFormatter;

/**
 * Class to record a log on a NewRelic application.
 * Enabling New Relic High Security mode may prevent capture of useful information.
 *
 * This handler requires a NormalizerFormatter to function and expects an array in $record['formatted']
 *
 * @see https://docs.newrelic.com/docs/agents/php-agent
 * @see https://docs.newrelic.com/docs/accounts-partnerships/accounts/security/high-security
 */
class NewRelicHandler extends AbstractProcessingHandler
{
    /**
     * Name of the New Relic application that will receive logs from this handler.
     *
     * @var string
     */
    protected $appName;

    /**
     * Name of the current transaction
     *
     * @var string
     */
    protected $transactionName;

    /**
     * Some context and extra data is passed into the handler as arrays of values. Do we send them as is
     * (useful if we are using the API), or explode them for display on the NewRelic RPM website?
     *
     * @var bool
     */
    protected $explodeArrays;

    /**
     * {@inheritDoc}
     *
     * @param string $appName
     * @param bool   $explodeArrays
     * @param string $transactionName
     */
    public function __construct(
        $level = Logger::ERROR,
        $bubble = true,
        $appName = null,
        $explodeArrays = false,
        $transactionName = null
    ) {
        parent::__construct($level, $bubble);

        $this->appName       = $appName;
        $this->explodeArrays = $explodeArrays;
        $this->transactionName = $transactionName;
    }

    /**
     * {@inheritDoc}
     */
    protected function write(array $record)
    {
        if (!$this->isNewRelicEnabled()) {
            throw new MissingExtensionException('The newrelic PHP extension is required to use the NewRelicHandler');
        }

        if ($appName = $this->getAppName($record['context'])) {
            $this->setNewRelicAppName($appName);
        }

        if ($transactionName = $this->getTransactionName($record['context'])) {
            $this->setNewRelicTransactionName($transactionName);
            unset($record['formatted']['context']['transaction_name']);
        }

        if (isset($record['context']['exception']) && ($record['context']['exception'] instanceof \Exception || (PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable))) {
            newrelic_notice_error($record['message'], $record['context']['exception']);
            unset($record['formatted']['context']['exception']);
        } else {
            newrelic_notice_error($record['message']);
        }

        if (isset($record['formatted']['context']) && is_array($record['formatted']['context'])) {
            foreach ($record['formatted']['context'] as $key => $parameter) {
                if (is_array($parameter) && $this->explodeArrays) {
                    foreach ($parameter as $paramKey => $paramValue) {
                        $this->setNewRelicParameter('context_' . $key . '_' . $paramKey, $paramValue);
                    }
                } else {
                    $this->setNewRelicParameter('context_' . $key, $parameter);
                }
            }
        }

        if (isset($record['formatted']['extra']) && is_array($record['formatted']['extra'])) {
            foreach ($record['formatted']['extra'] as $key => $parameter) {
                if (is_array($parameter) && $this->explodeArrays) {
                    foreach ($parameter as $paramKey => $paramValue) {
                        $this->setNewRelicParameter('extra_' . $key . '_' . $paramKey, $paramValue);
                    }
                } else {
                    $this->setNewRelicParameter('extra_' . $key, $parameter);
                }
            }
        }
    }

    /**
     * Checks whether the NewRelic extension is enabled in the system.
     *
     * @return bool
     */
    protected function isNewRelicEnabled()
    {
        return extension_loaded('newrelic');
    }

    /**
     * Returns the appname where this log should be sent. Each log can override the default appname, set in this
     * handler's constructor, by providing the appname in it's context.
     *
     * @param  array       $context
     * @return null|string
     */
    protected function getAppName(array $context)
    {
        if (isset($context['appname'])) {
            return $context['appname'];
        }

        return $this->appName;
    }

    /**
     * Returns the name of the current transaction. Each log can override the default transaction name, set in this
     * handler's constructor, by providing the transaction_name in it's context
     *
     * @param array $context
     *
     * @return null|string
     */
    protected function getTransactionName(array $context)
    {
        if (isset($context['transaction_name'])) {
            return $context['transaction_name'];
        }

        return $this->transactionName;
    }

    /**
     * Sets the NewRelic application that should receive this log.
     *
     * @param string $appName
     */
    protected function setNewRelicAppName($appName)
    {
        newrelic_set_appname($appName);
    }

    /**
     * Overwrites the name of the current transaction
     *
     * @param string $transactionName
     */
    protected function setNewRelicTransactionName($transactionName)
    {
        newrelic_name_transaction($transactionName);
    }

    /**
     * @param string $key
     * @param mixed  $value
     */
    protected function setNewRelicParameter($key, $value)
    {
        if (null === $value || is_scalar($value)) {
            newrelic_add_custom_parameter($key, $value);
        } else {
            newrelic_add_custom_parameter($key, @json_encode($value));
        }
    }

    /**
     * {@inheritDoc}
     */
    protected function getDefaultFormatter()
    {
        return new NormalizerFormatter();
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\FormatterInterface;

/**
 * Interface that all Monolog Handlers must implement
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface HandlerInterface
{
    /**
     * Checks whether the given record will be handled by this handler.
     *
     * This is mostly done for performance reasons, to avoid calling processors for nothing.
     *
     * Handlers should still check the record levels within handle(), returning false in isHandling()
     * is no guarantee that handle() will not be called, and isHandling() might not be called
     * for a given record.
     *
     * @param array $record Partial log record containing only a level key
     *
     * @return bool
     */
    public function isHandling(array $record);

    /**
     * Handles a record.
     *
     * All records may be passed to this method, and the handler should discard
     * those that it does not want to handle.
     *
     * The return value of this function controls the bubbling process of the handler stack.
     * Unless the bubbling is interrupted (by returning true), the Logger class will keep on
     * calling further handlers in the stack with a given log record.
     *
     * @param  array   $record The record to handle
     * @return bool true means that this handler handled the record, and that bubbling is not permitted.
     *                        false means the record was either not processed or that this handler allows bubbling.
     */
    public function handle(array $record);

    /**
     * Handles a set of records at once.
     *
     * @param array $records The records to handle (an array of record arrays)
     */
    public function handleBatch(array $records);

    /**
     * Adds a processor in the stack.
     *
     * @param  callable $callback
     * @return self
     */
    public function pushProcessor($callback);

    /**
     * Removes the processor on top of the stack and returns it.
     *
     * @return callable
     */
    public function popProcessor();

    /**
     * Sets the formatter.
     *
     * @param  FormatterInterface $formatter
     * @return self
     */
    public function setFormatter(FormatterInterface $formatter);

    /**
     * Gets the formatter.
     *
     * @return FormatterInterface
     */
    public function getFormatter();
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\FormatterInterface;
use Monolog\Logger;
use Monolog\Handler\Slack\SlackRecord;

/**
 * Sends notifications through Slack API
 *
 * @author Greg Kedzierski <greg@gregkedzierski.com>
 * @see    https://api.slack.com/
 */
class SlackHandler extends SocketHandler
{
    /**
     * Slack API token
     * @var string
     */
    private $token;

    /**
     * Instance of the SlackRecord util class preparing data for Slack API.
     * @var SlackRecord
     */
    private $slackRecord;

    /**
     * @param  string                    $token                  Slack API token
     * @param  string                    $channel                Slack channel (encoded ID or name)
     * @param  string|null               $username               Name of a bot
     * @param  bool                      $useAttachment          Whether the message should be added to Slack as attachment (plain text otherwise)
     * @param  string|null               $iconEmoji              The emoji name to use (or null)
     * @param  int                       $level                  The minimum logging level at which this handler will be triggered
     * @param  bool                      $bubble                 Whether the messages that are handled can bubble up the stack or not
     * @param  bool                      $useShortAttachment     Whether the the context/extra messages added to Slack as attachments are in a short style
     * @param  bool                      $includeContextAndExtra Whether the attachment should include context and extra data
     * @param  array                     $excludeFields          Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2']
     * @throws MissingExtensionException If no OpenSSL PHP extension configured
     */
    public function __construct($token, $channel, $username = null, $useAttachment = true, $iconEmoji = null, $level = Logger::CRITICAL, $bubble = true, $useShortAttachment = false, $includeContextAndExtra = false, array $excludeFields = array())
    {
        if (!extension_loaded('openssl')) {
            throw new MissingExtensionException('The OpenSSL PHP extension is required to use the SlackHandler');
        }

        parent::__construct('ssl://slack.com:443', $level, $bubble);

        $this->slackRecord = new SlackRecord(
            $channel,
            $username,
            $useAttachment,
            $iconEmoji,
            $useShortAttachment,
            $includeContextAndExtra,
            $excludeFields,
            $this->formatter
        );

        $this->token = $token;
    }

    public function getSlackRecord()
    {
        return $this->slackRecord;
    }

    public function getToken()
    {
        return $this->token;
    }

    /**
     * {@inheritdoc}
     *
     * @param  array  $record
     * @return string
     */
    protected function generateDataStream($record)
    {
        $content = $this->buildContent($record);

        return $this->buildHeader($content) . $content;
    }

    /**
     * Builds the body of API call
     *
     * @param  array  $record
     * @return string
     */
    private function buildContent($record)
    {
        $dataArray = $this->prepareContentData($record);

        return http_build_query($dataArray);
    }

    /**
     * Prepares content data
     *
     * @param  array $record
     * @return array
     */
    protected function prepareContentData($record)
    {
        $dataArray = $this->slackRecord->getSlackData($record);
        $dataArray['token'] = $this->token;

        if (!empty($dataArray['attachments'])) {
            $dataArray['attachments'] = json_encode($dataArray['attachments']);
        }

        return $dataArray;
    }

    /**
     * Builds the header of the API Call
     *
     * @param  string $content
     * @return string
     */
    private function buildHeader($content)
    {
        $header = "POST /api/chat.postMessage HTTP/1.1\r\n";
        $header .= "Host: slack.com\r\n";
        $header .= "Content-Type: application/x-www-form-urlencoded\r\n";
        $header .= "Content-Length: " . strlen($content) . "\r\n";
        $header .= "\r\n";

        return $header;
    }

    /**
     * {@inheritdoc}
     *
     * @param array $record
     */
    protected function write(array $record)
    {
        parent::write($record);
        $this->finalizeWrite();
    }

    /**
     * Finalizes the request by reading some bytes and then closing the socket
     *
     * If we do not read some but close the socket too early, slack sometimes
     * drops the request entirely.
     */
    protected function finalizeWrite()
    {
        $res = $this->getResource();
        if (is_resource($res)) {
            @fread($res, 2048);
        }
        $this->closeSocket();
    }

    /**
     * Returned a Slack message attachment color associated with
     * provided level.
     *
     * @param  int    $level
     * @return string
     * @deprecated Use underlying SlackRecord instead
     */
    protected function getAttachmentColor($level)
    {
        trigger_error(
            'SlackHandler::getAttachmentColor() is deprecated. Use underlying SlackRecord instead.',
            E_USER_DEPRECATED
        );

        return $this->slackRecord->getAttachmentColor($level);
    }

    /**
     * Stringifies an array of key/value pairs to be used in attachment fields
     *
     * @param  array  $fields
     * @return string
     * @deprecated Use underlying SlackRecord instead
     */
    protected function stringify($fields)
    {
        trigger_error(
            'SlackHandler::stringify() is deprecated. Use underlying SlackRecord instead.',
            E_USER_DEPRECATED
        );

        return $this->slackRecord->stringify($fields);
    }

    public function setFormatter(FormatterInterface $formatter)
    {
        parent::setFormatter($formatter);
        $this->slackRecord->setFormatter($formatter);

        return $this;
    }

    public function getFormatter()
    {
        $formatter = parent::getFormatter();
        $this->slackRecord->setFormatter($formatter);

        return $formatter;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;

/**
 * Sends notifications through the hipchat api to a hipchat room
 *
 * Notes:
 * API token - HipChat API token
 * Room      - HipChat Room Id or name, where messages are sent
 * Name      - Name used to send the message (from)
 * notify    - Should the message trigger a notification in the clients
 * version   - The API version to use (HipChatHandler::API_V1 | HipChatHandler::API_V2)
 *
 * @author Rafael Dohms <rafael@doh.ms>
 * @see    https://www.hipchat.com/docs/api
 */
class HipChatHandler extends SocketHandler
{
    /**
     * Use API version 1
     */
    const API_V1 = 'v1';

    /**
     * Use API version v2
     */
    const API_V2 = 'v2';

    /**
     * The maximum allowed length for the name used in the "from" field.
     */
    const MAXIMUM_NAME_LENGTH = 15;

    /**
     * The maximum allowed length for the message.
     */
    const MAXIMUM_MESSAGE_LENGTH = 9500;

    /**
     * @var string
     */
    private $token;

    /**
     * @var string
     */
    private $room;

    /**
     * @var string
     */
    private $name;

    /**
     * @var bool
     */
    private $notify;

    /**
     * @var string
     */
    private $format;

    /**
     * @var string
     */
    private $host;

    /**
     * @var string
     */
    private $version;

    /**
     * @param string $token   HipChat API Token
     * @param string $room    The room that should be alerted of the message (Id or Name)
     * @param string $name    Name used in the "from" field.
     * @param bool   $notify  Trigger a notification in clients or not
     * @param int    $level   The minimum logging level at which this handler will be triggered
     * @param bool   $bubble  Whether the messages that are handled can bubble up the stack or not
     * @param bool   $useSSL  Whether to connect via SSL.
     * @param string $format  The format of the messages (default to text, can be set to html if you have html in the messages)
     * @param string $host    The HipChat server hostname.
     * @param string $version The HipChat API version (default HipChatHandler::API_V1)
     */
    public function __construct($token, $room, $name = 'Monolog', $notify = false, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $format = 'text', $host = 'api.hipchat.com', $version = self::API_V1)
    {
        if ($version == self::API_V1 && !$this->validateStringLength($name, static::MAXIMUM_NAME_LENGTH)) {
            throw new \InvalidArgumentException('The supplied name is too long. HipChat\'s v1 API supports names up to 15 UTF-8 characters.');
        }

        $connectionString = $useSSL ? 'ssl://'.$host.':443' : $host.':80';
        parent::__construct($connectionString, $level, $bubble);

        $this->token = $token;
        $this->name = $name;
        $this->notify = $notify;
        $this->room = $room;
        $this->format = $format;
        $this->host = $host;
        $this->version = $version;
    }

    /**
     * {@inheritdoc}
     *
     * @param  array  $record
     * @return string
     */
    protected function generateDataStream($record)
    {
        $content = $this->buildContent($record);

        return $this->buildHeader($content) . $content;
    }

    /**
     * Builds the body of API call
     *
     * @param  array  $record
     * @return string
     */
    private function buildContent($record)
    {
        $dataArray = array(
            'notify' => $this->version == self::API_V1 ?
                ($this->notify ? 1 : 0) :
                ($this->notify ? 'true' : 'false'),
            'message' => $record['formatted'],
            'message_format' => $this->format,
            'color' => $this->getAlertColor($record['level']),
        );

        if (!$this->validateStringLength($dataArray['message'], static::MAXIMUM_MESSAGE_LENGTH)) {
            if (function_exists('mb_substr')) {
                $dataArray['message'] = mb_substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]';
            } else {
                $dataArray['message'] = substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]';
            }
        }

        // if we are using the legacy API then we need to send some additional information
        if ($this->version == self::API_V1) {
            $dataArray['room_id'] = $this->room;
        }

        // append the sender name if it is set
        // always append it if we use the v1 api (it is required in v1)
        if ($this->version == self::API_V1 || $this->name !== null) {
            $dataArray['from'] = (string) $this->name;
        }

        return http_build_query($dataArray);
    }

    /**
     * Builds the header of the API Call
     *
     * @param  string $content
     * @return string
     */
    private function buildHeader($content)
    {
        if ($this->version == self::API_V1) {
            $header = "POST /v1/rooms/message?format=json&auth_token={$this->token} HTTP/1.1\r\n";
        } else {
            // needed for rooms with special (spaces, etc) characters in the name
            $room = rawurlencode($this->room);
            $header = "POST /v2/room/{$room}/notification?auth_token={$this->token} HTTP/1.1\r\n";
        }

        $header .= "Host: {$this->host}\r\n";
        $header .= "Content-Type: application/x-www-form-urlencoded\r\n";
        $header .= "Content-Length: " . strlen($content) . "\r\n";
        $header .= "\r\n";

        return $header;
    }

    /**
     * Assigns a color to each level of log records.
     *
     * @param  int    $level
     * @return string
     */
    protected function getAlertColor($level)
    {
        switch (true) {
            case $level >= Logger::ERROR:
                return 'red';
            case $level >= Logger::WARNING:
                return 'yellow';
            case $level >= Logger::INFO:
                return 'green';
            case $level == Logger::DEBUG:
                return 'gray';
            default:
                return 'yellow';
        }
    }

    /**
     * {@inheritdoc}
     *
     * @param array $record
     */
    protected function write(array $record)
    {
        parent::write($record);
        $this->finalizeWrite();
    }

    /**
     * Finalizes the request by reading some bytes and then closing the socket
     *
     * If we do not read some but close the socket too early, hipchat sometimes
     * drops the request entirely.
     */
    protected function finalizeWrite()
    {
        $res = $this->getResource();
        if (is_resource($res)) {
            @fread($res, 2048);
        }
        $this->closeSocket();
    }

    /**
     * {@inheritdoc}
     */
    public function handleBatch(array $records)
    {
        if (count($records) == 0) {
            return true;
        }

        $batchRecords = $this->combineRecords($records);

        $handled = false;
        foreach ($batchRecords as $batchRecord) {
            if ($this->isHandling($batchRecord)) {
                $this->write($batchRecord);
                $handled = true;
            }
        }

        if (!$handled) {
            return false;
        }

        return false === $this->bubble;
    }

    /**
     * Combines multiple records into one. Error level of the combined record
     * will be the highest level from the given records. Datetime will be taken
     * from the first record.
     *
     * @param $records
     * @return array
     */
    private function combineRecords($records)
    {
        $batchRecord = null;
        $batchRecords = array();
        $messages = array();
        $formattedMessages = array();
        $level = 0;
        $levelName = null;
        $datetime = null;

        foreach ($records as $record) {
            $record = $this->processRecord($record);

            if ($record['level'] > $level) {
                $level = $record['level'];
                $levelName = $record['level_name'];
            }

            if (null === $datetime) {
                $datetime = $record['datetime'];
            }

            $messages[] = $record['message'];
            $messageStr = implode(PHP_EOL, $messages);
            $formattedMessages[] = $this->getFormatter()->format($record);
            $formattedMessageStr = implode('', $formattedMessages);

            $batchRecord = array(
                'message'   => $messageStr,
                'formatted' => $formattedMessageStr,
                'context'   => array(),
                'extra'     => array(),
            );

            if (!$this->validateStringLength($batchRecord['formatted'], static::MAXIMUM_MESSAGE_LENGTH)) {
                // Pop the last message and implode the remaining messages
                $lastMessage = array_pop($messages);
                $lastFormattedMessage = array_pop($formattedMessages);
                $batchRecord['message'] = implode(PHP_EOL, $messages);
                $batchRecord['formatted'] = implode('', $formattedMessages);

                $batchRecords[] = $batchRecord;
                $messages = array($lastMessage);
                $formattedMessages = array($lastFormattedMessage);

                $batchRecord = null;
            }
        }

        if (null !== $batchRecord) {
            $batchRecords[] = $batchRecord;
        }

        // Set the max level and datetime for all records
        foreach ($batchRecords as &$batchRecord) {
            $batchRecord = array_merge(
                $batchRecord,
                array(
                    'level'      => $level,
                    'level_name' => $levelName,
                    'datetime'   => $datetime,
                )
            );
        }

        return $batchRecords;
    }

    /**
     * Validates the length of a string.
     *
     * If the `mb_strlen()` function is available, it will use that, as HipChat
     * allows UTF-8 characters. Otherwise, it will fall back to `strlen()`.
     *
     * Note that this might cause false failures in the specific case of using
     * a valid name with less than 16 characters, but 16 or more bytes, on a
     * system where `mb_strlen()` is unavailable.
     *
     * @param string $str
     * @param int    $length
     *
     * @return bool
     */
    private function validateStringLength($str, $length)
    {
        if (function_exists('mb_strlen')) {
            return (mb_strlen($str) <= $length);
        }

        return (strlen($str) <= $length);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\LineFormatter;
use Monolog\Logger;

/**
 * Sends logs to Fleep.io using Webhook integrations
 *
 * You'll need a Fleep.io account to use this handler.
 *
 * @see https://fleep.io/integrations/webhooks/ Fleep Webhooks Documentation
 * @author Ando Roots <ando@sqroot.eu>
 */
class FleepHookHandler extends SocketHandler
{
    const FLEEP_HOST = 'fleep.io';

    const FLEEP_HOOK_URI = '/hook/';

    /**
     * @var string Webhook token (specifies the conversation where logs are sent)
     */
    protected $token;

    /**
     * Construct a new Fleep.io Handler.
     *
     * For instructions on how to create a new web hook in your conversations
     * see https://fleep.io/integrations/webhooks/
     *
     * @param  string                    $token  Webhook token
     * @param  bool|int                  $level  The minimum logging level at which this handler will be triggered
     * @param  bool                      $bubble Whether the messages that are handled can bubble up the stack or not
     * @throws MissingExtensionException
     */
    public function __construct($token, $level = Logger::DEBUG, $bubble = true)
    {
        if (!extension_loaded('openssl')) {
            throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FleepHookHandler');
        }

        $this->token = $token;

        $connectionString = 'ssl://' . self::FLEEP_HOST . ':443';
        parent::__construct($connectionString, $level, $bubble);
    }

    /**
     * Returns the default formatter to use with this handler
     *
     * Overloaded to remove empty context and extra arrays from the end of the log message.
     *
     * @return LineFormatter
     */
    protected function getDefaultFormatter()
    {
        return new LineFormatter(null, null, true, true);
    }

    /**
     * Handles a log record
     *
     * @param array $record
     */
    public function write(array $record)
    {
        parent::write($record);
        $this->closeSocket();
    }

    /**
     * {@inheritdoc}
     *
     * @param  array  $record
     * @return string
     */
    protected function generateDataStream($record)
    {
        $content = $this->buildContent($record);

        return $this->buildHeader($content) . $content;
    }

    /**
     * Builds the header of the API Call
     *
     * @param  string $content
     * @return string
     */
    private function buildHeader($content)
    {
        $header = "POST " . self::FLEEP_HOOK_URI . $this->token . " HTTP/1.1\r\n";
        $header .= "Host: " . self::FLEEP_HOST . "\r\n";
        $header .= "Content-Type: application/x-www-form-urlencoded\r\n";
        $header .= "Content-Length: " . strlen($content) . "\r\n";
        $header .= "\r\n";

        return $header;
    }

    /**
     * Builds the body of API call
     *
     * @param  array  $record
     * @return string
     */
    private function buildContent($record)
    {
        $dataArray = array(
            'message' => $record['formatted'],
        );

        return http_build_query($dataArray);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;

/**
 * Blackhole
 *
 * Any record it can handle will be thrown away. This can be used
 * to put on top of an existing stack to override it temporarily.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class NullHandler extends AbstractHandler
{
    /**
     * @param int $level The minimum logging level at which this handler will be triggered
     */
    public function __construct($level = Logger::DEBUG)
    {
        parent::__construct($level, false);
    }

    /**
     * {@inheritdoc}
     */
    public function handle(array $record)
    {
        if ($record['level'] < $this->level) {
            return false;
        }

        return true;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;
use Monolog\Formatter\JsonFormatter;
use PhpAmqpLib\Message\AMQPMessage;
use PhpAmqpLib\Channel\AMQPChannel;
use AMQPExchange;

class AmqpHandler extends AbstractProcessingHandler
{
    /**
     * @var AMQPExchange|AMQPChannel $exchange
     */
    protected $exchange;

    /**
     * @var string
     */
    protected $exchangeName;

    /**
     * @param AMQPExchange|AMQPChannel $exchange     AMQPExchange (php AMQP ext) or PHP AMQP lib channel, ready for use
     * @param string                   $exchangeName
     * @param int                      $level
     * @param bool                     $bubble       Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct($exchange, $exchangeName = 'log', $level = Logger::DEBUG, $bubble = true)
    {
        if ($exchange instanceof AMQPExchange) {
            $exchange->setName($exchangeName);
        } elseif ($exchange instanceof AMQPChannel) {
            $this->exchangeName = $exchangeName;
        } else {
            throw new \InvalidArgumentException('PhpAmqpLib\Channel\AMQPChannel or AMQPExchange instance required');
        }
        $this->exchange = $exchange;

        parent::__construct($level, $bubble);
    }

    /**
     * {@inheritDoc}
     */
    protected function write(array $record)
    {
        $data = $record["formatted"];
        $routingKey = $this->getRoutingKey($record);

        if ($this->exchange instanceof AMQPExchange) {
            $this->exchange->publish(
                $data,
                $routingKey,
                0,
                array(
                    'delivery_mode' => 2,
                    'content_type' => 'application/json',
                )
            );
        } else {
            $this->exchange->basic_publish(
                $this->createAmqpMessage($data),
                $this->exchangeName,
                $routingKey
            );
        }
    }

    /**
     * {@inheritDoc}
     */
    public function handleBatch(array $records)
    {
        if ($this->exchange instanceof AMQPExchange) {
            parent::handleBatch($records);

            return;
        }

        foreach ($records as $record) {
            if (!$this->isHandling($record)) {
                continue;
            }

            $record = $this->processRecord($record);
            $data = $this->getFormatter()->format($record);

            $this->exchange->batch_basic_publish(
                $this->createAmqpMessage($data),
                $this->exchangeName,
                $this->getRoutingKey($record)
            );
        }

        $this->exchange->publish_batch();
    }

    /**
     * Gets the routing key for the AMQP exchange
     *
     * @param  array  $record
     * @return string
     */
    protected function getRoutingKey(array $record)
    {
        $routingKey = sprintf(
            '%s.%s',
            // TODO 2.0 remove substr call
            substr($record['level_name'], 0, 4),
            $record['channel']
        );

        return strtolower($routingKey);
    }

    /**
     * @param  string      $data
     * @return AMQPMessage
     */
    private function createAmqpMessage($data)
    {
        return new AMQPMessage(
            (string) $data,
            array(
                'delivery_mode' => 2,
                'content_type' => 'application/json',
            )
        );
    }

    /**
     * {@inheritDoc}
     */
    protected function getDefaultFormatter()
    {
        return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;

/**
 * MandrillHandler uses cURL to send the emails to the Mandrill API
 *
 * @author Adam Nicholson <adamnicholson10@gmail.com>
 */
class MandrillHandler extends MailHandler
{
    protected $message;
    protected $apiKey;

    /**
     * @param string                  $apiKey  A valid Mandrill API key
     * @param callable|\Swift_Message $message An example message for real messages, only the body will be replaced
     * @param int                     $level   The minimum logging level at which this handler will be triggered
     * @param bool                    $bubble  Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct($apiKey, $message, $level = Logger::ERROR, $bubble = true)
    {
        parent::__construct($level, $bubble);

        if (!$message instanceof \Swift_Message && is_callable($message)) {
            $message = call_user_func($message);
        }
        if (!$message instanceof \Swift_Message) {
            throw new \InvalidArgumentException('You must provide either a Swift_Message instance or a callable returning it');
        }
        $this->message = $message;
        $this->apiKey = $apiKey;
    }

    /**
     * {@inheritdoc}
     */
    protected function send($content, array $records)
    {
        $message = clone $this->message;
        $message->setBody($content);
        $message->setDate(time());

        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, 'https://mandrillapp.com/api/1.0/messages/send-raw.json');
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array(
            'key' => $this->apiKey,
            'raw_message' => (string) $message,
            'async' => false,
        )));

        Curl\Util::execute($ch);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\ResettableInterface;

/**
 * Base Handler class providing the Handler structure
 *
 * Classes extending it should (in most cases) only implement write($record)
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Christophe Coevoet <stof@notk.org>
 */
abstract class AbstractProcessingHandler extends AbstractHandler
{
    /**
     * {@inheritdoc}
     */
    public function handle(array $record)
    {
        if (!$this->isHandling($record)) {
            return false;
        }

        $record = $this->processRecord($record);

        $record['formatted'] = $this->getFormatter()->format($record);

        $this->write($record);

        return false === $this->bubble;
    }

    /**
     * Writes the record down to the log of the implementing handler
     *
     * @param  array $record
     * @return void
     */
    abstract protected function write(array $record);

    /**
     * Processes a record.
     *
     * @param  array $record
     * @return array
     */
    protected function processRecord(array $record)
    {
        if ($this->processors) {
            foreach ($this->processors as $processor) {
                $record = call_user_func($processor, $record);
            }
        }

        return $record;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\JsonFormatter;
use Monolog\Logger;

/**
 * CouchDB handler
 *
 * @author Markus Bachmann <markus.bachmann@bachi.biz>
 */
class CouchDBHandler extends AbstractProcessingHandler
{
    private $options;

    public function __construct(array $options = array(), $level = Logger::DEBUG, $bubble = true)
    {
        $this->options = array_merge(array(
            'host'     => 'localhost',
            'port'     => 5984,
            'dbname'   => 'logger',
            'username' => null,
            'password' => null,
        ), $options);

        parent::__construct($level, $bubble);
    }

    /**
     * {@inheritDoc}
     */
    protected function write(array $record)
    {
        $basicAuth = null;
        if ($this->options['username']) {
            $basicAuth = sprintf('%s:%s@', $this->options['username'], $this->options['password']);
        }

        $url = 'http://'.$basicAuth.$this->options['host'].':'.$this->options['port'].'/'.$this->options['dbname'];
        $context = stream_context_create(array(
            'http' => array(
                'method'        => 'POST',
                'content'       => $record['formatted'],
                'ignore_errors' => true,
                'max_redirects' => 0,
                'header'        => 'Content-type: application/json',
            ),
        ));

        if (false === @file_get_contents($url, null, $context)) {
            throw new \RuntimeException(sprintf('Could not connect to %s', $url));
        }
    }

    /**
     * {@inheritDoc}
     */
    protected function getDefaultFormatter()
    {
        return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\LineFormatter;
use Monolog\Logger;

/**
 * Logs to a Redis key using rpush
 *
 * usage example:
 *
 *   $log = new Logger('application');
 *   $redis = new RedisHandler(new Predis\Client("tcp://localhost:6379"), "logs", "prod");
 *   $log->pushHandler($redis);
 *
 * @author Thomas Tourlourat <thomas@tourlourat.com>
 */
class RedisHandler extends AbstractProcessingHandler
{
    private $redisClient;
    private $redisKey;
    protected $capSize;

    /**
     * @param \Predis\Client|\Redis $redis   The redis instance
     * @param string                $key     The key name to push records to
     * @param int                   $level   The minimum logging level at which this handler will be triggered
     * @param bool                  $bubble  Whether the messages that are handled can bubble up the stack or not
     * @param int                   $capSize Number of entries to limit list size to
     */
    public function __construct($redis, $key, $level = Logger::DEBUG, $bubble = true, $capSize = false)
    {
        if (!(($redis instanceof \Predis\Client) || ($redis instanceof \Redis))) {
            throw new \InvalidArgumentException('Predis\Client or Redis instance required');
        }

        $this->redisClient = $redis;
        $this->redisKey = $key;
        $this->capSize = $capSize;

        parent::__construct($level, $bubble);
    }

    /**
     * {@inheritDoc}
     */
    protected function write(array $record)
    {
        if ($this->capSize) {
            $this->writeCapped($record);
        } else {
            $this->redisClient->rpush($this->redisKey, $record["formatted"]);
        }
    }

    /**
     * Write and cap the collection
     * Writes the record to the redis list and caps its
     *
     * @param  array $record associative record array
     * @return void
     */
    protected function writeCapped(array $record)
    {
        if ($this->redisClient instanceof \Redis) {
            $this->redisClient->multi()
                ->rpush($this->redisKey, $record["formatted"])
                ->ltrim($this->redisKey, -$this->capSize, -1)
                ->exec();
        } else {
            $redisKey = $this->redisKey;
            $capSize = $this->capSize;
            $this->redisClient->transaction(function ($tx) use ($record, $redisKey, $capSize) {
                $tx->rpush($redisKey, $record["formatted"]);
                $tx->ltrim($redisKey, -$capSize, -1);
            });
        }
    }

    /**
     * {@inheritDoc}
     */
    protected function getDefaultFormatter()
    {
        return new LineFormatter();
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;

/**
 * Logs to Cube.
 *
 * @link http://square.github.com/cube/
 * @author Wan Chen <kami@kamisama.me>
 */
class CubeHandler extends AbstractProcessingHandler
{
    private $udpConnection;
    private $httpConnection;
    private $scheme;
    private $host;
    private $port;
    private $acceptedSchemes = array('http', 'udp');

    /**
     * Create a Cube handler
     *
     * @throws \UnexpectedValueException when given url is not a valid url.
     *                                   A valid url must consist of three parts : protocol://host:port
     *                                   Only valid protocols used by Cube are http and udp
     */
    public function __construct($url, $level = Logger::DEBUG, $bubble = true)
    {
        $urlInfo = parse_url($url);

        if (!isset($urlInfo['scheme'], $urlInfo['host'], $urlInfo['port'])) {
            throw new \UnexpectedValueException('URL "'.$url.'" is not valid');
        }

        if (!in_array($urlInfo['scheme'], $this->acceptedSchemes)) {
            throw new \UnexpectedValueException(
                'Invalid protocol (' . $urlInfo['scheme']  . ').'
                . ' Valid options are ' . implode(', ', $this->acceptedSchemes));
        }

        $this->scheme = $urlInfo['scheme'];
        $this->host = $urlInfo['host'];
        $this->port = $urlInfo['port'];

        parent::__construct($level, $bubble);
    }

    /**
     * Establish a connection to an UDP socket
     *
     * @throws \LogicException           when unable to connect to the socket
     * @throws MissingExtensionException when there is no socket extension
     */
    protected function connectUdp()
    {
        if (!extension_loaded('sockets')) {
            throw new MissingExtensionException('The sockets extension is required to use udp URLs with the CubeHandler');
        }

        $this->udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0);
        if (!$this->udpConnection) {
            throw new \LogicException('Unable to create a socket');
        }

        if (!socket_connect($this->udpConnection, $this->host, $this->port)) {
            throw new \LogicException('Unable to connect to the socket at ' . $this->host . ':' . $this->port);
        }
    }

    /**
     * Establish a connection to a http server
     * @throws \LogicException when no curl extension
     */
    protected function connectHttp()
    {
        if (!extension_loaded('curl')) {
            throw new \LogicException('The curl extension is needed to use http URLs with the CubeHandler');
        }

        $this->httpConnection = curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put');

        if (!$this->httpConnection) {
            throw new \LogicException('Unable to connect to ' . $this->host . ':' . $this->port);
        }

        curl_setopt($this->httpConnection, CURLOPT_CUSTOMREQUEST, "POST");
        curl_setopt($this->httpConnection, CURLOPT_RETURNTRANSFER, true);
    }

    /**
     * {@inheritdoc}
     */
    protected function write(array $record)
    {
        $date = $record['datetime'];

        $data = array('time' => $date->format('Y-m-d\TH:i:s.uO'));
        unset($record['datetime']);

        if (isset($record['context']['type'])) {
            $data['type'] = $record['context']['type'];
            unset($record['context']['type']);
        } else {
            $data['type'] = $record['channel'];
        }

        $data['data'] = $record['context'];
        $data['data']['level'] = $record['level'];

        if ($this->scheme === 'http') {
            $this->writeHttp(json_encode($data));
        } else {
            $this->writeUdp(json_encode($data));
        }
    }

    private function writeUdp($data)
    {
        if (!$this->udpConnection) {
            $this->connectUdp();
        }

        socket_send($this->udpConnection, $data, strlen($data), 0);
    }

    private function writeHttp($data)
    {
        if (!$this->httpConnection) {
            $this->connectHttp();
        }

        curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$data.']');
        curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, array(
            'Content-Type: application/json',
            'Content-Length: ' . strlen('['.$data.']'),
        ));

        Curl\Util::execute($this->httpConnection, 5, false);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler\SyslogUdp;

class UdpSocket
{
    const DATAGRAM_MAX_LENGTH = 65023;

    protected $ip;
    protected $port;
    protected $socket;

    public function __construct($ip, $port = 514)
    {
        $this->ip = $ip;
        $this->port = $port;
        $this->socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
    }

    public function write($line, $header = "")
    {
        $this->send($this->assembleMessage($line, $header));
    }

    public function close()
    {
        if (is_resource($this->socket)) {
            socket_close($this->socket);
            $this->socket = null;
        }
    }

    protected function send($chunk)
    {
        if (!is_resource($this->socket)) {
            throw new \LogicException('The UdpSocket to '.$this->ip.':'.$this->port.' has been closed and can not be written to anymore');
        }
        socket_sendto($this->socket, $chunk, strlen($chunk), $flags = 0, $this->ip, $this->port);
    }

    protected function assembleMessage($line, $header)
    {
        $chunkSize = self::DATAGRAM_MAX_LENGTH - strlen($header);

        return $header . substr($line, 0, $chunkSize);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog;

use Monolog\Handler\HandlerInterface;
use Monolog\Handler\StreamHandler;
use Psr\Log\LoggerInterface;
use Psr\Log\InvalidArgumentException;
use Exception;

/**
 * Monolog log channel
 *
 * It contains a stack of Handlers and a stack of Processors,
 * and uses them to store records that are added to it.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Logger implements LoggerInterface, ResettableInterface
{
    /**
     * Detailed debug information
     */
    const DEBUG = 100;

    /**
     * Interesting events
     *
     * Examples: User logs in, SQL logs.
     */
    const INFO = 200;

    /**
     * Uncommon events
     */
    const NOTICE = 250;

    /**
     * Exceptional occurrences that are not errors
     *
     * Examples: Use of deprecated APIs, poor use of an API,
     * undesirable things that are not necessarily wrong.
     */
    const WARNING = 300;

    /**
     * Runtime errors
     */
    const ERROR = 400;

    /**
     * Critical conditions
     *
     * Example: Application component unavailable, unexpected exception.
     */
    const CRITICAL = 500;

    /**
     * Action must be taken immediately
     *
     * Example: Entire website down, database unavailable, etc.
     * This should trigger the SMS alerts and wake you up.
     */
    const ALERT = 550;

    /**
     * Urgent alert.
     */
    const EMERGENCY = 600;

    /**
     * Monolog API version
     *
     * This is only bumped when API breaks are done and should
     * follow the major version of the library
     *
     * @var int
     */
    const API = 1;

    /**
     * Logging levels from syslog protocol defined in RFC 5424
     *
     * @var array $levels Logging levels
     */
    protected static $levels = array(
        self::DEBUG     => 'DEBUG',
        self::INFO      => 'INFO',
        self::NOTICE    => 'NOTICE',
        self::WARNING   => 'WARNING',
        self::ERROR     => 'ERROR',
        self::CRITICAL  => 'CRITICAL',
        self::ALERT     => 'ALERT',
        self::EMERGENCY => 'EMERGENCY',
    );

    /**
     * @var \DateTimeZone
     */
    protected static $timezone;

    /**
     * @var string
     */
    protected $name;

    /**
     * The handler stack
     *
     * @var HandlerInterface[]
     */
    protected $handlers;

    /**
     * Processors that will process all log records
     *
     * To process records of a single handler instead, add the processor on that specific handler
     *
     * @var callable[]
     */
    protected $processors;

    /**
     * @var bool
     */
    protected $microsecondTimestamps = true;

    /**
     * @var callable
     */
    protected $exceptionHandler;

    /**
     * @param string             $name       The logging channel
     * @param HandlerInterface[] $handlers   Optional stack of handlers, the first one in the array is called first, etc.
     * @param callable[]         $processors Optional array of processors
     */
    public function __construct($name, array $handlers = array(), array $processors = array())
    {
        $this->name = $name;
        $this->setHandlers($handlers);
        $this->processors = $processors;
    }

    /**
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Return a new cloned instance with the name changed
     *
     * @return static
     */
    public function withName($name)
    {
        $new = clone $this;
        $new->name = $name;

        return $new;
    }

    /**
     * Pushes a handler on to the stack.
     *
     * @param  HandlerInterface $handler
     * @return $this
     */
    public function pushHandler(HandlerInterface $handler)
    {
        array_unshift($this->handlers, $handler);

        return $this;
    }

    /**
     * Pops a handler from the stack
     *
     * @return HandlerInterface
     */
    public function popHandler()
    {
        if (!$this->handlers) {
            throw new \LogicException('You tried to pop from an empty handler stack.');
        }

        return array_shift($this->handlers);
    }

    /**
     * Set handlers, replacing all existing ones.
     *
     * If a map is passed, keys will be ignored.
     *
     * @param  HandlerInterface[] $handlers
     * @return $this
     */
    public function setHandlers(array $handlers)
    {
        $this->handlers = array();
        foreach (array_reverse($handlers) as $handler) {
            $this->pushHandler($handler);
        }

        return $this;
    }

    /**
     * @return HandlerInterface[]
     */
    public function getHandlers()
    {
        return $this->handlers;
    }

    /**
     * Adds a processor on to the stack.
     *
     * @param  callable $callback
     * @return $this
     */
    public function pushProcessor($callback)
    {
        if (!is_callable($callback)) {
            throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given');
        }
        array_unshift($this->processors, $callback);

        return $this;
    }

    /**
     * Removes the processor on top of the stack and returns it.
     *
     * @return callable
     */
    public function popProcessor()
    {
        if (!$this->processors) {
            throw new \LogicException('You tried to pop from an empty processor stack.');
        }

        return array_shift($this->processors);
    }

    /**
     * @return callable[]
     */
    public function getProcessors()
    {
        return $this->processors;
    }

    /**
     * Control the use of microsecond resolution timestamps in the 'datetime'
     * member of new records.
     *
     * Generating microsecond resolution timestamps by calling
     * microtime(true), formatting the result via sprintf() and then parsing
     * the resulting string via \DateTime::createFromFormat() can incur
     * a measurable runtime overhead vs simple usage of DateTime to capture
     * a second resolution timestamp in systems which generate a large number
     * of log events.
     *
     * @param bool $micro True to use microtime() to create timestamps
     */
    public function useMicrosecondTimestamps($micro)
    {
        $this->microsecondTimestamps = (bool) $micro;
    }

    /**
     * Adds a log record.
     *
     * @param  int     $level   The logging level
     * @param  string  $message The log message
     * @param  array   $context The log context
     * @return bool Whether the record has been processed
     */
    public function addRecord($level, $message, array $context = array())
    {
        if (!$this->handlers) {
            $this->pushHandler(new StreamHandler('php://stderr', static::DEBUG));
        }

        $levelName = static::getLevelName($level);

        // check if any handler will handle this message so we can return early and save cycles
        $handlerKey = null;
        reset($this->handlers);
        while ($handler = current($this->handlers)) {
            if ($handler->isHandling(array('level' => $level))) {
                $handlerKey = key($this->handlers);
                break;
            }

            next($this->handlers);
        }

        if (null === $handlerKey) {
            return false;
        }

        if (!static::$timezone) {
            static::$timezone = new \DateTimeZone(date_default_timezone_get() ?: 'UTC');
        }

        // php7.1+ always has microseconds enabled, so we do not need this hack
        if ($this->microsecondTimestamps && PHP_VERSION_ID < 70100) {
            $ts = \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true)), static::$timezone);
        } else {
            $ts = new \DateTime(null, static::$timezone);
        }
        $ts->setTimezone(static::$timezone);

        $record = array(
            'message' => (string) $message,
            'context' => $context,
            'level' => $level,
            'level_name' => $levelName,
            'channel' => $this->name,
            'datetime' => $ts,
            'extra' => array(),
        );

        try {
            foreach ($this->processors as $processor) {
                $record = call_user_func($processor, $record);
            }

            while ($handler = current($this->handlers)) {
                if (true === $handler->handle($record)) {
                    break;
                }

                next($this->handlers);
            }
        } catch (Exception $e) {
            $this->handleException($e, $record);
        }

        return true;
    }

    /**
     * Ends a log cycle and frees all resources used by handlers.
     *
     * Closing a Handler means flushing all buffers and freeing any open resources/handles.
     * Handlers that have been closed should be able to accept log records again and re-open
     * themselves on demand, but this may not always be possible depending on implementation.
     *
     * This is useful at the end of a request and will be called automatically on every handler
     * when they get destructed.
     */
    public function close()
    {
        foreach ($this->handlers as $handler) {
            if (method_exists($handler, 'close')) {
                $handler->close();
            }
        }
    }

    /**
     * Ends a log cycle and resets all handlers and processors to their initial state.
     *
     * Resetting a Handler or a Processor means flushing/cleaning all buffers, resetting internal
     * state, and getting it back to a state in which it can receive log records again.
     *
     * This is useful in case you want to avoid logs leaking between two requests or jobs when you
     * have a long running process like a worker or an application server serving multiple requests
     * in one process.
     */
    public function reset()
    {
        foreach ($this->handlers as $handler) {
            if ($handler instanceof ResettableInterface) {
                $handler->reset();
            }
        }

        foreach ($this->processors as $processor) {
            if ($processor instanceof ResettableInterface) {
                $processor->reset();
            }
        }
    }

    /**
     * Adds a log record at the DEBUG level.
     *
     * @param  string $message The log message
     * @param  array  $context The log context
     * @return bool   Whether the record has been processed
     */
    public function addDebug($message, array $context = array())
    {
        return $this->addRecord(static::DEBUG, $message, $context);
    }

    /**
     * Adds a log record at the INFO level.
     *
     * @param  string $message The log message
     * @param  array  $context The log context
     * @return bool   Whether the record has been processed
     */
    public function addInfo($message, array $context = array())
    {
        return $this->addRecord(static::INFO, $message, $context);
    }

    /**
     * Adds a log record at the NOTICE level.
     *
     * @param  string $message The log message
     * @param  array  $context The log context
     * @return bool   Whether the record has been processed
     */
    public function addNotice($message, array $context = array())
    {
        return $this->addRecord(static::NOTICE, $message, $context);
    }

    /**
     * Adds a log record at the WARNING level.
     *
     * @param  string $message The log message
     * @param  array  $context The log context
     * @return bool   Whether the record has been processed
     */
    public function addWarning($message, array $context = array())
    {
        return $this->addRecord(static::WARNING, $message, $context);
    }

    /**
     * Adds a log record at the ERROR level.
     *
     * @param  string $message The log message
     * @param  array  $context The log context
     * @return bool   Whether the record has been processed
     */
    public function addError($message, array $context = array())
    {
        return $this->addRecord(static::ERROR, $message, $context);
    }

    /**
     * Adds a log record at the CRITICAL level.
     *
     * @param  string $message The log message
     * @param  array  $context The log context
     * @return bool   Whether the record has been processed
     */
    public function addCritical($message, array $context = array())
    {
        return $this->addRecord(static::CRITICAL, $message, $context);
    }

    /**
     * Adds a log record at the ALERT level.
     *
     * @param  string $message The log message
     * @param  array  $context The log context
     * @return bool   Whether the record has been processed
     */
    public function addAlert($message, array $context = array())
    {
        return $this->addRecord(static::ALERT, $message, $context);
    }

    /**
     * Adds a log record at the EMERGENCY level.
     *
     * @param  string $message The log message
     * @param  array  $context The log context
     * @return bool   Whether the record has been processed
     */
    public function addEmergency($message, array $context = array())
    {
        return $this->addRecord(static::EMERGENCY, $message, $context);
    }

    /**
     * Gets all supported logging levels.
     *
     * @return array Assoc array with human-readable level names => level codes.
     */
    public static function getLevels()
    {
        return array_flip(static::$levels);
    }

    /**
     * Gets the name of the logging level.
     *
     * @param  int    $level
     * @return string
     */
    public static function getLevelName($level)
    {
        if (!isset(static::$levels[$level])) {
            throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels)));
        }

        return static::$levels[$level];
    }

    /**
     * Converts PSR-3 levels to Monolog ones if necessary
     *
     * @param string|int Level number (monolog) or name (PSR-3)
     * @return int
     */
    public static function toMonologLevel($level)
    {
        if (is_string($level) && defined(__CLASS__.'::'.strtoupper($level))) {
            return constant(__CLASS__.'::'.strtoupper($level));
        }

        return $level;
    }

    /**
     * Checks whether the Logger has a handler that listens on the given level
     *
     * @param  int     $level
     * @return bool
     */
    public function isHandling($level)
    {
        $record = array(
            'level' => $level,
        );

        foreach ($this->handlers as $handler) {
            if ($handler->isHandling($record)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Set a custom exception handler
     *
     * @param  callable $callback
     * @return $this
     */
    public function setExceptionHandler($callback)
    {
        if (!is_callable($callback)) {
            throw new \InvalidArgumentException('Exception handler must be valid callable (callback or object with an __invoke method), '.var_export($callback, true).' given');
        }
        $this->exceptionHandler = $callback;

        return $this;
    }

    /**
     * @return callable
     */
    public function getExceptionHandler()
    {
        return $this->exceptionHandler;
    }

    /**
     * Delegates exception management to the custom exception handler,
     * or throws the exception if no custom handler is set.
     */
    protected function handleException(Exception $e, array $record)
    {
        if (!$this->exceptionHandler) {
            throw $e;
        }

        call_user_func($this->exceptionHandler, $e, $record);
    }

    /**
     * Adds a log record at an arbitrary level.
     *
     * This method allows for compatibility with common interfaces.
     *
     * @param  mixed   $level   The log level
     * @param  string $message The log message
     * @param  array  $context The log context
     * @return bool   Whether the record has been processed
     */
    public function log($level, $message, array $context = array())
    {
        $level = static::toMonologLevel($level);

        return $this->addRecord($level, $message, $context);
    }

    /**
     * Adds a log record at the DEBUG level.
     *
     * This method allows for compatibility with common interfaces.
     *
     * @param  string $message The log message
     * @param  array  $context The log context
     * @return bool   Whether the record has been processed
     */
    public function debug($message, array $context = array())
    {
        return $this->addRecord(static::DEBUG, $message, $context);
    }

    /**
     * Adds a log record at the INFO level.
     *
     * This method allows for compatibility with common interfaces.
     *
     * @param  string $message The log message
     * @param  array  $context The log context
     * @return bool   Whether the record has been processed
     */
    public function info($message, array $context = array())
    {
        return $this->addRecord(static::INFO, $message, $context);
    }

    /**
     * Adds a log record at the NOTICE level.
     *
     * This method allows for compatibility with common interfaces.
     *
     * @param  string $message The log message
     * @param  array  $context The log context
     * @return bool   Whether the record has been processed
     */
    public function notice($message, array $context = array())
    {
        return $this->addRecord(static::NOTICE, $message, $context);
    }

    /**
     * Adds a log record at the WARNING level.
     *
     * This method allows for compatibility with common interfaces.
     *
     * @param  string $message The log message
     * @param  array  $context The log context
     * @return bool   Whether the record has been processed
     */
    public function warn($message, array $context = array())
    {
        return $this->addRecord(static::WARNING, $message, $context);
    }

    /**
     * Adds a log record at the WARNING level.
     *
     * This method allows for compatibility with common interfaces.
     *
     * @param  string $message The log message
     * @param  array  $context The log context
     * @return bool   Whether the record has been processed
     */
    public function warning($message, array $context = array())
    {
        return $this->addRecord(static::WARNING, $message, $context);
    }

    /**
     * Adds a log record at the ERROR level.
     *
     * This method allows for compatibility with common interfaces.
     *
     * @param  string $message The log message
     * @param  array  $context The log context
     * @return bool   Whether the record has been processed
     */
    public function err($message, array $context = array())
    {
        return $this->addRecord(static::ERROR, $message, $context);
    }

    /**
     * Adds a log record at the ERROR level.
     *
     * This method allows for compatibility with common interfaces.
     *
     * @param  string $message The log message
     * @param  array  $context The log context
     * @return bool   Whether the record has been processed
     */
    public function error($message, array $context = array())
    {
        return $this->addRecord(static::ERROR, $message, $context);
    }

    /**
     * Adds a log record at the CRITICAL level.
     *
     * This method allows for compatibility with common interfaces.
     *
     * @param  string $message The log message
     * @param  array  $context The log context
     * @return bool   Whether the record has been processed
     */
    public function crit($message, array $context = array())
    {
        return $this->addRecord(static::CRITICAL, $message, $context);
    }

    /**
     * Adds a log record at the CRITICAL level.
     *
     * This method allows for compatibility with common interfaces.
     *
     * @param  string $message The log message
     * @param  array  $context The log context
     * @return bool   Whether the record has been processed
     */
    public function critical($message, array $context = array())
    {
        return $this->addRecord(static::CRITICAL, $message, $context);
    }

    /**
     * Adds a log record at the ALERT level.
     *
     * This method allows for compatibility with common interfaces.
     *
     * @param  string $message The log message
     * @param  array  $context The log context
     * @return bool   Whether the record has been processed
     */
    public function alert($message, array $context = array())
    {
        return $this->addRecord(static::ALERT, $message, $context);
    }

    /**
     * Adds a log record at the EMERGENCY level.
     *
     * This method allows for compatibility with common interfaces.
     *
     * @param  string $message The log message
     * @param  array  $context The log context
     * @return bool   Whether the record has been processed
     */
    public function emerg($message, array $context = array())
    {
        return $this->addRecord(static::EMERGENCY, $message, $context);
    }

    /**
     * Adds a log record at the EMERGENCY level.
     *
     * This method allows for compatibility with common interfaces.
     *
     * @param  string $message The log message
     * @param  array  $context The log context
     * @return bool   Whether the record has been processed
     */
    public function emergency($message, array $context = array())
    {
        return $this->addRecord(static::EMERGENCY, $message, $context);
    }

    /**
     * Set the timezone to be used for the timestamp of log records.
     *
     * This is stored globally for all Logger instances
     *
     * @param \DateTimeZone $tz Timezone object
     */
    public static function setTimezone(\DateTimeZone $tz)
    {
        self::$timezone = $tz;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog;

use InvalidArgumentException;

/**
 * Monolog log registry
 *
 * Allows to get `Logger` instances in the global scope
 * via static method calls on this class.
 *
 * <code>
 * $application = new Monolog\Logger('application');
 * $api = new Monolog\Logger('api');
 *
 * Monolog\Registry::addLogger($application);
 * Monolog\Registry::addLogger($api);
 *
 * function testLogger()
 * {
 *     Monolog\Registry::api()->addError('Sent to $api Logger instance');
 *     Monolog\Registry::application()->addError('Sent to $application Logger instance');
 * }
 * </code>
 *
 * @author Tomas Tatarko <tomas@tatarko.sk>
 */
class Registry
{
    /**
     * List of all loggers in the registry (by named indexes)
     *
     * @var Logger[]
     */
    private static $loggers = array();

    /**
     * Adds new logging channel to the registry
     *
     * @param  Logger                    $logger    Instance of the logging channel
     * @param  string|null               $name      Name of the logging channel ($logger->getName() by default)
     * @param  bool                      $overwrite Overwrite instance in the registry if the given name already exists?
     * @throws \InvalidArgumentException If $overwrite set to false and named Logger instance already exists
     */
    public static function addLogger(Logger $logger, $name = null, $overwrite = false)
    {
        $name = $name ?: $logger->getName();

        if (isset(self::$loggers[$name]) && !$overwrite) {
            throw new InvalidArgumentException('Logger with the given name already exists');
        }

        self::$loggers[$name] = $logger;
    }

    /**
     * Checks if such logging channel exists by name or instance
     *
     * @param string|Logger $logger Name or logger instance
     */
    public static function hasLogger($logger)
    {
        if ($logger instanceof Logger) {
            $index = array_search($logger, self::$loggers, true);

            return false !== $index;
        } else {
            return isset(self::$loggers[$logger]);
        }
    }

    /**
     * Removes instance from registry by name or instance
     *
     * @param string|Logger $logger Name or logger instance
     */
    public static function removeLogger($logger)
    {
        if ($logger instanceof Logger) {
            if (false !== ($idx = array_search($logger, self::$loggers, true))) {
                unset(self::$loggers[$idx]);
            }
        } else {
            unset(self::$loggers[$logger]);
        }
    }

    /**
     * Clears the registry
     */
    public static function clear()
    {
        self::$loggers = array();
    }

    /**
     * Gets Logger instance from the registry
     *
     * @param  string                    $name Name of the requested Logger instance
     * @throws \InvalidArgumentException If named Logger instance is not in the registry
     * @return Logger                    Requested instance of Logger
     */
    public static function getInstance($name)
    {
        if (!isset(self::$loggers[$name])) {
            throw new InvalidArgumentException(sprintf('Requested "%s" logger instance is not in the registry', $name));
        }

        return self::$loggers[$name];
    }

    /**
     * Gets Logger instance from the registry via static method call
     *
     * @param  string                    $name      Name of the requested Logger instance
     * @param  array                     $arguments Arguments passed to static method call
     * @throws \InvalidArgumentException If named Logger instance is not in the registry
     * @return Logger                    Requested instance of Logger
     */
    public static function __callStatic($name, $arguments)
    {
        return self::getInstance($name);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Processor;

use Monolog\Utils;

/**
 * Processes a record's message according to PSR-3 rules
 *
 * It replaces {foo} with the value from $context['foo']
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PsrLogMessageProcessor implements ProcessorInterface
{
    /**
     * @param  array $record
     * @return array
     */
    public function __invoke(array $record)
    {
        if (false === strpos($record['message'], '{')) {
            return $record;
        }

        $replacements = array();
        foreach ($record['context'] as $key => $val) {
            if (is_null($val) || is_scalar($val) || (is_object($val) && method_exists($val, "__toString"))) {
                $replacements['{'.$key.'}'] = $val;
            } elseif (is_object($val)) {
                $replacements['{'.$key.'}'] = '[object '.Utils::getClass($val).']';
            } else {
                $replacements['{'.$key.'}'] = '['.gettype($val).']';
            }
        }

        $record['message'] = strtr($record['message'], $replacements);

        return $record;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Processor;

/**
 * Injects url/method and remote IP of the current web request in all records
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class WebProcessor implements ProcessorInterface
{
    /**
     * @var array|\ArrayAccess
     */
    protected $serverData;

    /**
     * Default fields
     *
     * Array is structured as [key in record.extra => key in $serverData]
     *
     * @var array
     */
    protected $extraFields = array(
        'url'         => 'REQUEST_URI',
        'ip'          => 'REMOTE_ADDR',
        'http_method' => 'REQUEST_METHOD',
        'server'      => 'SERVER_NAME',
        'referrer'    => 'HTTP_REFERER',
    );

    /**
     * @param array|\ArrayAccess $serverData  Array or object w/ ArrayAccess that provides access to the $_SERVER data
     * @param array|null         $extraFields Field names and the related key inside $serverData to be added. If not provided it defaults to: url, ip, http_method, server, referrer
     */
    public function __construct($serverData = null, array $extraFields = null)
    {
        if (null === $serverData) {
            $this->serverData = &$_SERVER;
        } elseif (is_array($serverData) || $serverData instanceof \ArrayAccess) {
            $this->serverData = $serverData;
        } else {
            throw new \UnexpectedValueException('$serverData must be an array or object implementing ArrayAccess.');
        }

        if (null !== $extraFields) {
            if (isset($extraFields[0])) {
                foreach (array_keys($this->extraFields) as $fieldName) {
                    if (!in_array($fieldName, $extraFields)) {
                        unset($this->extraFields[$fieldName]);
                    }
                }
            } else {
                $this->extraFields = $extraFields;
            }
        }
    }

    /**
     * @param  array $record
     * @return array
     */
    public function __invoke(array $record)
    {
        // skip processing if for some reason request data
        // is not present (CLI or wonky SAPIs)
        if (!isset($this->serverData['REQUEST_URI'])) {
            return $record;
        }

        $record['extra'] = $this->appendExtraFields($record['extra']);

        return $record;
    }

    /**
     * @param  string $extraName
     * @param  string $serverName
     * @return $this
     */
    public function addExtraField($extraName, $serverName)
    {
        $this->extraFields[$extraName] = $serverName;

        return $this;
    }

    /**
     * @param  array $extra
     * @return array
     */
    private function appendExtraFields(array $extra)
    {
        foreach ($this->extraFields as $extraName => $serverName) {
            $extra[$extraName] = isset($this->serverData[$serverName]) ? $this->serverData[$serverName] : null;
        }

        if (isset($this->serverData['UNIQUE_ID'])) {
            $extra['unique_id'] = $this->serverData['UNIQUE_ID'];
        }

        return $extra;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Processor;

/**
 * Injects memory_get_peak_usage in all records
 *
 * @see Monolog\Processor\MemoryProcessor::__construct() for options
 * @author Rob Jensen
 */
class MemoryPeakUsageProcessor extends MemoryProcessor
{
    /**
     * @param  array $record
     * @return array
     */
    public function __invoke(array $record)
    {
        $bytes = memory_get_peak_usage($this->realUsage);
        $formatted = $this->formatBytes($bytes);

        $record['extra']['memory_peak_usage'] = $formatted;

        return $record;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Processor;

/**
 * Adds value of getmypid into records
 *
 * @author Andreas HÃ¶rnicke
 */
class ProcessIdProcessor implements ProcessorInterface
{
    /**
     * @param  array $record
     * @return array
     */
    public function __invoke(array $record)
    {
        $record['extra']['process_id'] = getmypid();

        return $record;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jonathan A. Schweder <jonathanschweder@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Processor;

use Monolog\Logger;

/**
 * Injects Hg branch and Hg revision number in all records
 *
 * @author Jonathan A. Schweder <jonathanschweder@gmail.com>
 */
class MercurialProcessor implements ProcessorInterface
{
    private $level;
    private static $cache;

    public function __construct($level = Logger::DEBUG)
    {
        $this->level = Logger::toMonologLevel($level);
    }

    /**
     * @param  array $record
     * @return array
     */
    public function __invoke(array $record)
    {
        // return if the level is not high enough
        if ($record['level'] < $this->level) {
            return $record;
        }

        $record['extra']['hg'] = self::getMercurialInfo();

        return $record;
    }

    private static function getMercurialInfo()
    {
        if (self::$cache) {
            return self::$cache;
        }

        $result = explode(' ', trim(`hg id -nb`));
        if (count($result) >= 3) {
            return self::$cache = array(
                'branch' => $result[1],
                'revision' => $result[2],
            );
        }

        return self::$cache = array();
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Processor;

/**
 * Adds a tags array into record
 *
 * @author Martijn Riemers
 */
class TagProcessor implements ProcessorInterface
{
    private $tags;

    public function __construct(array $tags = array())
    {
        $this->setTags($tags);
    }

    public function addTags(array $tags = array())
    {
        $this->tags = array_merge($this->tags, $tags);
    }

    public function setTags(array $tags = array())
    {
        $this->tags = $tags;
    }

    public function __invoke(array $record)
    {
        $record['extra']['tags'] = $this->tags;

        return $record;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Processor;

use Monolog\Logger;

/**
 * Injects Git branch and Git commit SHA in all records
 *
 * @author Nick Otter
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class GitProcessor implements ProcessorInterface
{
    private $level;
    private static $cache;

    public function __construct($level = Logger::DEBUG)
    {
        $this->level = Logger::toMonologLevel($level);
    }

    /**
     * @param  array $record
     * @return array
     */
    public function __invoke(array $record)
    {
        // return if the level is not high enough
        if ($record['level'] < $this->level) {
            return $record;
        }

        $record['extra']['git'] = self::getGitInfo();

        return $record;
    }

    private static function getGitInfo()
    {
        if (self::$cache) {
            return self::$cache;
        }

        $branches = `git branch -v --no-abbrev`;
        if (preg_match('{^\* (.+?)\s+([a-f0-9]{40})(?:\s|$)}m', $branches, $matches)) {
            return self::$cache = array(
                'branch' => $matches[1],
                'commit' => $matches[2],
            );
        }

        return self::$cache = array();
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Processor;

/**
 * Some methods that are common for all memory processors
 *
 * @author Rob Jensen
 */
abstract class MemoryProcessor implements ProcessorInterface
{
    /**
     * @var bool If true, get the real size of memory allocated from system. Else, only the memory used by emalloc() is reported.
     */
    protected $realUsage;

    /**
     * @var bool If true, then format memory size to human readable string (MB, KB, B depending on size)
     */
    protected $useFormatting;

    /**
     * @param bool $realUsage     Set this to true to get the real size of memory allocated from system.
     * @param bool $useFormatting If true, then format memory size to human readable string (MB, KB, B depending on size)
     */
    public function __construct($realUsage = true, $useFormatting = true)
    {
        $this->realUsage = (bool) $realUsage;
        $this->useFormatting = (bool) $useFormatting;
    }

    /**
     * Formats bytes into a human readable string if $this->useFormatting is true, otherwise return $bytes as is
     *
     * @param  int        $bytes
     * @return string|int Formatted string if $this->useFormatting is true, otherwise return $bytes as is
     */
    protected function formatBytes($bytes)
    {
        $bytes = (int) $bytes;

        if (!$this->useFormatting) {
            return $bytes;
        }

        if ($bytes > 1024 * 1024) {
            return round($bytes / 1024 / 1024, 2).' MB';
        } elseif ($bytes > 1024) {
            return round($bytes / 1024, 2).' KB';
        }

        return $bytes . ' B';
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Processor;

use Monolog\Logger;

/**
 * Injects line/file:class/function where the log message came from
 *
 * Warning: This only works if the handler processes the logs directly.
 * If you put the processor on a handler that is behind a FingersCrossedHandler
 * for example, the processor will only be called once the trigger level is reached,
 * and all the log records will have the same file/line/.. data from the call that
 * triggered the FingersCrossedHandler.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class IntrospectionProcessor implements ProcessorInterface
{
    private $level;

    private $skipClassesPartials;

    private $skipStackFramesCount;

    private $skipFunctions = array(
        'call_user_func',
        'call_user_func_array',
    );

    public function __construct($level = Logger::DEBUG, array $skipClassesPartials = array(), $skipStackFramesCount = 0)
    {
        $this->level = Logger::toMonologLevel($level);
        $this->skipClassesPartials = array_merge(array('Monolog\\'), $skipClassesPartials);
        $this->skipStackFramesCount = $skipStackFramesCount;
    }

    /**
     * @param  array $record
     * @return array
     */
    public function __invoke(array $record)
    {
        // return if the level is not high enough
        if ($record['level'] < $this->level) {
            return $record;
        }

        /*
        * http://php.net/manual/en/function.debug-backtrace.php
        * As of 5.3.6, DEBUG_BACKTRACE_IGNORE_ARGS option was added.
        * Any version less than 5.3.6 must use the DEBUG_BACKTRACE_IGNORE_ARGS constant value '2'.
        */
        $trace = debug_backtrace((PHP_VERSION_ID < 50306) ? 2 : DEBUG_BACKTRACE_IGNORE_ARGS);

        // skip first since it's always the current method
        array_shift($trace);
        // the call_user_func call is also skipped
        array_shift($trace);

        $i = 0;

        while ($this->isTraceClassOrSkippedFunction($trace, $i)) {
            if (isset($trace[$i]['class'])) {
                foreach ($this->skipClassesPartials as $part) {
                    if (strpos($trace[$i]['class'], $part) !== false) {
                        $i++;
                        continue 2;
                    }
                }
            } elseif (in_array($trace[$i]['function'], $this->skipFunctions)) {
                $i++;
                continue;
            }

            break;
        }

        $i += $this->skipStackFramesCount;

        // we should have the call source now
        $record['extra'] = array_merge(
            $record['extra'],
            array(
                'file'      => isset($trace[$i - 1]['file']) ? $trace[$i - 1]['file'] : null,
                'line'      => isset($trace[$i - 1]['line']) ? $trace[$i - 1]['line'] : null,
                'class'     => isset($trace[$i]['class']) ? $trace[$i]['class'] : null,
                'function'  => isset($trace[$i]['function']) ? $trace[$i]['function'] : null,
            )
        );

        return $record;
    }

    private function isTraceClassOrSkippedFunction(array $trace, $index)
    {
        if (!isset($trace[$index])) {
            return false;
        }

        return isset($trace[$index]['class']) || in_array($trace[$index]['function'], $this->skipFunctions);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Processor;

use Monolog\ResettableInterface;

/**
 * Adds a unique identifier into records
 *
 * @author Simon MÃ¶nch <sm@webfactory.de>
 */
class UidProcessor implements ProcessorInterface, ResettableInterface
{
    private $uid;

    public function __construct($length = 7)
    {
        if (!is_int($length) || $length > 32 || $length < 1) {
            throw new \InvalidArgumentException('The uid length must be an integer between 1 and 32');
        }


        $this->uid = $this->generateUid($length);
    }

    public function __invoke(array $record)
    {
        $record['extra']['uid'] = $this->uid;

        return $record;
    }

    /**
     * @return string
     */
    public function getUid()
    {
        return $this->uid;
    }

    public function reset()
    {
        $this->uid = $this->generateUid(strlen($this->uid));
    }

    private function generateUid($length)
    {
        return substr(hash('md5', uniqid('', true)), 0, $length);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Processor;

/**
 * An optional interface to allow labelling Monolog processors.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
interface ProcessorInterface
{
    /**
     * @return array The processed records
     */
    public function __invoke(array $records);
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Processor;

/**
 * Injects memory_get_usage in all records
 *
 * @see Monolog\Processor\MemoryProcessor::__construct() for options
 * @author Rob Jensen
 */
class MemoryUsageProcessor extends MemoryProcessor
{
    /**
     * @param  array $record
     * @return array
     */
    public function __invoke(array $record)
    {
        $bytes = memory_get_usage($this->realUsage);
        $formatted = $this->formatBytes($bytes);

        $record['extra']['memory_usage'] = $formatted;

        return $record;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog;

/**
 * Handler or Processor implementing this interface will be reset when Logger::reset() is called.
 *
 * Resetting ends a log cycle gets them back to their initial state.
 *
 * Resetting a Handler or a Processor means flushing/cleaning all buffers, resetting internal
 * state, and getting it back to a state in which it can receive log records again.
 *
 * This is useful in case you want to avoid logs leaking between two requests or jobs when you
 * have a long running process like a worker or an application server serving multiple requests
 * in one process.
 *
 * @author GrÃ©goire Pineau <lyrixx@lyrixx.info>
 */
interface ResettableInterface
{
    public function reset();
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog;

use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use ReflectionExtension;

/**
 * Monolog POSIX signal handler
 *
 * @author Robert Gust-Bardon <robert@gust-bardon.org>
 */
class SignalHandler
{
    private $logger;

    private $previousSignalHandler = array();
    private $signalLevelMap = array();
    private $signalRestartSyscalls = array();

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

    public function registerSignalHandler($signo, $level = LogLevel::CRITICAL, $callPrevious = true, $restartSyscalls = true, $async = true)
    {
        if (!extension_loaded('pcntl') || !function_exists('pcntl_signal')) {
            return $this;
        }

        if ($callPrevious) {
            if (function_exists('pcntl_signal_get_handler')) {
                $handler = pcntl_signal_get_handler($signo);
                if ($handler === false) {
                    return $this;
                }
                $this->previousSignalHandler[$signo] = $handler;
            } else {
                $this->previousSignalHandler[$signo] = true;
            }
        } else {
            unset($this->previousSignalHandler[$signo]);
        }
        $this->signalLevelMap[$signo] = $level;
        $this->signalRestartSyscalls[$signo] = $restartSyscalls;

        if (function_exists('pcntl_async_signals') && $async !== null) {
            pcntl_async_signals($async);
        }

        pcntl_signal($signo, array($this, 'handleSignal'), $restartSyscalls);

        return $this;
    }

    public function handleSignal($signo, array $siginfo = null)
    {
        static $signals = array();

        if (!$signals && extension_loaded('pcntl')) {
            $pcntl = new ReflectionExtension('pcntl');
            $constants = $pcntl->getConstants();
            if (!$constants) {
                // HHVM 3.24.2 returns an empty array.
                $constants = get_defined_constants(true);
                $constants = $constants['Core'];
            }
            foreach ($constants as $name => $value) {
                if (substr($name, 0, 3) === 'SIG' && $name[3] !== '_' && is_int($value)) {
                    $signals[$value] = $name;
                }
            }
            unset($constants);
        }

        $level = isset($this->signalLevelMap[$signo]) ? $this->signalLevelMap[$signo] : LogLevel::CRITICAL;
        $signal = isset($signals[$signo]) ? $signals[$signo] : $signo;
        $context = isset($siginfo) ? $siginfo : array();
        $this->logger->log($level, sprintf('Program received signal %s', $signal), $context);

        if (!isset($this->previousSignalHandler[$signo])) {
            return;
        }

        if ($this->previousSignalHandler[$signo] === true || $this->previousSignalHandler[$signo] === SIG_DFL) {
            if (extension_loaded('pcntl') && function_exists('pcntl_signal') && function_exists('pcntl_sigprocmask') && function_exists('pcntl_signal_dispatch')
                && extension_loaded('posix') && function_exists('posix_getpid') && function_exists('posix_kill')) {
                    $restartSyscalls = isset($this->restartSyscalls[$signo]) ? $this->restartSyscalls[$signo] : true;
                    pcntl_signal($signo, SIG_DFL, $restartSyscalls);
                    pcntl_sigprocmask(SIG_UNBLOCK, array($signo), $oldset);
                    posix_kill(posix_getpid(), $signo);
                    pcntl_signal_dispatch();
                    pcntl_sigprocmask(SIG_SETMASK, $oldset);
                    pcntl_signal($signo, array($this, 'handleSignal'), $restartSyscalls);
                }
        } elseif (is_callable($this->previousSignalHandler[$signo])) {
            if (PHP_VERSION_ID >= 70100) {
                $this->previousSignalHandler[$signo]($signo, $siginfo);
            } else {
                $this->previousSignalHandler[$signo]($signo);
            }
        }
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

use Elastica\Document;

/**
 * Format a log message into an Elastica Document
 *
 * @author Jelle Vink <jelle.vink@gmail.com>
 */
class ElasticaFormatter extends NormalizerFormatter
{
    /**
     * @var string Elastic search index name
     */
    protected $index;

    /**
     * @var string Elastic search document type
     */
    protected $type;

    /**
     * @param string $index Elastic Search index name
     * @param string $type  Elastic Search document type
     */
    public function __construct($index, $type)
    {
        // elasticsearch requires a ISO 8601 format date with optional millisecond precision.
        parent::__construct('Y-m-d\TH:i:s.uP');

        $this->index = $index;
        $this->type = $type;
    }

    /**
     * {@inheritdoc}
     */
    public function format(array $record)
    {
        $record = parent::format($record);

        return $this->getDocument($record);
    }

    /**
     * Getter index
     * @return string
     */
    public function getIndex()
    {
        return $this->index;
    }

    /**
     * Getter type
     * @return string
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * Convert a log message into an Elastica Document
     *
     * @param  array    $record Log message
     * @return Document
     */
    protected function getDocument($record)
    {
        $document = new Document();
        $document->setData($record);
        $document->setType($this->type);
        $document->setIndex($this->index);

        return $document;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

use Monolog\Utils;

/**
 * Formats incoming records into a one-line string
 *
 * This is especially useful for logging to files
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Christophe Coevoet <stof@notk.org>
 */
class LineFormatter extends NormalizerFormatter
{
    const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n";

    protected $format;
    protected $allowInlineLineBreaks;
    protected $ignoreEmptyContextAndExtra;
    protected $includeStacktraces;

    /**
     * @param string $format                     The format of the message
     * @param string $dateFormat                 The format of the timestamp: one supported by DateTime::format
     * @param bool   $allowInlineLineBreaks      Whether to allow inline line breaks in log entries
     * @param bool   $ignoreEmptyContextAndExtra
     */
    public function __construct($format = null, $dateFormat = null, $allowInlineLineBreaks = false, $ignoreEmptyContextAndExtra = false)
    {
        $this->format = $format ?: static::SIMPLE_FORMAT;
        $this->allowInlineLineBreaks = $allowInlineLineBreaks;
        $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra;
        parent::__construct($dateFormat);
    }

    public function includeStacktraces($include = true)
    {
        $this->includeStacktraces = $include;
        if ($this->includeStacktraces) {
            $this->allowInlineLineBreaks = true;
        }
    }

    public function allowInlineLineBreaks($allow = true)
    {
        $this->allowInlineLineBreaks = $allow;
    }

    public function ignoreEmptyContextAndExtra($ignore = true)
    {
        $this->ignoreEmptyContextAndExtra = $ignore;
    }

    /**
     * {@inheritdoc}
     */
    public function format(array $record)
    {
        $vars = parent::format($record);

        $output = $this->format;

        foreach ($vars['extra'] as $var => $val) {
            if (false !== strpos($output, '%extra.'.$var.'%')) {
                $output = str_replace('%extra.'.$var.'%', $this->stringify($val), $output);
                unset($vars['extra'][$var]);
            }
        }


        foreach ($vars['context'] as $var => $val) {
            if (false !== strpos($output, '%context.'.$var.'%')) {
                $output = str_replace('%context.'.$var.'%', $this->stringify($val), $output);
                unset($vars['context'][$var]);
            }
        }

        if ($this->ignoreEmptyContextAndExtra) {
            if (empty($vars['context'])) {
                unset($vars['context']);
                $output = str_replace('%context%', '', $output);
            }

            if (empty($vars['extra'])) {
                unset($vars['extra']);
                $output = str_replace('%extra%', '', $output);
            }
        }

        foreach ($vars as $var => $val) {
            if (false !== strpos($output, '%'.$var.'%')) {
                $output = str_replace('%'.$var.'%', $this->stringify($val), $output);
            }
        }

        // remove leftover %extra.xxx% and %context.xxx% if any
        if (false !== strpos($output, '%')) {
            $output = preg_replace('/%(?:extra|context)\..+?%/', '', $output);
        }

        return $output;
    }

    public function formatBatch(array $records)
    {
        $message = '';
        foreach ($records as $record) {
            $message .= $this->format($record);
        }

        return $message;
    }

    public function stringify($value)
    {
        return $this->replaceNewlines($this->convertToString($value));
    }

    protected function normalizeException($e)
    {
        // TODO 2.0 only check for Throwable
        if (!$e instanceof \Exception && !$e instanceof \Throwable) {
            throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.Utils::getClass($e));
        }

        $previousText = '';
        if ($previous = $e->getPrevious()) {
            do {
                $previousText .= ', '.Utils::getClass($previous).'(code: '.$previous->getCode().'): '.$previous->getMessage().' at '.$previous->getFile().':'.$previous->getLine();
            } while ($previous = $previous->getPrevious());
        }

        $str = '[object] ('.Utils::getClass($e).'(code: '.$e->getCode().'): '.$e->getMessage().' at '.$e->getFile().':'.$e->getLine().$previousText.')';
        if ($this->includeStacktraces) {
            $str .= "\n[stacktrace]\n".$e->getTraceAsString()."\n";
        }

        return $str;
    }

    protected function convertToString($data)
    {
        if (null === $data || is_bool($data)) {
            return var_export($data, true);
        }

        if (is_scalar($data)) {
            return (string) $data;
        }

        if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
            return $this->toJson($data, true);
        }

        return str_replace('\\/', '/', @json_encode($data));
    }

    protected function replaceNewlines($str)
    {
        if ($this->allowInlineLineBreaks) {
            if (0 === strpos($str, '{')) {
                return str_replace(array('\r', '\n'), array("\r", "\n"), $str);
            }

            return $str;
        }

        return str_replace(array("\r\n", "\r", "\n"), ' ', $str);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

use Exception;
use Monolog\Utils;
use Throwable;

/**
 * Encodes whatever record data is passed to it as json
 *
 * This can be useful to log to databases or remote APIs
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class JsonFormatter extends NormalizerFormatter
{
    const BATCH_MODE_JSON = 1;
    const BATCH_MODE_NEWLINES = 2;

    protected $batchMode;
    protected $appendNewline;

    /**
     * @var bool
     */
    protected $includeStacktraces = false;

    /**
     * @param int $batchMode
     * @param bool $appendNewline
     */
    public function __construct($batchMode = self::BATCH_MODE_JSON, $appendNewline = true)
    {
        $this->batchMode = $batchMode;
        $this->appendNewline = $appendNewline;
    }

    /**
     * The batch mode option configures the formatting style for
     * multiple records. By default, multiple records will be
     * formatted as a JSON-encoded array. However, for
     * compatibility with some API endpoints, alternative styles
     * are available.
     *
     * @return int
     */
    public function getBatchMode()
    {
        return $this->batchMode;
    }

    /**
     * True if newlines are appended to every formatted record
     *
     * @return bool
     */
    public function isAppendingNewlines()
    {
        return $this->appendNewline;
    }

    /**
     * {@inheritdoc}
     */
    public function format(array $record)
    {
        return $this->toJson($this->normalize($record), true) . ($this->appendNewline ? "\n" : '');
    }

    /**
     * {@inheritdoc}
     */
    public function formatBatch(array $records)
    {
        switch ($this->batchMode) {
            case static::BATCH_MODE_NEWLINES:
                return $this->formatBatchNewlines($records);

            case static::BATCH_MODE_JSON:
            default:
                return $this->formatBatchJson($records);
        }
    }

    /**
     * @param bool $include
     */
    public function includeStacktraces($include = true)
    {
        $this->includeStacktraces = $include;
    }

    /**
     * Return a JSON-encoded array of records.
     *
     * @param  array  $records
     * @return string
     */
    protected function formatBatchJson(array $records)
    {
        return $this->toJson($this->normalize($records), true);
    }

    /**
     * Use new lines to separate records instead of a
     * JSON-encoded array.
     *
     * @param  array  $records
     * @return string
     */
    protected function formatBatchNewlines(array $records)
    {
        $instance = $this;

        $oldNewline = $this->appendNewline;
        $this->appendNewline = false;
        array_walk($records, function (&$value, $key) use ($instance) {
            $value = $instance->format($value);
        });
        $this->appendNewline = $oldNewline;

        return implode("\n", $records);
    }

    /**
     * Normalizes given $data.
     *
     * @param mixed $data
     *
     * @return mixed
     */
    protected function normalize($data, $depth = 0)
    {
        if ($depth > 9) {
            return 'Over 9 levels deep, aborting normalization';
        }

        if (is_array($data) || $data instanceof \Traversable) {
            $normalized = array();

            $count = 1;
            foreach ($data as $key => $value) {
                if ($count++ > 1000) {
                    $normalized['...'] = 'Over 1000 items ('.count($data).' total), aborting normalization';
                    break;
                }

                $normalized[$key] = $this->normalize($value, $depth+1);
            }

            return $normalized;
        }

        if ($data instanceof Exception || $data instanceof Throwable) {
            return $this->normalizeException($data);
        }

        return $data;
    }

    /**
     * Normalizes given exception with or without its own stack trace based on
     * `includeStacktraces` property.
     *
     * @param Exception|Throwable $e
     *
     * @return array
     */
    protected function normalizeException($e)
    {
        // TODO 2.0 only check for Throwable
        if (!$e instanceof Exception && !$e instanceof Throwable) {
            throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.Utils::getClass($e));
        }

        $data = array(
            'class' => Utils::getClass($e),
            'message' => $e->getMessage(),
            'code' => $e->getCode(),
            'file' => $e->getFile().':'.$e->getLine(),
        );

        if ($this->includeStacktraces) {
            $trace = $e->getTrace();
            foreach ($trace as $frame) {
                if (isset($frame['file'])) {
                    $data['trace'][] = $frame['file'].':'.$frame['line'];
                } elseif (isset($frame['function']) && $frame['function'] === '{closure}') {
                    // We should again normalize the frames, because it might contain invalid items
                    $data['trace'][] = $frame['function'];
                } else {
                    // We should again normalize the frames, because it might contain invalid items
                    $data['trace'][] = $this->normalize($frame);
                }
            }
        }

        if ($previous = $e->getPrevious()) {
            $data['previous'] = $this->normalizeException($previous);
        }

        return $data;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

/**
 * formats the record to be used in the FlowdockHandler
 *
 * @author Dominik Liebler <liebler.dominik@gmail.com>
 */
class FlowdockFormatter implements FormatterInterface
{
    /**
     * @var string
     */
    private $source;

    /**
     * @var string
     */
    private $sourceEmail;

    /**
     * @param string $source
     * @param string $sourceEmail
     */
    public function __construct($source, $sourceEmail)
    {
        $this->source = $source;
        $this->sourceEmail = $sourceEmail;
    }

    /**
     * {@inheritdoc}
     */
    public function format(array $record)
    {
        $tags = array(
            '#logs',
            '#' . strtolower($record['level_name']),
            '#' . $record['channel'],
        );

        foreach ($record['extra'] as $value) {
            $tags[] = '#' . $value;
        }

        $subject = sprintf(
            'in %s: %s - %s',
            $this->source,
            $record['level_name'],
            $this->getShortMessage($record['message'])
        );

        $record['flowdock'] = array(
            'source' => $this->source,
            'from_address' => $this->sourceEmail,
            'subject' => $subject,
            'content' => $record['message'],
            'tags' => $tags,
            'project' => $this->source,
        );

        return $record;
    }

    /**
     * {@inheritdoc}
     */
    public function formatBatch(array $records)
    {
        $formatted = array();

        foreach ($records as $record) {
            $formatted[] = $this->format($record);
        }

        return $formatted;
    }

    /**
     * @param string $message
     *
     * @return string
     */
    public function getShortMessage($message)
    {
        static $hasMbString;

        if (null === $hasMbString) {
            $hasMbString = function_exists('mb_strlen');
        }

        $maxLength = 45;

        if ($hasMbString) {
            if (mb_strlen($message, 'UTF-8') > $maxLength) {
                $message = mb_substr($message, 0, $maxLength - 4, 'UTF-8') . ' ...';
            }
        } else {
            if (strlen($message) > $maxLength) {
                $message = substr($message, 0, $maxLength - 4) . ' ...';
            }
        }

        return $message;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

/**
 * Interface for formatters
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface FormatterInterface
{
    /**
     * Formats a log record.
     *
     * @param  array $record A record to format
     * @return mixed The formatted record
     */
    public function format(array $record);

    /**
     * Formats a set of log records.
     *
     * @param  array $records A set of records to format
     * @return mixed The formatted set of records
     */
    public function formatBatch(array $records);
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

/**
 * Serializes a log message to Logstash Event Format
 *
 * @see http://logstash.net/
 * @see https://github.com/logstash/logstash/blob/master/lib/logstash/event.rb
 *
 * @author Tim Mower <timothy.mower@gmail.com>
 */
class LogstashFormatter extends NormalizerFormatter
{
    const V0 = 0;
    const V1 = 1;

    /**
     * @var string the name of the system for the Logstash log message, used to fill the @source field
     */
    protected $systemName;

    /**
     * @var string an application name for the Logstash log message, used to fill the @type field
     */
    protected $applicationName;

    /**
     * @var string a prefix for 'extra' fields from the Monolog record (optional)
     */
    protected $extraPrefix;

    /**
     * @var string a prefix for 'context' fields from the Monolog record (optional)
     */
    protected $contextPrefix;

    /**
     * @var int logstash format version to use
     */
    protected $version;

    /**
     * @param string $applicationName the application that sends the data, used as the "type" field of logstash
     * @param string $systemName      the system/machine name, used as the "source" field of logstash, defaults to the hostname of the machine
     * @param string $extraPrefix     prefix for extra keys inside logstash "fields"
     * @param string $contextPrefix   prefix for context keys inside logstash "fields", defaults to ctxt_
     * @param int    $version         the logstash format version to use, defaults to 0
     */
    public function __construct($applicationName, $systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_', $version = self::V0)
    {
        // logstash requires a ISO 8601 format date with optional millisecond precision.
        parent::__construct('Y-m-d\TH:i:s.uP');

        $this->systemName = $systemName ?: gethostname();
        $this->applicationName = $applicationName;
        $this->extraPrefix = $extraPrefix;
        $this->contextPrefix = $contextPrefix;
        $this->version = $version;
    }

    /**
     * {@inheritdoc}
     */
    public function format(array $record)
    {
        $record = parent::format($record);

        if ($this->version === self::V1) {
            $message = $this->formatV1($record);
        } else {
            $message = $this->formatV0($record);
        }

        return $this->toJson($message) . "\n";
    }

    protected function formatV0(array $record)
    {
        if (empty($record['datetime'])) {
            $record['datetime'] = gmdate('c');
        }
        $message = array(
            '@timestamp' => $record['datetime'],
            '@source' => $this->systemName,
            '@fields' => array(),
        );
        if (isset($record['message'])) {
            $message['@message'] = $record['message'];
        }
        if (isset($record['channel'])) {
            $message['@tags'] = array($record['channel']);
            $message['@fields']['channel'] = $record['channel'];
        }
        if (isset($record['level'])) {
            $message['@fields']['level'] = $record['level'];
        }
        if ($this->applicationName) {
            $message['@type'] = $this->applicationName;
        }
        if (isset($record['extra']['server'])) {
            $message['@source_host'] = $record['extra']['server'];
        }
        if (isset($record['extra']['url'])) {
            $message['@source_path'] = $record['extra']['url'];
        }
        if (!empty($record['extra'])) {
            foreach ($record['extra'] as $key => $val) {
                $message['@fields'][$this->extraPrefix . $key] = $val;
            }
        }
        if (!empty($record['context'])) {
            foreach ($record['context'] as $key => $val) {
                $message['@fields'][$this->contextPrefix . $key] = $val;
            }
        }

        return $message;
    }

    protected function formatV1(array $record)
    {
        if (empty($record['datetime'])) {
            $record['datetime'] = gmdate('c');
        }
        $message = array(
            '@timestamp' => $record['datetime'],
            '@version' => 1,
            'host' => $this->systemName,
        );
        if (isset($record['message'])) {
            $message['message'] = $record['message'];
        }
        if (isset($record['channel'])) {
            $message['type'] = $record['channel'];
            $message['channel'] = $record['channel'];
        }
        if (isset($record['level_name'])) {
            $message['level'] = $record['level_name'];
        }
        if ($this->applicationName) {
            $message['type'] = $this->applicationName;
        }
        if (!empty($record['extra'])) {
            foreach ($record['extra'] as $key => $val) {
                $message[$this->extraPrefix . $key] = $val;
            }
        }
        if (!empty($record['context'])) {
            foreach ($record['context'] as $key => $val) {
                $message[$this->contextPrefix . $key] = $val;
            }
        }

        return $message;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

/**
 * Encodes message information into JSON in a format compatible with Loggly.
 *
 * @author Adam Pancutt <adam@pancutt.com>
 */
class LogglyFormatter extends JsonFormatter
{
    /**
     * Overrides the default batch mode to new lines for compatibility with the
     * Loggly bulk API.
     *
     * @param int $batchMode
     */
    public function __construct($batchMode = self::BATCH_MODE_NEWLINES, $appendNewline = false)
    {
        parent::__construct($batchMode, $appendNewline);
    }

    /**
     * Appends the 'timestamp' parameter for indexing by Loggly.
     *
     * @see https://www.loggly.com/docs/automated-parsing/#json
     * @see \Monolog\Formatter\JsonFormatter::format()
     */
    public function format(array $record)
    {
        if (isset($record["datetime"]) && ($record["datetime"] instanceof \DateTime)) {
            $record["timestamp"] = $record["datetime"]->format("Y-m-d\TH:i:s.uO");
            // TODO 2.0 unset the 'datetime' parameter, retained for BC
        }

        return parent::format($record);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

/**
 * Formats data into an associative array of scalar values.
 * Objects and arrays will be JSON encoded.
 *
 * @author Andrew Lawson <adlawson@gmail.com>
 */
class ScalarFormatter extends NormalizerFormatter
{
    /**
     * {@inheritdoc}
     */
    public function format(array $record)
    {
        foreach ($record as $key => $value) {
            $record[$key] = $this->normalizeValue($value);
        }

        return $record;
    }

    /**
     * @param  mixed $value
     * @return mixed
     */
    protected function normalizeValue($value)
    {
        $normalized = $this->normalize($value);

        if (is_array($normalized) || is_object($normalized)) {
            return $this->toJson($normalized, true);
        }

        return $normalized;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

use Monolog\Utils;

/**
 * Formats a record for use with the MongoDBHandler.
 *
 * @author Florian Plattner <me@florianplattner.de>
 */
class MongoDBFormatter implements FormatterInterface
{
    private $exceptionTraceAsString;
    private $maxNestingLevel;

    /**
     * @param int  $maxNestingLevel        0 means infinite nesting, the $record itself is level 1, $record['context'] is 2
     * @param bool $exceptionTraceAsString set to false to log exception traces as a sub documents instead of strings
     */
    public function __construct($maxNestingLevel = 3, $exceptionTraceAsString = true)
    {
        $this->maxNestingLevel = max($maxNestingLevel, 0);
        $this->exceptionTraceAsString = (bool) $exceptionTraceAsString;
    }

    /**
     * {@inheritDoc}
     */
    public function format(array $record)
    {
        return $this->formatArray($record);
    }

    /**
     * {@inheritDoc}
     */
    public function formatBatch(array $records)
    {
        foreach ($records as $key => $record) {
            $records[$key] = $this->format($record);
        }

        return $records;
    }

    protected function formatArray(array $record, $nestingLevel = 0)
    {
        if ($this->maxNestingLevel == 0 || $nestingLevel <= $this->maxNestingLevel) {
            foreach ($record as $name => $value) {
                if ($value instanceof \DateTime) {
                    $record[$name] = $this->formatDate($value, $nestingLevel + 1);
                } elseif ($value instanceof \Exception) {
                    $record[$name] = $this->formatException($value, $nestingLevel + 1);
                } elseif (is_array($value)) {
                    $record[$name] = $this->formatArray($value, $nestingLevel + 1);
                } elseif (is_object($value)) {
                    $record[$name] = $this->formatObject($value, $nestingLevel + 1);
                }
            }
        } else {
            $record = '[...]';
        }

        return $record;
    }

    protected function formatObject($value, $nestingLevel)
    {
        $objectVars = get_object_vars($value);
        $objectVars['class'] = Utils::getClass($value);

        return $this->formatArray($objectVars, $nestingLevel);
    }

    protected function formatException(\Exception $exception, $nestingLevel)
    {
        $formattedException = array(
            'class' => Utils::getClass($exception),
            'message' => $exception->getMessage(),
            'code' => $exception->getCode(),
            'file' => $exception->getFile() . ':' . $exception->getLine(),
        );

        if ($this->exceptionTraceAsString === true) {
            $formattedException['trace'] = $exception->getTraceAsString();
        } else {
            $formattedException['trace'] = $exception->getTrace();
        }

        return $this->formatArray($formattedException, $nestingLevel);
    }

    protected function formatDate(\DateTime $value, $nestingLevel)
    {
        return new \MongoDate($value->getTimestamp());
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

use Monolog\Logger;

/**
 * Formats a log message according to the ChromePHP array format
 *
 * @author Christophe Coevoet <stof@notk.org>
 */
class ChromePHPFormatter implements FormatterInterface
{
    /**
     * Translates Monolog log levels to Wildfire levels.
     */
    private $logLevels = array(
        Logger::DEBUG     => 'log',
        Logger::INFO      => 'info',
        Logger::NOTICE    => 'info',
        Logger::WARNING   => 'warn',
        Logger::ERROR     => 'error',
        Logger::CRITICAL  => 'error',
        Logger::ALERT     => 'error',
        Logger::EMERGENCY => 'error',
    );

    /**
     * {@inheritdoc}
     */
    public function format(array $record)
    {
        // Retrieve the line and file if set and remove them from the formatted extra
        $backtrace = 'unknown';
        if (isset($record['extra']['file'], $record['extra']['line'])) {
            $backtrace = $record['extra']['file'].' : '.$record['extra']['line'];
            unset($record['extra']['file'], $record['extra']['line']);
        }

        $message = array('message' => $record['message']);
        if ($record['context']) {
            $message['context'] = $record['context'];
        }
        if ($record['extra']) {
            $message['extra'] = $record['extra'];
        }
        if (count($message) === 1) {
            $message = reset($message);
        }

        return array(
            $record['channel'],
            $message,
            $backtrace,
            $this->logLevels[$record['level']],
        );
    }

    public function formatBatch(array $records)
    {
        $formatted = array();

        foreach ($records as $record) {
            $formatted[] = $this->format($record);
        }

        return $formatted;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

/**
 * Class FluentdFormatter
 *
 * Serializes a log message to Fluentd unix socket protocol
 *
 * Fluentd config:
 *
 * <source>
 *  type unix
 *  path /var/run/td-agent/td-agent.sock
 * </source>
 *
 * Monolog setup:
 *
 * $logger = new Monolog\Logger('fluent.tag');
 * $fluentHandler = new Monolog\Handler\SocketHandler('unix:///var/run/td-agent/td-agent.sock');
 * $fluentHandler->setFormatter(new Monolog\Formatter\FluentdFormatter());
 * $logger->pushHandler($fluentHandler);
 *
 * @author Andrius Putna <fordnox@gmail.com>
 */
class FluentdFormatter implements FormatterInterface
{
    /**
     * @var bool $levelTag should message level be a part of the fluentd tag
     */
    protected $levelTag = false;

    public function __construct($levelTag = false)
    {
        if (!function_exists('json_encode')) {
            throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s FluentdUnixFormatter');
        }

        $this->levelTag = (bool) $levelTag;
    }

    public function isUsingLevelsInTag()
    {
        return $this->levelTag;
    }

    public function format(array $record)
    {
        $tag = $record['channel'];
        if ($this->levelTag) {
            $tag .= '.' . strtolower($record['level_name']);
        }

        $message = array(
            'message' => $record['message'],
            'context' => $record['context'],
            'extra' => $record['extra'],
        );

        if (!$this->levelTag) {
            $message['level'] = $record['level'];
            $message['level_name'] = $record['level_name'];
        }

        return json_encode(array($tag, $record['datetime']->getTimestamp(), $message));
    }

    public function formatBatch(array $records)
    {
        $message = '';
        foreach ($records as $record) {
            $message .= $this->format($record);
        }

        return $message;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

use Monolog\Logger;
use Gelf\Message;

/**
 * Serializes a log message to GELF
 * @see http://www.graylog2.org/about/gelf
 *
 * @author Matt Lehner <mlehner@gmail.com>
 */
class GelfMessageFormatter extends NormalizerFormatter
{
    const DEFAULT_MAX_LENGTH = 32766;

    /**
     * @var string the name of the system for the Gelf log message
     */
    protected $systemName;

    /**
     * @var string a prefix for 'extra' fields from the Monolog record (optional)
     */
    protected $extraPrefix;

    /**
     * @var string a prefix for 'context' fields from the Monolog record (optional)
     */
    protected $contextPrefix;

    /**
     * @var int max length per field
     */
    protected $maxLength;

    /**
     * Translates Monolog log levels to Graylog2 log priorities.
     */
    private $logLevels = array(
        Logger::DEBUG     => 7,
        Logger::INFO      => 6,
        Logger::NOTICE    => 5,
        Logger::WARNING   => 4,
        Logger::ERROR     => 3,
        Logger::CRITICAL  => 2,
        Logger::ALERT     => 1,
        Logger::EMERGENCY => 0,
    );

    public function __construct($systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_', $maxLength = null)
    {
        parent::__construct('U.u');

        $this->systemName = $systemName ?: gethostname();

        $this->extraPrefix = $extraPrefix;
        $this->contextPrefix = $contextPrefix;
        $this->maxLength = is_null($maxLength) ? self::DEFAULT_MAX_LENGTH : $maxLength;
    }

    /**
     * {@inheritdoc}
     */
    public function format(array $record)
    {
        $record = parent::format($record);

        if (!isset($record['datetime'], $record['message'], $record['level'])) {
            throw new \InvalidArgumentException('The record should at least contain datetime, message and level keys, '.var_export($record, true).' given');
        }

        $message = new Message();
        $message
            ->setTimestamp($record['datetime'])
            ->setShortMessage((string) $record['message'])
            ->setHost($this->systemName)
            ->setLevel($this->logLevels[$record['level']]);

        // message length + system name length + 200 for padding / metadata 
        $len = 200 + strlen((string) $record['message']) + strlen($this->systemName);

        if ($len > $this->maxLength) {
            $message->setShortMessage(substr($record['message'], 0, $this->maxLength));
        }

        if (isset($record['channel'])) {
            $message->setFacility($record['channel']);
        }
        if (isset($record['extra']['line'])) {
            $message->setLine($record['extra']['line']);
            unset($record['extra']['line']);
        }
        if (isset($record['extra']['file'])) {
            $message->setFile($record['extra']['file']);
            unset($record['extra']['file']);
        }

        foreach ($record['extra'] as $key => $val) {
            $val = is_scalar($val) || null === $val ? $val : $this->toJson($val);
            $len = strlen($this->extraPrefix . $key . $val);
            if ($len > $this->maxLength) {
                $message->setAdditional($this->extraPrefix . $key, substr($val, 0, $this->maxLength));
                break;
            }
            $message->setAdditional($this->extraPrefix . $key, $val);
        }

        foreach ($record['context'] as $key => $val) {
            $val = is_scalar($val) || null === $val ? $val : $this->toJson($val);
            $len = strlen($this->contextPrefix . $key . $val);
            if ($len > $this->maxLength) {
                $message->setAdditional($this->contextPrefix . $key, substr($val, 0, $this->maxLength));
                break;
            }
            $message->setAdditional($this->contextPrefix . $key, $val);
        }

        if (null === $message->getFile() && isset($record['context']['exception']['file'])) {
            if (preg_match("/^(.+):([0-9]+)$/", $record['context']['exception']['file'], $matches)) {
                $message->setFile($matches[1]);
                $message->setLine($matches[2]);
            }
        }

        return $message;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

use Monolog\Logger;

/**
 * Serializes a log message according to Wildfire's header requirements
 *
 * @author Eric Clemmons (@ericclemmons) <eric@uxdriven.com>
 * @author Christophe Coevoet <stof@notk.org>
 * @author Kirill chEbba Chebunin <iam@chebba.org>
 */
class WildfireFormatter extends NormalizerFormatter
{
    const TABLE = 'table';

    /**
     * Translates Monolog log levels to Wildfire levels.
     */
    private $logLevels = array(
        Logger::DEBUG     => 'LOG',
        Logger::INFO      => 'INFO',
        Logger::NOTICE    => 'INFO',
        Logger::WARNING   => 'WARN',
        Logger::ERROR     => 'ERROR',
        Logger::CRITICAL  => 'ERROR',
        Logger::ALERT     => 'ERROR',
        Logger::EMERGENCY => 'ERROR',
    );

    /**
     * {@inheritdoc}
     */
    public function format(array $record)
    {
        // Retrieve the line and file if set and remove them from the formatted extra
        $file = $line = '';
        if (isset($record['extra']['file'])) {
            $file = $record['extra']['file'];
            unset($record['extra']['file']);
        }
        if (isset($record['extra']['line'])) {
            $line = $record['extra']['line'];
            unset($record['extra']['line']);
        }

        $record = $this->normalize($record);
        $message = array('message' => $record['message']);
        $handleError = false;
        if ($record['context']) {
            $message['context'] = $record['context'];
            $handleError = true;
        }
        if ($record['extra']) {
            $message['extra'] = $record['extra'];
            $handleError = true;
        }
        if (count($message) === 1) {
            $message = reset($message);
        }

        if (isset($record['context'][self::TABLE])) {
            $type  = 'TABLE';
            $label = $record['channel'] .': '. $record['message'];
            $message = $record['context'][self::TABLE];
        } else {
            $type  = $this->logLevels[$record['level']];
            $label = $record['channel'];
        }

        // Create JSON object describing the appearance of the message in the console
        $json = $this->toJson(array(
            array(
                'Type'  => $type,
                'File'  => $file,
                'Line'  => $line,
                'Label' => $label,
            ),
            $message,
        ), $handleError);

        // The message itself is a serialization of the above JSON object + it's length
        return sprintf(
            '%s|%s|',
            strlen($json),
            $json
        );
    }

    public function formatBatch(array $records)
    {
        throw new \BadMethodCallException('Batch formatting does not make sense for the WildfireFormatter');
    }

    protected function normalize($data, $depth = 0)
    {
        if (is_object($data) && !$data instanceof \DateTime) {
            return $data;
        }

        return parent::normalize($data, $depth);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

use Exception;
use Monolog\Utils;

/**
 * Normalizes incoming records to remove objects/resources so it's easier to dump to various targets
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class NormalizerFormatter implements FormatterInterface
{
    const SIMPLE_DATE = "Y-m-d H:i:s";

    protected $dateFormat;

    /**
     * @param string $dateFormat The format of the timestamp: one supported by DateTime::format
     */
    public function __construct($dateFormat = null)
    {
        $this->dateFormat = $dateFormat ?: static::SIMPLE_DATE;
        if (!function_exists('json_encode')) {
            throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s NormalizerFormatter');
        }
    }

    /**
     * {@inheritdoc}
     */
    public function format(array $record)
    {
        return $this->normalize($record);
    }

    /**
     * {@inheritdoc}
     */
    public function formatBatch(array $records)
    {
        foreach ($records as $key => $record) {
            $records[$key] = $this->format($record);
        }

        return $records;
    }

    protected function normalize($data, $depth = 0)
    {
        if ($depth > 9) {
            return 'Over 9 levels deep, aborting normalization';
        }

        if (null === $data || is_scalar($data)) {
            if (is_float($data)) {
                if (is_infinite($data)) {
                    return ($data > 0 ? '' : '-') . 'INF';
                }
                if (is_nan($data)) {
                    return 'NaN';
                }
            }

            return $data;
        }

        if (is_array($data)) {
            $normalized = array();

            $count = 1;
            foreach ($data as $key => $value) {
                if ($count++ > 1000) {
                    $normalized['...'] = 'Over 1000 items ('.count($data).' total), aborting normalization';
                    break;
                }

                $normalized[$key] = $this->normalize($value, $depth+1);
            }

            return $normalized;
        }

        if ($data instanceof \DateTime) {
            return $data->format($this->dateFormat);
        }

        if (is_object($data)) {
            // TODO 2.0 only check for Throwable
            if ($data instanceof Exception || (PHP_VERSION_ID > 70000 && $data instanceof \Throwable)) {
                return $this->normalizeException($data);
            }

            // non-serializable objects that implement __toString stringified
            if (method_exists($data, '__toString') && !$data instanceof \JsonSerializable) {
                $value = $data->__toString();
            } else {
                // the rest is json-serialized in some way
                $value = $this->toJson($data, true);
            }

            return sprintf("[object] (%s: %s)", Utils::getClass($data), $value);
        }

        if (is_resource($data)) {
            return sprintf('[resource] (%s)', get_resource_type($data));
        }

        return '[unknown('.gettype($data).')]';
    }

    protected function normalizeException($e)
    {
        // TODO 2.0 only check for Throwable
        if (!$e instanceof Exception && !$e instanceof \Throwable) {
            throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.Utils::getClass($e));
        }

        $data = array(
            'class' => Utils::getClass($e),
            'message' => $e->getMessage(),
            'code' => $e->getCode(),
            'file' => $e->getFile().':'.$e->getLine(),
        );

        if ($e instanceof \SoapFault) {
            if (isset($e->faultcode)) {
                $data['faultcode'] = $e->faultcode;
            }

            if (isset($e->faultactor)) {
                $data['faultactor'] = $e->faultactor;
            }

            if (isset($e->detail)) {
                $data['detail'] = $e->detail;
            }
        }

        $trace = $e->getTrace();
        foreach ($trace as $frame) {
            if (isset($frame['file'])) {
                $data['trace'][] = $frame['file'].':'.$frame['line'];
            } elseif (isset($frame['function']) && $frame['function'] === '{closure}') {
                // Simplify closures handling
                $data['trace'][] = $frame['function'];
            } else {
                if (isset($frame['args'])) {
                    // Make sure that objects present as arguments are not serialized nicely but rather only
                    // as a class name to avoid any unexpected leak of sensitive information
                    $frame['args'] = array_map(function ($arg) {
                        if (is_object($arg) && !($arg instanceof \DateTime || $arg instanceof \DateTimeInterface)) {
                            return sprintf("[object] (%s)", Utils::getClass($arg));
                        }

                        return $arg;
                    }, $frame['args']);
                }
                // We should again normalize the frames, because it might contain invalid items
                $data['trace'][] = $this->toJson($this->normalize($frame), true);
            }
        }

        if ($previous = $e->getPrevious()) {
            $data['previous'] = $this->normalizeException($previous);
        }

        return $data;
    }

    /**
     * Return the JSON representation of a value
     *
     * @param  mixed             $data
     * @param  bool              $ignoreErrors
     * @throws \RuntimeException if encoding fails and errors are not ignored
     * @return string
     */
    protected function toJson($data, $ignoreErrors = false)
    {
        // suppress json_encode errors since it's twitchy with some inputs
        if ($ignoreErrors) {
            return @$this->jsonEncode($data);
        }

        $json = $this->jsonEncode($data);

        if ($json === false) {
            $json = $this->handleJsonError(json_last_error(), $data);
        }

        return $json;
    }

    /**
     * @param  mixed  $data
     * @return string JSON encoded data or null on failure
     */
    private function jsonEncode($data)
    {
        if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
            return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        }

        return json_encode($data);
    }

    /**
     * Handle a json_encode failure.
     *
     * If the failure is due to invalid string encoding, try to clean the
     * input and encode again. If the second encoding attempt fails, the
     * inital error is not encoding related or the input can't be cleaned then
     * raise a descriptive exception.
     *
     * @param  int               $code return code of json_last_error function
     * @param  mixed             $data data that was meant to be encoded
     * @throws \RuntimeException if failure can't be corrected
     * @return string            JSON encoded data after error correction
     */
    private function handleJsonError($code, $data)
    {
        if ($code !== JSON_ERROR_UTF8) {
            $this->throwEncodeError($code, $data);
        }

        if (is_string($data)) {
            $this->detectAndCleanUtf8($data);
        } elseif (is_array($data)) {
            array_walk_recursive($data, array($this, 'detectAndCleanUtf8'));
        } else {
            $this->throwEncodeError($code, $data);
        }

        $json = $this->jsonEncode($data);

        if ($json === false) {
            $this->throwEncodeError(json_last_error(), $data);
        }

        return $json;
    }

    /**
     * Throws an exception according to a given code with a customized message
     *
     * @param  int               $code return code of json_last_error function
     * @param  mixed             $data data that was meant to be encoded
     * @throws \RuntimeException
     */
    private function throwEncodeError($code, $data)
    {
        switch ($code) {
            case JSON_ERROR_DEPTH:
                $msg = 'Maximum stack depth exceeded';
                break;
            case JSON_ERROR_STATE_MISMATCH:
                $msg = 'Underflow or the modes mismatch';
                break;
            case JSON_ERROR_CTRL_CHAR:
                $msg = 'Unexpected control character found';
                break;
            case JSON_ERROR_UTF8:
                $msg = 'Malformed UTF-8 characters, possibly incorrectly encoded';
                break;
            default:
                $msg = 'Unknown error';
        }

        throw new \RuntimeException('JSON encoding failed: '.$msg.'. Encoding: '.var_export($data, true));
    }

    /**
     * Detect invalid UTF-8 string characters and convert to valid UTF-8.
     *
     * Valid UTF-8 input will be left unmodified, but strings containing
     * invalid UTF-8 codepoints will be reencoded as UTF-8 with an assumed
     * original encoding of ISO-8859-15. This conversion may result in
     * incorrect output if the actual encoding was not ISO-8859-15, but it
     * will be clean UTF-8 output and will not rely on expensive and fragile
     * detection algorithms.
     *
     * Function converts the input in place in the passed variable so that it
     * can be used as a callback for array_walk_recursive.
     *
     * @param mixed &$data Input to check and convert if needed
     * @private
     */
    public function detectAndCleanUtf8(&$data)
    {
        if (is_string($data) && !preg_match('//u', $data)) {
            $data = preg_replace_callback(
                '/[\x80-\xFF]+/',
                function ($m) { return utf8_encode($m[0]); },
                $data
            );
            $data = str_replace(
                array('Â¤', 'Â¦', 'Â¨', 'Â´', 'Â¸', 'Â¼', 'Â½', 'Â¾'),
                array('â‚¬', 'Å ', 'Å¡', 'Å½', 'Å¾', 'Å’', 'Å“', 'Å¸'),
                $data
            );
        }
    }
}
<?php
/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

use Monolog\Logger;

/**
 * Formats incoming records into an HTML table
 *
 * This is especially useful for html email logging
 *
 * @author Tiago Brito <tlfbrito@gmail.com>
 */
class HtmlFormatter extends NormalizerFormatter
{
    /**
     * Translates Monolog log levels to html color priorities.
     */
    protected $logLevels = array(
        Logger::DEBUG     => '#cccccc',
        Logger::INFO      => '#468847',
        Logger::NOTICE    => '#3a87ad',
        Logger::WARNING   => '#c09853',
        Logger::ERROR     => '#f0ad4e',
        Logger::CRITICAL  => '#FF7708',
        Logger::ALERT     => '#C12A19',
        Logger::EMERGENCY => '#000000',
    );

    /**
     * @param string $dateFormat The format of the timestamp: one supported by DateTime::format
     */
    public function __construct($dateFormat = null)
    {
        parent::__construct($dateFormat);
    }

    /**
     * Creates an HTML table row
     *
     * @param  string $th       Row header content
     * @param  string $td       Row standard cell content
     * @param  bool   $escapeTd false if td content must not be html escaped
     * @return string
     */
    protected function addRow($th, $td = ' ', $escapeTd = true)
    {
        $th = htmlspecialchars($th, ENT_NOQUOTES, 'UTF-8');
        if ($escapeTd) {
            $td = '<pre>'.htmlspecialchars($td, ENT_NOQUOTES, 'UTF-8').'</pre>';
        }

        return "<tr style=\"padding: 4px;text-align: left;\">\n<th style=\"vertical-align: top;background: #ccc;color: #000\" width=\"100\">$th:</th>\n<td style=\"padding: 4px;text-align: left;vertical-align: top;background: #eee;color: #000\">".$td."</td>\n</tr>";
    }

    /**
     * Create a HTML h1 tag
     *
     * @param  string $title Text to be in the h1
     * @param  int    $level Error level
     * @return string
     */
    protected function addTitle($title, $level)
    {
        $title = htmlspecialchars($title, ENT_NOQUOTES, 'UTF-8');

        return '<h1 style="background: '.$this->logLevels[$level].';color: #ffffff;padding: 5px;" class="monolog-output">'.$title.'</h1>';
    }

    /**
     * Formats a log record.
     *
     * @param  array $record A record to format
     * @return mixed The formatted record
     */
    public function format(array $record)
    {
        $output = $this->addTitle($record['level_name'], $record['level']);
        $output .= '<table cellspacing="1" width="100%" class="monolog-output">';

        $output .= $this->addRow('Message', (string) $record['message']);
        $output .= $this->addRow('Time', $record['datetime']->format($this->dateFormat));
        $output .= $this->addRow('Channel', $record['channel']);
        if ($record['context']) {
            $embeddedTable = '<table cellspacing="1" width="100%">';
            foreach ($record['context'] as $key => $value) {
                $embeddedTable .= $this->addRow($key, $this->convertToString($value));
            }
            $embeddedTable .= '</table>';
            $output .= $this->addRow('Context', $embeddedTable, false);
        }
        if ($record['extra']) {
            $embeddedTable = '<table cellspacing="1" width="100%">';
            foreach ($record['extra'] as $key => $value) {
                $embeddedTable .= $this->addRow($key, $this->convertToString($value));
            }
            $embeddedTable .= '</table>';
            $output .= $this->addRow('Extra', $embeddedTable, false);
        }

        return $output.'</table>';
    }

    /**
     * Formats a set of log records.
     *
     * @param  array $records A set of records to format
     * @return mixed The formatted set of records
     */
    public function formatBatch(array $records)
    {
        $message = '';
        foreach ($records as $record) {
            $message .= $this->format($record);
        }

        return $message;
    }

    protected function convertToString($data)
    {
        if (null === $data || is_scalar($data)) {
            return (string) $data;
        }

        $data = $this->normalize($data);
        if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
            return json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        }

        return str_replace('\\/', '/', json_encode($data));
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog;

use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Monolog\Handler\AbstractHandler;
use Monolog\Registry;

/**
 * Monolog error handler
 *
 * A facility to enable logging of runtime errors, exceptions and fatal errors.
 *
 * Quick setup: <code>ErrorHandler::register($logger);</code>
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ErrorHandler
{
    private $logger;

    private $previousExceptionHandler;
    private $uncaughtExceptionLevel;

    private $previousErrorHandler;
    private $errorLevelMap;
    private $handleOnlyReportedErrors;

    private $hasFatalErrorHandler;
    private $fatalLevel;
    private $reservedMemory;
    private $lastFatalTrace;
    private static $fatalErrors = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR);

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

    /**
     * Registers a new ErrorHandler for a given Logger
     *
     * By default it will handle errors, exceptions and fatal errors
     *
     * @param  LoggerInterface $logger
     * @param  array|false     $errorLevelMap  an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling
     * @param  int|false       $exceptionLevel a LogLevel::* constant, or false to disable exception handling
     * @param  int|false       $fatalLevel     a LogLevel::* constant, or false to disable fatal error handling
     * @return ErrorHandler
     */
    public static function register(LoggerInterface $logger, $errorLevelMap = array(), $exceptionLevel = null, $fatalLevel = null)
    {
        //Forces the autoloader to run for LogLevel. Fixes an autoload issue at compile-time on PHP5.3. See https://github.com/Seldaek/monolog/pull/929
        class_exists('\\Psr\\Log\\LogLevel', true);

        $handler = new static($logger);
        if ($errorLevelMap !== false) {
            $handler->registerErrorHandler($errorLevelMap);
        }
        if ($exceptionLevel !== false) {
            $handler->registerExceptionHandler($exceptionLevel);
        }
        if ($fatalLevel !== false) {
            $handler->registerFatalHandler($fatalLevel);
        }

        return $handler;
    }

    public function registerExceptionHandler($level = null, $callPrevious = true)
    {
        $prev = set_exception_handler(array($this, 'handleException'));
        $this->uncaughtExceptionLevel = $level;
        if ($callPrevious && $prev) {
            $this->previousExceptionHandler = $prev;
        }
    }

    public function registerErrorHandler(array $levelMap = array(), $callPrevious = true, $errorTypes = -1, $handleOnlyReportedErrors = true)
    {
        $prev = set_error_handler(array($this, 'handleError'), $errorTypes);
        $this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap);
        if ($callPrevious) {
            $this->previousErrorHandler = $prev ?: true;
        }

        $this->handleOnlyReportedErrors = $handleOnlyReportedErrors;
    }

    public function registerFatalHandler($level = null, $reservedMemorySize = 20)
    {
        register_shutdown_function(array($this, 'handleFatalError'));

        $this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize);
        $this->fatalLevel = $level;
        $this->hasFatalErrorHandler = true;
    }

    protected function defaultErrorLevelMap()
    {
        return array(
            E_ERROR             => LogLevel::CRITICAL,
            E_WARNING           => LogLevel::WARNING,
            E_PARSE             => LogLevel::ALERT,
            E_NOTICE            => LogLevel::NOTICE,
            E_CORE_ERROR        => LogLevel::CRITICAL,
            E_CORE_WARNING      => LogLevel::WARNING,
            E_COMPILE_ERROR     => LogLevel::ALERT,
            E_COMPILE_WARNING   => LogLevel::WARNING,
            E_USER_ERROR        => LogLevel::ERROR,
            E_USER_WARNING      => LogLevel::WARNING,
            E_USER_NOTICE       => LogLevel::NOTICE,
            E_STRICT            => LogLevel::NOTICE,
            E_RECOVERABLE_ERROR => LogLevel::ERROR,
            E_DEPRECATED        => LogLevel::NOTICE,
            E_USER_DEPRECATED   => LogLevel::NOTICE,
        );
    }

    /**
     * @private
     */
    public function handleException($e)
    {
        $this->logger->log(
            $this->uncaughtExceptionLevel === null ? LogLevel::ERROR : $this->uncaughtExceptionLevel,
            sprintf('Uncaught Exception %s: "%s" at %s line %s', Utils::getClass($e), $e->getMessage(), $e->getFile(), $e->getLine()),
            array('exception' => $e)
        );

        if ($this->previousExceptionHandler) {
            call_user_func($this->previousExceptionHandler, $e);
        }

        exit(255);
    }

    /**
     * @private
     */
    public function handleError($code, $message, $file = '', $line = 0, $context = array())
    {
        if ($this->handleOnlyReportedErrors && !(error_reporting() & $code)) {
            return;
        }

        // fatal error codes are ignored if a fatal error handler is present as well to avoid duplicate log entries
        if (!$this->hasFatalErrorHandler || !in_array($code, self::$fatalErrors, true)) {
            $level = isset($this->errorLevelMap[$code]) ? $this->errorLevelMap[$code] : LogLevel::CRITICAL;
            $this->logger->log($level, self::codeToString($code).': '.$message, array('code' => $code, 'message' => $message, 'file' => $file, 'line' => $line));
        } else {
            // http://php.net/manual/en/function.debug-backtrace.php
            // As of 5.3.6, DEBUG_BACKTRACE_IGNORE_ARGS option was added.
            // Any version less than 5.3.6 must use the DEBUG_BACKTRACE_IGNORE_ARGS constant value '2'.
            $trace = debug_backtrace((PHP_VERSION_ID < 50306) ? 2 : DEBUG_BACKTRACE_IGNORE_ARGS);
            array_shift($trace); // Exclude handleError from trace
            $this->lastFatalTrace = $trace;
        }

        if ($this->previousErrorHandler === true) {
            return false;
        } elseif ($this->previousErrorHandler) {
            return call_user_func($this->previousErrorHandler, $code, $message, $file, $line, $context);
        }
    }

    /**
     * @private
     */
    public function handleFatalError()
    {
        $this->reservedMemory = null;

        $lastError = error_get_last();
        if ($lastError && in_array($lastError['type'], self::$fatalErrors, true)) {
            $this->logger->log(
                $this->fatalLevel === null ? LogLevel::ALERT : $this->fatalLevel,
                'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'],
                array('code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line'], 'trace' => $this->lastFatalTrace)
            );

            if ($this->logger instanceof Logger) {
                foreach ($this->logger->getHandlers() as $handler) {
                    if ($handler instanceof AbstractHandler) {
                        $handler->close();
                    }
                }
            }
        }
    }

    private static function codeToString($code)
    {
        switch ($code) {
            case E_ERROR:
                return 'E_ERROR';
            case E_WARNING:
                return 'E_WARNING';
            case E_PARSE:
                return 'E_PARSE';
            case E_NOTICE:
                return 'E_NOTICE';
            case E_CORE_ERROR:
                return 'E_CORE_ERROR';
            case E_CORE_WARNING:
                return 'E_CORE_WARNING';
            case E_COMPILE_ERROR:
                return 'E_COMPILE_ERROR';
            case E_COMPILE_WARNING:
                return 'E_COMPILE_WARNING';
            case E_USER_ERROR:
                return 'E_USER_ERROR';
            case E_USER_WARNING:
                return 'E_USER_WARNING';
            case E_USER_NOTICE:
                return 'E_USER_NOTICE';
            case E_STRICT:
                return 'E_STRICT';
            case E_RECOVERABLE_ERROR:
                return 'E_RECOVERABLE_ERROR';
            case E_DEPRECATED:
                return 'E_DEPRECATED';
            case E_USER_DEPRECATED:
                return 'E_USER_DEPRECATED';
        }

        return 'Unknown PHP error';
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog;

class Utils
{
    /**
     * @internal
     */
    public static function getClass($object)
    {
        $class = \get_class($object);

        return 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class;
    }
}
{
  "name": "voku/php-hooks",
  "description": "A fork of the WordPress filters hook system rolled in to a class to be ported into any PHP-based system",
  "keywords": [
    "hook-system",
    "filters-system",
    "hooks",
    "actions",
    "filters"
  ],
  "type": "library",
  "homepage": "http://github.com/voku/php-hooks/",
  "license": "GPL-3.0",
  "authors": [
    {
      "name": "Ohad Raz",
      "homepage": "https://github.com/bainternet",
      "role": "Developer"
    },
    {
      "name": "Lars Moelleken",
      "homepage": "http://www.moelleken.org/",
      "role": "Developer"
    }
  ],
  "support": {
    "issues": "https://github.com/voku/php-hooks/issues"
  },
  "require": {
    "php": ">=5.3.0"
  },
  "require-dev": {
    "phpunit/phpunit": "4.*"
  },
  "autoload": {
    "psr-4": {
      "voku\\helper\\": "src/voku/helper/"
    }
  }
}
<?php

namespace voku\helper;

/**
 * PHP Hooks Class (Modified)
 *
 * <p>
 * <br />
 * The PHP Hooks Class is a fork of the WordPress filters hook system rolled in
 * to a class to be ported into any php based system
 *
 * <br /><br />
 * This class is heavily based on the WordPress plugin API and most (if not all)
 * of the code comes from there.
 * </p>
 *
 * @copyright   2011 - 2017
 *
 * @author      Ohad Raz <admin@bainternet.info>
 * @link        http://en.bainternet.info
 * @author      David Miles <david@amereservant.com>
 * @link        http://github.com/amereservant/PHP-Hooks
 * @author      Lars Moelleken <lars@moelleken.org>
 * @link        https://github.com/voku/PHP-Hooks/
 * @author      Damien "Mistic" Sorel <contact@git.strangeplanet.fr>
 * @link        http://www.strangeplanet.fr
 *
 * @license     GNU General Public License v3.0 - license.txt
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * @package     voku\helper
 */
class Hooks
{
  /**
   * Filters - holds list of hooks
   *
   * @var array
   */
  protected $filters = array();

  /**
   * Merged Filters
   *
   * @var array
   */
  protected $merged_filters = array();

  /**
   * Actions
   *
   * @var array
   */
  protected $actions = array();

  /**
   * Current Filter - holds the name of the current filter
   *
   * @var array
   */
  protected $current_filter = array();

  /**
   * Container for storing shortcode tags and their hook to call for the shortcode
   *
   * @var array
   */
  public static $shortcode_tags = array();

  /**
   * Default priority
   *
   * @const int
   */
  const PRIORITY_NEUTRAL = 50;

  /**
   * This class is not allowed to call from outside: private!
   */
  protected function __construct()
  {
  }

  /**
   * Prevent the object from being cloned.
   */
  protected function __clone()
  {
  }

  /**
   * Avoid serialization.
   */
  public function __wakeup()
  {
  }

  /**
   * Returns a Singleton instance of this class.
   *
   * @return Hooks
   */
  public static function getInstance()
  {
    static $instance;

    if (null === $instance) {
      $instance = new self();
    }

    return $instance;
  }

  /**
   * FILTERS
   */

  /**
   * Adds Hooks to a function or method to a specific filter action.
   *
   * @param    string       $tag             <p>
   *                                         The name of the filter to hook the
   *                                         {@link $function_to_add} to.
   *                                         </p>
   * @param    string|array $function_to_add <p>
   *                                         The name of the function to be called
   *                                         when the filter is applied.
   *                                         </p>
   * @param    integer      $priority        <p>
   *                                         [optional] Used to specify the order in
   *                                         which the functions associated with a
   *                                         particular action are executed (default: 50).
   *                                         Lower numbers correspond with earlier execution,
   *                                         and functions with the same priority are executed
   *                                         in the order in which they were added to the action.
   *                                         </p>
   * @param string          $include_path    <p>
   *                                         [optional] File to include before executing the callback.
   *                                         </p>
   *
   * @return boolean
   */
  public function add_filter($tag, $function_to_add, $priority = self::PRIORITY_NEUTRAL, $include_path = null)
  {
    $idx = $this->_filter_build_unique_id($function_to_add);

    $this->filters[$tag][$priority][$idx] = array(
        'function'     => $function_to_add,
        'include_path' => is_string($include_path) ? $include_path : null,
    );

    unset($this->merged_filters[$tag]);

    return true;
  }

  /**
   * Removes a function from a specified filter hook.
   *
   * @param string $tag                <p>The filter hook to which the function to be removed is hooked.</p>
   * @param mixed  $function_to_remove <p>The name of the function which should be removed.</p>
   * @param int    $priority           <p>[optional] The priority of the function (default: 50).</p>
   *
   * @return bool
   */
  public function remove_filter($tag, $function_to_remove, $priority = self::PRIORITY_NEUTRAL)
  {
    $function_to_remove = $this->_filter_build_unique_id($function_to_remove);

    if (!isset($this->filters[$tag][$priority][$function_to_remove])) {
      return false;
    }

    unset($this->filters[$tag][$priority][$function_to_remove]);
    if (empty($this->filters[$tag][$priority])) {
      unset($this->filters[$tag][$priority]);
    }

    unset($this->merged_filters[$tag]);

    return true;
  }

  /**
   * Remove all of the hooks from a filter.
   *
   * @param string $tag      <p>The filter to remove hooks from.</p>
   * @param bool   $priority <p>The priority number to remove.</p>
   *
   * @return bool
   */
  public function remove_all_filters($tag, $priority = false)
  {
    if (isset($this->merged_filters[$tag])) {
      unset($this->merged_filters[$tag]);
    }

    if (!isset($this->filters[$tag])) {
      return true;
    }

    if (false !== $priority && isset($this->filters[$tag][$priority])) {
      unset($this->filters[$tag][$priority]);
    } else {
      unset($this->filters[$tag]);
    }

    return true;
  }

  /**
   * Check if any filter has been registered for the given hook.
   *
   * <p>
   * <br />
   * <strong>INFO:</strong> Use !== false to check if it's true!
   * </p>
   *
   * @param    string $tag               <p>The name of the filter hook.</p>
   * @param    bool   $function_to_check <p>[optional] Callback function name to check for </p>
   *
   * @return   mixed                       <p>
   *                                       If {@link $function_to_check} is omitted,
   *                                       returns boolean for whether the hook has
   *                                       anything registered.
   *                                       When checking a specific function, the priority
   *                                       of that hook is returned, or false if the
   *                                       function is not attached.
   *                                       When using the {@link $function_to_check} argument,
   *                                       this function may return a non-boolean value that
   *                                       evaluates to false
   *                                       (e.g.) 0, so use the === operator for testing the return value.
   *                                       </p>
   */
  public function has_filter($tag, $function_to_check = false)
  {
    $has = isset($this->filters[$tag]);
    if (false === $function_to_check || !$has) {
      return $has;
    }

    if (!($idx = $this->_filter_build_unique_id($function_to_check))) {
      return false;
    }

    foreach ((array)array_keys($this->filters[$tag]) as $priority) {
      if (isset($this->filters[$tag][$priority][$idx])) {
        return $priority;
      }
    }

    return false;
  }

  /**
   * Call the functions added to a filter hook.
   *
   * <p>
   * <br />
   * <strong>INFO:</strong> Additional variables passed to the functions hooked to <tt>$tag</tt>.
   * </p>
   *
   * @param    string|array $tag   <p>The name of the filter hook.</p>
   * @param    mixed        $value <p>The value on which the filters hooked to <tt>$tag</tt> are applied on.</p>
   *
   * @return   mixed               <p>The filtered value after all hooked functions are applied to it.</p>
   */
  public function apply_filters($tag, $value)
  {
    $args = array();

    // Do 'all' actions first
    if (isset($this->filters['all'])) {
      $this->current_filter[] = $tag;
      $args = func_get_args();
      $this->_call_all_hook($args);
    }

    if (!isset($this->filters[$tag])) {
      if (isset($this->filters['all'])) {
        array_pop($this->current_filter);
      }

      return $value;
    }

    if (!isset($this->filters['all'])) {
      $this->current_filter[] = $tag;
    }

    // Sort
    if (!isset($this->merged_filters[$tag])) {
      ksort($this->filters[$tag]);
      $this->merged_filters[$tag] = true;
    }

    reset($this->filters[$tag]);

    if (empty($args)) {
      $args = func_get_args();
    }

    array_shift($args);

    do {
      foreach ((array)current($this->filters[$tag]) as $the_) {
        if (null !== $the_['function']) {

          if (null !== $the_['include_path']) {
            /** @noinspection PhpIncludeInspection */
            include_once $the_['include_path'];
          }

          $args[0] = $value;
          $value = call_user_func_array($the_['function'], $args);
        }
      }
    } while (next($this->filters[$tag]) !== false);

    array_pop($this->current_filter);

    return $value;
  }

  /**
   * Execute functions hooked on a specific filter hook, specifying arguments in an array.
   *
   * @param    string $tag  <p>The name of the filter hook.</p>
   * @param    array  $args <p>The arguments supplied to the functions hooked to <tt>$tag</tt></p>
   *
   * @return   mixed        <p>The filtered value after all hooked functions are applied to it.</p>
   */
  public function apply_filters_ref_array($tag, $args)
  {
    // Do 'all' actions first
    if (isset($this->filters['all'])) {
      $this->current_filter[] = $tag;
      $all_args = func_get_args();
      $this->_call_all_hook($all_args);
    }

    if (!isset($this->filters[$tag])) {
      if (isset($this->filters['all'])) {
        array_pop($this->current_filter);
      }

      return $args[0];
    }

    if (!isset($this->filters['all'])) {
      $this->current_filter[] = $tag;
    }

    // Sort
    if (!isset($this->merged_filters[$tag])) {
      ksort($this->filters[$tag]);
      $this->merged_filters[$tag] = true;
    }

    reset($this->filters[$tag]);

    do {
      foreach ((array)current($this->filters[$tag]) as $the_) {
        if (null !== $the_['function']) {

          if (null !== $the_['include_path']) {
            /** @noinspection PhpIncludeInspection */
            include_once $the_['include_path'];
          }

          $args[0] = call_user_func_array($the_['function'], $args);
        }
      }
    } while (next($this->filters[$tag]) !== false);

    array_pop($this->current_filter);

    return $args[0];
  }

  /**
   * Hooks a function on to a specific action.
   *
   * @param    string  $tag             <p>
   *                                    The name of the action to which the
   *                                    <tt>$function_to_add</tt> is hooked.
   *                                    </p>
   * @param    string  $function_to_add <p>The name of the function you wish to be called.</p>
   * @param    integer $priority        <p>
   *                                    [optional] Used to specify the order in which
   *                                    the functions associated with a particular
   *                                    action are executed (default: 50).
   *                                    Lower numbers correspond with earlier execution,
   *                                    and functions with the same priority are executed
   *                                    in the order in which they were added to the action.
   *                                    </p>
   * @param     string $include_path    <p>[optional] File to include before executing the callback.</p>
   *
   * @return bool
   */
  public function add_action($tag, $function_to_add, $priority = self::PRIORITY_NEUTRAL, $include_path = null)
  {
    return $this->add_filter($tag, $function_to_add, $priority, $include_path);
  }

  /**
   * Check if any action has been registered for a hook.
   *
   * <p>
   * <br />
   * <strong>INFO:</strong> Use !== false to check if it's true!
   * </p>
   *
   * @param    string   $tag               <p>The name of the action hook.</p>
   * @param bool|string $function_to_check <p>[optional]</p>
   *
   * @return   mixed                       <p>
   *                                       If <tt>$function_to_check</tt> is omitted,
   *                                       returns boolean for whether the hook has
   *                                       anything registered.
   *                                       When checking a specific function,
   *                                       the priority of that hook is returned,
   *                                       or false if the function is not attached.
   *                                       When using the <tt>$function_to_check</tt>
   *                                       argument, this function may return a non-boolean
   *                                       value that evaluates to false (e.g.) 0,
   *                                       so use the === operator for testing the return value.
   *                                       </p>
   */
  public function has_action($tag, $function_to_check = false)
  {
    return $this->has_filter($tag, $function_to_check);
  }

  /**
   * Removes a function from a specified action hook.
   *
   * @param string $tag                <p>The action hook to which the function to be removed is hooked.</p>
   * @param mixed  $function_to_remove <p>The name of the function which should be removed.</p>
   * @param int    $priority           <p>[optional] The priority of the function (default: 50).</p>
   *
   * @return bool <p>Whether the function is removed.</p>
   */
  public function remove_action($tag, $function_to_remove, $priority = self::PRIORITY_NEUTRAL)
  {
    return $this->remove_filter($tag, $function_to_remove, $priority);
  }

  /**
   * Remove all of the hooks from an action.
   *
   * @param string $tag      <p>The action to remove hooks from.</p>
   * @param bool   $priority <p>The priority number to remove them from.</p>
   *
   * @return bool
   */
  public function remove_all_actions($tag, $priority = false)
  {
    return $this->remove_all_filters($tag, $priority);
  }

  /**
   * Execute functions hooked on a specific action hook.
   *
   * @param    string $tag     <p>The name of the action to be executed.</p>
   * @param    mixed  $arg     <p>
   *                           [optional] Additional arguments which are passed on
   *                           to the functions hooked to the action.
   *                           </p>
   *
   * @return   bool            <p>Will return false if $tag does not exist in $filter array.</p>
   */
  public function do_action($tag, $arg = '')
  {
    if (!is_array($this->actions)) {
      $this->actions = array();
    }

    if (!isset($this->actions[$tag])) {
      $this->actions[$tag] = 1;
    } else {
      ++$this->actions[$tag];
    }

    // Do 'all' actions first
    if (isset($this->filters['all'])) {
      $this->current_filter[] = $tag;
      $all_args = func_get_args();
      $this->_call_all_hook($all_args);
    }

    if (!isset($this->filters[$tag])) {
      if (isset($this->filters['all'])) {
        array_pop($this->current_filter);
      }

      return false;
    }

    if (!isset($this->filters['all'])) {
      $this->current_filter[] = $tag;
    }

    $args = array();

    if (
        is_array($arg)
        &&
        isset($arg[0])
        &&
        is_object($arg[0])
        &&
        1 == count($arg)
    ) {
      $args[] =& $arg[0];
    } else {
      $args[] = $arg;
    }

    $numArgs = func_num_args();

    for ($a = 2; $a < $numArgs; $a++) {
      $args[] = func_get_arg($a);
    }

    // Sort
    if (!isset($this->merged_filters[$tag])) {
      ksort($this->filters[$tag]);
      $this->merged_filters[$tag] = true;
    }

    reset($this->filters[$tag]);

    do {
      foreach ((array)current($this->filters[$tag]) as $the_) {
        if (null !== $the_['function']) {

          if (null !== $the_['include_path']) {
            /** @noinspection PhpIncludeInspection */
            include_once $the_['include_path'];
          }

          call_user_func_array($the_['function'], $args);
        }
      }
    } while (next($this->filters[$tag]) !== false);

    array_pop($this->current_filter);

    return true;
  }

  /**
   * Execute functions hooked on a specific action hook, specifying arguments in an array.
   *
   * @param    string $tag  <p>The name of the action to be executed.</p>
   * @param    array  $args <p>The arguments supplied to the functions hooked to <tt>$tag</tt></p>
   *
   * @return   bool         <p>Will return false if $tag does not exist in $filter array.</p>
   */
  public function do_action_ref_array($tag, $args)
  {
    if (!is_array($this->actions)) {
      $this->actions = array();
    }

    if (!isset($this->actions[$tag])) {
      $this->actions[$tag] = 1;
    } else {
      ++$this->actions[$tag];
    }

    // Do 'all' actions first
    if (isset($this->filters['all'])) {
      $this->current_filter[] = $tag;
      $all_args = func_get_args();
      $this->_call_all_hook($all_args);
    }

    if (!isset($this->filters[$tag])) {
      if (isset($this->filters['all'])) {
        array_pop($this->current_filter);
      }

      return false;
    }

    if (!isset($this->filters['all'])) {
      $this->current_filter[] = $tag;
    }

    // Sort
    if (!isset($merged_filters[$tag])) {
      ksort($this->filters[$tag]);
      $merged_filters[$tag] = true;
    }

    reset($this->filters[$tag]);

    do {
      foreach ((array)current($this->filters[$tag]) as $the_) {
        if (null !== $the_['function']) {

          if (null !== $the_['include_path']) {
            /** @noinspection PhpIncludeInspection */
            include_once $the_['include_path'];
          }

          call_user_func_array($the_['function'], $args);
        }
      }
    } while (next($this->filters[$tag]) !== false);

    array_pop($this->current_filter);

    return true;
  }

  /**
   * Retrieve the number of times an action has fired.
   *
   * @param string $tag <p>The name of the action hook.</p>
   *
   * @return integer <p>The number of times action hook <tt>$tag</tt> is fired.</p>
   */
  public function did_action($tag)
  {
    if (!is_array($this->actions) || !isset($this->actions[$tag])) {
      return 0;
    }

    return $this->actions[$tag];
  }

  /**
   * Retrieve the name of the current filter or action.
   *
   * @return string <p>Hook name of the current filter or action.</p>
   */
  public function current_filter()
  {
    return end($this->current_filter);
  }

  /**
   * Build Unique ID for storage and retrieval.
   *
   * @param    string|array $function <p>Used for creating unique id.</p>
   *
   * @return   string|bool             <p>
   *                                   Unique ID for usage as array key or false if
   *                                   $priority === false and $function is an
   *                                   object reference, and it does not already have a unique id.
   *                                   </p>
   */
  private function _filter_build_unique_id($function)
  {
    if (is_string($function)) {
      return $function;
    }

    if (is_object($function)) {
      // Closures are currently implemented as objects
      $function = array(
          $function,
          '',
      );
    } else {
      $function = (array)$function;
    }

    if (is_object($function[0])) {
      // Object Class Calling
      return spl_object_hash($function[0]) . $function[1];
    }

    if (is_string($function[0])) {
      // Static Calling
      return $function[0] . $function[1];
    }

    return false;
  }

  /**
   * Call "All" Hook
   *
   * @param array $args
   */
  public function _call_all_hook($args)
  {
    reset($this->filters['all']);

    do {
      foreach ((array)current($this->filters['all']) as $the_) {
        if (null !== $the_['function']) {

          if (null !== $the_['include_path']) {
            /** @noinspection PhpIncludeInspection */
            include_once $the_['include_path'];
          }

          call_user_func_array($the_['function'], $args);
        }
      }
    } while (next($this->filters['all']) !== false);
  }

  /** @noinspection MagicMethodsValidityInspection */
  /**
   * @param array $args
   *
   * @deprecated use "this->_call_all_hook()"
   */
  public function __call_all_hook($args)
  {
    // <-- refactoring "__call_all_hook()" into "_call_all_hook()" is a breaking change (BC),
    // so we will only deprecate the usage

    $this->_call_all_hook($args);
  }

  /**
   * Add hook for shortcode tag.
   *
   * <p>
   * <br />
   * There can only be one hook for each shortcode. Which means that if another
   * plugin has a similar shortcode, it will override yours or yours will override
   * theirs depending on which order the plugins are included and/or ran.
   * <br />
   * <br />
   * </p>
   *
   * Simplest example of a shortcode tag using the API:
   *
   * <code>
   * // [footag foo="bar"]
   * function footag_func($atts) {
   *  return "foo = {$atts[foo]}";
   * }
   * add_shortcode('footag', 'footag_func');
   * </code>
   *
   * Example with nice attribute defaults:
   *
   * <code>
   * // [bartag foo="bar"]
   * function bartag_func($atts) {
   *  $args = shortcode_atts(array(
   *    'foo' => 'no foo',
   *    'baz' => 'default baz',
   *  ), $atts);
   *
   *  return "foo = {$args['foo']}";
   * }
   * add_shortcode('bartag', 'bartag_func');
   * </code>
   *
   * Example with enclosed content:
   *
   * <code>
   * // [baztag]content[/baztag]
   * function baztag_func($atts, $content='') {
   *  return "content = $content";
   * }
   * add_shortcode('baztag', 'baztag_func');
   * </code>
   *
   * @param string   $tag  <p>Shortcode tag to be searched in post content.</p>
   * @param callable $func <p>Hook to run when shortcode is found.</p>
   *
   * @return bool
   */
  public function add_shortcode($tag, $func)
  {
    if (is_callable($func)) {
      self::$shortcode_tags[$tag] = $func;

      return true;
    }

    return false;
  }

  /**
   * Removes hook for shortcode.
   *
   * @param string $tag <p>shortcode tag to remove hook for.</p>
   *
   * @return bool
   */
  public function remove_shortcode($tag)
  {
    if (isset(self::$shortcode_tags[$tag])) {
      unset(self::$shortcode_tags[$tag]);

      return true;
    }

    return false;
  }

  /**
   * This function is simple, it clears all of the shortcode tags by replacing the
   * shortcodes by a empty array. This is actually a very efficient method
   * for removing all shortcodes.
   *
   * @return bool
   */
  public function remove_all_shortcodes()
  {
    self::$shortcode_tags = array();

    return true;
  }

  /**
   * Whether a registered shortcode exists named $tag
   *
   * @param string $tag
   *
   * @return boolean
   */
  public function shortcode_exists($tag)
  {
    return array_key_exists($tag, self::$shortcode_tags);
  }

  /**
   * Whether the passed content contains the specified shortcode.
   *
   * @param string $content
   * @param string $tag
   *
   * @return bool
   */
  public function has_shortcode($content, $tag)
  {
    if (false === strpos($content, '[')) {
      return false;
    }

    if ($this->shortcode_exists($tag)) {
      preg_match_all('/' . $this->get_shortcode_regex() . '/s', $content, $matches, PREG_SET_ORDER);
      if (empty($matches)) {
        return false;
      }

      foreach ($matches as $shortcode) {
        if ($tag === $shortcode[2]) {
          return true;
        }

        if (!empty($shortcode[5]) && $this->has_shortcode($shortcode[5], $tag)) {
          return true;
        }
      }
    }

    return false;
  }

  /**
   * Search content for shortcodes and filter shortcodes through their hooks.
   *
   * <p>
   * <br />
   * If there are no shortcode tags defined, then the content will be returned
   * without any filtering. This might cause issues when plugins are disabled but
   * the shortcode will still show up in the post or content.
   * </p>
   *
   * @param string $content <p>Content to search for shortcodes.</p>
   *
   * @return string <p>Content with shortcodes filtered out.</p>
   */
  public function do_shortcode($content)
  {
    if (empty(self::$shortcode_tags) || !is_array(self::$shortcode_tags)) {
      return $content;
    }

    $pattern = $this->get_shortcode_regex();

    return preg_replace_callback(
        "/$pattern/s",
        array(
            $this,
            '_do_shortcode_tag',
        ),
        $content
    );
  }

  /**
   * Retrieve the shortcode regular expression for searching.
   *
   * <p>
   * <br />
   * The regular expression combines the shortcode tags in the regular expression
   * in a regex class.
   * <br /><br />
   *
   * The regular expression contains 6 different sub matches to help with parsing.
   * <br /><br />
   *
   * 1 - An extra [ to allow for escaping shortcodes with double [[]]<br />
   * 2 - The shortcode name<br />
   * 3 - The shortcode argument list<br />
   * 4 - The self closing /<br />
   * 5 - The content of a shortcode when it wraps some content.<br />
   * 6 - An extra ] to allow for escaping shortcodes with double [[]]<br />
   * </p>
   *
   * @return string The shortcode search regular expression
   */
  public function get_shortcode_regex()
  {
    $tagnames = array_keys(self::$shortcode_tags);
    $tagregexp = implode('|', array_map('preg_quote', $tagnames));

    // WARNING! Do not change this regex without changing __do_shortcode_tag() and __strip_shortcode_tag()
    // Also, see shortcode_unautop() and shortcode.js.
    return
        '\\[' // Opening bracket
        . '(\\[?)' // 1: Optional second opening bracket for escaping shortcodes: [[tag]]
        . "($tagregexp)" // 2: Shortcode name
        . '(?![\\w-])' // Not followed by word character or hyphen
        . '(' // 3: Unroll the loop: Inside the opening shortcode tag
        . '[^\\]\\/]*' // Not a closing bracket or forward slash
        . '(?:'
        . '\\/(?!\\])' // A forward slash not followed by a closing bracket
        . '[^\\]\\/]*' // Not a closing bracket or forward slash
        . ')*?'
        . ')'
        . '(?:'
        . '(\\/)' // 4: Self closing tag ...
        . '\\]' // ... and closing bracket
        . '|'
        . '\\]' // Closing bracket
        . '(?:'
        . '(' // 5: Unroll the loop: Optionally, anything between the opening and closing shortcode tags
        . '[^\\[]*+' // Not an opening bracket
        . '(?:'
        . '\\[(?!\\/\\2\\])' // An opening bracket not followed by the closing shortcode tag
        . '[^\\[]*+' // Not an opening bracket
        . ')*+'
        . ')'
        . '\\[\\/\\2\\]' // Closing shortcode tag
        . ')?'
        . ')'
        . '(\\]?)'; // 6: Optional second closing brocket for escaping shortcodes: [[tag]]
  }

  /**
   * Regular Expression callable for do_shortcode() for calling shortcode hook.
   *
   * @see self::get_shortcode_regex for details of the match array contents.
   *
   * @param array $m <p>regular expression match array</p>
   *
   * @return mixed <p><strong>false</strong> on failure</p>
   */
  private function _do_shortcode_tag($m)
  {
    // allow [[foo]] syntax for escaping a tag
    if ($m[1] == '[' && $m[6] == ']') {
      return substr($m[0], 1, -1);
    }

    $tag = $m[2];
    $attr = $this->shortcode_parse_atts($m[3]);

    // enclosing tag - extra parameter
    if (isset($m[5])) {
      return $m[1] . call_user_func(self::$shortcode_tags[$tag], $attr, $m[5], $tag) . $m[6];
    }

    // self-closing tag
    return $m[1] . call_user_func(self::$shortcode_tags[$tag], $attr, null, $tag) . $m[6];
  }

  /**
   * Retrieve all attributes from the shortcodes tag.
   *
   * <p>
   * <br />
   * The attributes list has the attribute name as the key and the value of the
   * attribute as the value in the key/value pair. This allows for easier
   * retrieval of the attributes, since all attributes have to be known.
   * </p>
   *
   * @param string $text
   *
   * @return array <p>List of attributes and their value.</p>
   */
  public function shortcode_parse_atts($text)
  {
    $atts = array();
    $pattern = '/(\w+)\s*=\s*"([^"]*)"(?:\s|$)|(\w+)\s*=\s*\'([^\']*)\'(?:\s|$)|(\w+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|(\S+)(?:\s|$)/';
    $text = preg_replace("/[\x{00a0}\x{200b}]+/u", ' ', $text);
    if (preg_match_all($pattern, $text, $match, PREG_SET_ORDER)) {
      foreach ($match as $m) {
        if (!empty($m[1])) {
          $atts[strtolower($m[1])] = stripcslashes($m[2]);
        } elseif (!empty($m[3])) {
          $atts[strtolower($m[3])] = stripcslashes($m[4]);
        } elseif (!empty($m[5])) {
          $atts[strtolower($m[5])] = stripcslashes($m[6]);
        } elseif (isset($m[7]) && $m[7] !== '') {
          $atts[] = stripcslashes($m[7]);
        } elseif (isset($m[8])) {
          $atts[] = stripcslashes($m[8]);
        }
      }
    } else {
      $atts = ltrim($text);
    }

    return $atts;
  }

  /**
   * Combine user attributes with known attributes and fill in defaults when needed.
   *
   * <p>
   * <br />
   * The pairs should be considered to be all of the attributes which are
   * supported by the caller and given as a list. The returned attributes will
   * only contain the attributes in the $pairs list.
   *
   * <br /><br />
   * If the $atts list has unsupported attributes, then they will be ignored and
   * removed from the final returned list.
   * </p>
   *
   * @param array  $pairs     <p>Entire list of supported attributes and their defaults.</p>
   * @param array  $atts      <p>User defined attributes in shortcode tag.</p>
   * @param string $shortcode <p>[optional] The name of the shortcode, provided for context to enable filtering.</p>
   *
   * @return array <p>Combined and filtered attribute list.</p>
   */
  public function shortcode_atts($pairs, $atts, $shortcode = '')
  {
    $atts = (array)$atts;
    $out = array();
    foreach ($pairs as $name => $default) {
      if (array_key_exists($name, $atts)) {
        $out[$name] = $atts[$name];
      } else {
        $out[$name] = $default;
      }
    }
    /**
     * Filter a shortcode's default attributes.
     *
     * <p>
     * <br />
     * If the third parameter of the shortcode_atts() function is present then this filter is available.
     * The third parameter, $shortcode, is the name of the shortcode.
     * </p>
     *
     * @param array $out   <p>The output array of shortcode attributes.</p>
     * @param array $pairs <p>The supported attributes and their defaults.</p>
     * @param array $atts  <p>The user defined shortcode attributes.</p>
     */
    if ($shortcode) {
      $out = $this->apply_filters(
          array(
              $this,
              "shortcode_atts_{$shortcode}",
          ), $out, $pairs, $atts
      );
    }

    return $out;
  }

  /**
   * Remove all shortcode tags from the given content.
   *
   * @param string $content <p>Content to remove shortcode tags.</p>
   *
   * @return string <p>Content without shortcode tags.</p>
   */
  public function strip_shortcodes($content)
  {

    if (empty(self::$shortcode_tags) || !is_array(self::$shortcode_tags)) {
      return $content;
    }

    $pattern = $this->get_shortcode_regex();

    return preg_replace_callback(
        "/$pattern/s",
        array(
            $this,
            '_strip_shortcode_tag',
        ),
        $content
    );
  }

  /**
   * Strip shortcode by tag.
   *
   * @param array $m
   *
   * @return string
   */
  private function _strip_shortcode_tag($m)
  {
    // allow [[foo]] syntax for escaping a tag
    if ($m[1] == '[' && $m[6] == ']') {
      return substr($m[0], 1, -1);
    }

    return $m[1] . $m[6];
  }

}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Dotenv;

use Symfony\Component\Dotenv\Exception\FormatException;
use Symfony\Component\Dotenv\Exception\FormatExceptionContext;
use Symfony\Component\Dotenv\Exception\PathException;
use Symfony\Component\Process\Exception\ExceptionInterface as ProcessException;
use Symfony\Component\Process\Process;

/**
 * Manages .env files.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
final class Dotenv
{
    const VARNAME_REGEX = '(?i:[A-Z][A-Z0-9_]*+)';
    const STATE_VARNAME = 0;
    const STATE_VALUE = 1;

    private $path;
    private $cursor;
    private $lineno;
    private $data;
    private $end;
    private $values;

    /**
     * Loads one or several .env files.
     *
     * @param string    $path  A file to load
     * @param ...string $paths A list of additional files to load
     *
     * @throws FormatException when a file has a syntax error
     * @throws PathException   when a file does not exist or is not readable
     */
    public function load($path/*, ...$paths*/)
    {
        // func_get_args() to be replaced by a variadic argument for Symfony 4.0
        foreach (\func_get_args() as $path) {
            if (!is_readable($path) || is_dir($path)) {
                throw new PathException($path);
            }

            $this->populate($this->parse(file_get_contents($path), $path));
        }
    }

    /**
     * Sets values as environment variables (via putenv, $_ENV, and $_SERVER).
     *
     * Note that existing environment variables are not overridden.
     *
     * @param array $values An array of env variables
     */
    public function populate($values)
    {
        $updateLoadedVars = false;
        $loadedVars = array_flip(explode(',', isset($_SERVER['SYMFONY_DOTENV_VARS']) ? $_SERVER['SYMFONY_DOTENV_VARS'] : (isset($_ENV['SYMFONY_DOTENV_VARS']) ? $_ENV['SYMFONY_DOTENV_VARS'] : '')));

        foreach ($values as $name => $value) {
            $notHttpName = 0 !== strpos($name, 'HTTP_');
            // don't check existence with getenv() because of thread safety issues
            if (!isset($loadedVars[$name]) && (isset($_ENV[$name]) || (isset($_SERVER[$name]) && $notHttpName))) {
                continue;
            }

            putenv("$name=$value");
            $_ENV[$name] = $value;
            if ($notHttpName) {
                $_SERVER[$name] = $value;
            }

            if (!isset($loadedVars[$name])) {
                $loadedVars[$name] = $updateLoadedVars = true;
            }
        }

        if ($updateLoadedVars) {
            unset($loadedVars['']);
            $loadedVars = implode(',', array_keys($loadedVars));
            putenv('SYMFONY_DOTENV_VARS='.$_ENV['SYMFONY_DOTENV_VARS'] = $_SERVER['SYMFONY_DOTENV_VARS'] = $loadedVars);
        }
    }

    /**
     * Parses the contents of an .env file.
     *
     * @param string $data The data to be parsed
     * @param string $path The original file name where data where stored (used for more meaningful error messages)
     *
     * @return array An array of env variables
     *
     * @throws FormatException when a file has a syntax error
     */
    public function parse($data, $path = '.env')
    {
        $this->path = $path;
        $this->data = str_replace(["\r\n", "\r"], "\n", $data);
        $this->lineno = 1;
        $this->cursor = 0;
        $this->end = \strlen($this->data);
        $state = self::STATE_VARNAME;
        $this->values = [];
        $name = '';

        $this->skipEmptyLines();

        while ($this->cursor < $this->end) {
            switch ($state) {
                case self::STATE_VARNAME:
                    $name = $this->lexVarname();
                    $state = self::STATE_VALUE;
                    break;

                case self::STATE_VALUE:
                    $this->values[$name] = $this->lexValue();
                    $state = self::STATE_VARNAME;
                    break;
            }
        }

        if (self::STATE_VALUE === $state) {
            $this->values[$name] = '';
        }

        try {
            return $this->values;
        } finally {
            $this->values = [];
            $this->data = null;
            $this->path = null;
        }
    }

    private function lexVarname()
    {
        // var name + optional export
        if (!preg_match('/(export[ \t]++)?('.self::VARNAME_REGEX.')/A', $this->data, $matches, 0, $this->cursor)) {
            throw $this->createFormatException('Invalid character in variable name');
        }
        $this->moveCursor($matches[0]);

        if ($this->cursor === $this->end || "\n" === $this->data[$this->cursor] || '#' === $this->data[$this->cursor]) {
            if ($matches[1]) {
                throw $this->createFormatException('Unable to unset an environment variable');
            }

            throw $this->createFormatException('Missing = in the environment variable declaration');
        }

        if (' ' === $this->data[$this->cursor] || "\t" === $this->data[$this->cursor]) {
            throw $this->createFormatException('Whitespace characters are not supported after the variable name');
        }

        if ('=' !== $this->data[$this->cursor]) {
            throw $this->createFormatException('Missing = in the environment variable declaration');
        }
        ++$this->cursor;

        return $matches[2];
    }

    private function lexValue()
    {
        if (preg_match('/[ \t]*+(?:#.*)?$/Am', $this->data, $matches, 0, $this->cursor)) {
            $this->moveCursor($matches[0]);
            $this->skipEmptyLines();

            return '';
        }

        if (' ' === $this->data[$this->cursor] || "\t" === $this->data[$this->cursor]) {
            throw $this->createFormatException('Whitespace are not supported before the value');
        }

        $v = '';

        do {
            if ("'" === $this->data[$this->cursor]) {
                $value = '';
                ++$this->cursor;

                while ("\n" !== $this->data[$this->cursor]) {
                    if ("'" === $this->data[$this->cursor]) {
                        break;
                    }
                    $value .= $this->data[$this->cursor];
                    ++$this->cursor;

                    if ($this->cursor === $this->end) {
                        throw $this->createFormatException('Missing quote to end the value');
                    }
                }
                if ("\n" === $this->data[$this->cursor]) {
                    throw $this->createFormatException('Missing quote to end the value');
                }
                ++$this->cursor;
                $v .= $value;
            } elseif ('"' === $this->data[$this->cursor]) {
                $value = '';
                ++$this->cursor;

                while ('"' !== $this->data[$this->cursor] || ('\\' === $this->data[$this->cursor - 1] && '\\' !== $this->data[$this->cursor - 2])) {
                    $value .= $this->data[$this->cursor];
                    ++$this->cursor;

                    if ($this->cursor === $this->end) {
                        throw $this->createFormatException('Missing quote to end the value');
                    }
                }
                if ("\n" === $this->data[$this->cursor]) {
                    throw $this->createFormatException('Missing quote to end the value');
                }
                ++$this->cursor;
                $value = str_replace(['\\"', '\r', '\n'], ['"', "\r", "\n"], $value);
                $resolvedValue = $value;
                $resolvedValue = $this->resolveVariables($resolvedValue);
                $resolvedValue = $this->resolveCommands($resolvedValue);
                $resolvedValue = str_replace('\\\\', '\\', $resolvedValue);
                $v .= $resolvedValue;
            } else {
                $value = '';
                $prevChr = $this->data[$this->cursor - 1];
                while ($this->cursor < $this->end && !\in_array($this->data[$this->cursor], ["\n", '"', "'"], true) && !((' ' === $prevChr || "\t" === $prevChr) && '#' === $this->data[$this->cursor])) {
                    if ('\\' === $this->data[$this->cursor] && isset($this->data[$this->cursor + 1]) && ('"' === $this->data[$this->cursor + 1] || "'" === $this->data[$this->cursor + 1])) {
                        ++$this->cursor;
                    }

                    $value .= $prevChr = $this->data[$this->cursor];

                    if ('$' === $this->data[$this->cursor] && isset($this->data[$this->cursor + 1]) && '(' === $this->data[$this->cursor + 1]) {
                        ++$this->cursor;
                        $value .= '('.$this->lexNestedExpression().')';
                    }

                    ++$this->cursor;
                }
                $value = rtrim($value);
                $resolvedValue = $value;
                $resolvedValue = $this->resolveVariables($resolvedValue);
                $resolvedValue = $this->resolveCommands($resolvedValue);
                $resolvedValue = str_replace('\\\\', '\\', $resolvedValue);

                if ($resolvedValue === $value && preg_match('/\s+/', $value)) {
                    throw $this->createFormatException('A value containing spaces must be surrounded by quotes');
                }

                $v .= $resolvedValue;

                if ($this->cursor < $this->end && '#' === $this->data[$this->cursor]) {
                    break;
                }
            }
        } while ($this->cursor < $this->end && "\n" !== $this->data[$this->cursor]);

        $this->skipEmptyLines();

        return $v;
    }

    private function lexNestedExpression()
    {
        ++$this->cursor;
        $value = '';

        while ("\n" !== $this->data[$this->cursor] && ')' !== $this->data[$this->cursor]) {
            $value .= $this->data[$this->cursor];

            if ('(' === $this->data[$this->cursor]) {
                $value .= $this->lexNestedExpression().')';
            }

            ++$this->cursor;

            if ($this->cursor === $this->end) {
                throw $this->createFormatException('Missing closing parenthesis.');
            }
        }

        if ("\n" === $this->data[$this->cursor]) {
            throw $this->createFormatException('Missing closing parenthesis.');
        }

        return $value;
    }

    private function skipEmptyLines()
    {
        if (preg_match('/(?:\s*+(?:#[^\n]*+)?+)++/A', $this->data, $match, 0, $this->cursor)) {
            $this->moveCursor($match[0]);
        }
    }

    private function resolveCommands($value)
    {
        if (false === strpos($value, '$')) {
            return $value;
        }

        $regex = '/
            (\\\\)?               # escaped with a backslash?
            \$
            (?<cmd>
                \(                # require opening parenthesis
                ([^()]|\g<cmd>)+  # allow any number of non-parens, or balanced parens (by nesting the <cmd> expression recursively)
                \)                # require closing paren
            )
        /x';

        return preg_replace_callback($regex, function ($matches) {
            if ('\\' === $matches[1]) {
                return substr($matches[0], 1);
            }

            if ('\\' === \DIRECTORY_SEPARATOR) {
                throw new \LogicException('Resolving commands is not supported on Windows.');
            }

            if (!class_exists(Process::class)) {
                throw new \LogicException('Resolving commands requires the Symfony Process component.');
            }

            $process = new Process('echo '.$matches[0]);
            $process->inheritEnvironmentVariables(true);
            $process->setEnv($this->values);
            try {
                $process->mustRun();
            } catch (ProcessException $e) {
                throw $this->createFormatException(sprintf('Issue expanding a command (%s)', $process->getErrorOutput()));
            }

            return preg_replace('/[\r\n]+$/', '', $process->getOutput());
        }, $value);
    }

    private function resolveVariables($value)
    {
        if (false === strpos($value, '$')) {
            return $value;
        }

        $regex = '/
            (?<!\\\\)
            (?P<backslashes>\\\\*)             # escaped with a backslash?
            \$
            (?!\()                             # no opening parenthesis
            (?P<opening_brace>\{)?             # optional brace
            (?P<name>'.self::VARNAME_REGEX.')? # var name
            (?P<closing_brace>\})?             # optional closing brace
        /x';

        $value = preg_replace_callback($regex, function ($matches) {
            // odd number of backslashes means the $ character is escaped
            if (1 === \strlen($matches['backslashes']) % 2) {
                return substr($matches[0], 1);
            }

            // unescaped $ not followed by variable name
            if (!isset($matches['name'])) {
                return $matches[0];
            }

            if ('{' === $matches['opening_brace'] && !isset($matches['closing_brace'])) {
                throw $this->createFormatException('Unclosed braces on variable expansion');
            }

            $name = $matches['name'];
            if (isset($this->values[$name])) {
                $value = $this->values[$name];
            } elseif (isset($_SERVER[$name]) && 0 !== strpos($name, 'HTTP_')) {
                $value = $_SERVER[$name];
            } elseif (isset($_ENV[$name])) {
                $value = $_ENV[$name];
            } else {
                $value = (string) getenv($name);
            }

            if (!$matches['opening_brace'] && isset($matches['closing_brace'])) {
                $value .= '}';
            }

            return $matches['backslashes'].$value;
        }, $value);

        return $value;
    }

    private function moveCursor($text)
    {
        $this->cursor += \strlen($text);
        $this->lineno += substr_count($text, "\n");
    }

    private function createFormatException($message)
    {
        return new FormatException($message, new FormatExceptionContext($this->data, $this->path, $this->lineno, $this->cursor));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Dotenv\Exception;

/**
 * Thrown when a file has a syntax error.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
final class FormatException extends \LogicException implements ExceptionInterface
{
    private $context;

    public function __construct($message, FormatExceptionContext $context, $code = 0, \Exception $previous = null)
    {
        $this->context = $context;

        parent::__construct(sprintf("%s in \"%s\" at line %d.\n%s", $message, $context->getPath(), $context->getLineno(), $context->getDetails()), $code, $previous);
    }

    public function getContext()
    {
        return $this->context;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Dotenv\Exception;

/**
 * Thrown when a file does not exist or is not readable.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
final class PathException extends \RuntimeException implements ExceptionInterface
{
    public function __construct($path, $code = 0, \Exception $previous = null)
    {
        parent::__construct(sprintf('Unable to read the "%s" environment file.', $path), $code, $previous);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Dotenv\Exception;

/**
 * Interface for exceptions.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
interface ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Dotenv\Exception;

/**
 * @author Fabien Potencier <fabien@symfony.com>
 */
final class FormatExceptionContext
{
    private $data;
    private $path;
    private $lineno;
    private $cursor;

    public function __construct($data, $path, $lineno, $cursor)
    {
        $this->data = $data;
        $this->path = $path;
        $this->lineno = $lineno;
        $this->cursor = $cursor;
    }

    public function getPath()
    {
        return $this->path;
    }

    public function getLineno()
    {
        return $this->lineno;
    }

    public function getDetails()
    {
        $before = str_replace("\n", '\n', substr($this->data, max(0, $this->cursor - 20), min(20, $this->cursor)));
        $after = str_replace("\n", '\n', substr($this->data, $this->cursor, 20));

        return '...'.$before.$after."...\n".str_repeat(' ', \strlen($before) + 2).'^ line '.$this->lineno.' offset '.$this->cursor;
    }
}
{
    "name": "symfony/dotenv",
    "type": "library",
    "description": "Registers environment variables from a .env file",
    "keywords": ["environment", "env", "dotenv"],
    "homepage": "https://symfony.com",
    "license" : "MIT",
    "authors": [
        {
            "name": "Fabien Potencier",
            "email": "fabien@symfony.com"
        },
        {
            "name": "Symfony Community",
            "homepage": "https://symfony.com/contributors"
        }
    ],
    "require": {
        "php": "^5.5.9|>=7.0.8"
    },
    "require-dev": {
        "symfony/process": "~3.2|~4.0"
    },
    "autoload": {
        "psr-4": { "Symfony\\Component\\Dotenv\\": "" },
        "exclude-from-classmap": [
            "/Tests/"
        ]
    },
    "minimum-stability": "dev",
    "extra": {
        "branch-alias": {
            "dev-master": "3.4-dev"
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Symfony\Polyfill\Ctype as p;

if (!function_exists('ctype_alnum')) {
    function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); }
    function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); }
    function ctype_cntrl($text) { return p\Ctype::ctype_cntrl($text); }
    function ctype_digit($text) { return p\Ctype::ctype_digit($text); }
    function ctype_graph($text) { return p\Ctype::ctype_graph($text); }
    function ctype_lower($text) { return p\Ctype::ctype_lower($text); }
    function ctype_print($text) { return p\Ctype::ctype_print($text); }
    function ctype_punct($text) { return p\Ctype::ctype_punct($text); }
    function ctype_space($text) { return p\Ctype::ctype_space($text); }
    function ctype_upper($text) { return p\Ctype::ctype_upper($text); }
    function ctype_xdigit($text) { return p\Ctype::ctype_xdigit($text); }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Polyfill\Ctype;

/**
 * Ctype implementation through regex.
 *
 * @internal
 *
 * @author Gert de Pagter <BackEndTea@gmail.com>
 */
final class Ctype
{
    /**
     * Returns TRUE if every character in text is either a letter or a digit, FALSE otherwise.
     *
     * @see https://php.net/ctype-alnum
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_alnum($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z0-9]/', $text);
    }

    /**
     * Returns TRUE if every character in text is a letter, FALSE otherwise.
     *
     * @see https://php.net/ctype-alpha
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_alpha($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z]/', $text);
    }

    /**
     * Returns TRUE if every character in text is a control character from the current locale, FALSE otherwise.
     *
     * @see https://php.net/ctype-cntrl
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_cntrl($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^\x00-\x1f\x7f]/', $text);
    }

    /**
     * Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise.
     *
     * @see https://php.net/ctype-digit
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_digit($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^0-9]/', $text);
    }

    /**
     * Returns TRUE if every character in text is printable and actually creates visible output (no white space), FALSE otherwise.
     *
     * @see https://php.net/ctype-graph
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_graph($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^!-~]/', $text);
    }

    /**
     * Returns TRUE if every character in text is a lowercase letter.
     *
     * @see https://php.net/ctype-lower
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_lower($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^a-z]/', $text);
    }

    /**
     * Returns TRUE if every character in text will actually create output (including blanks). Returns FALSE if text contains control characters or characters that do not have any output or control function at all.
     *
     * @see https://php.net/ctype-print
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_print($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^ -~]/', $text);
    }

    /**
     * Returns TRUE if every character in text is printable, but neither letter, digit or blank, FALSE otherwise.
     *
     * @see https://php.net/ctype-punct
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_punct($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^!-\/\:-@\[-`\{-~]/', $text);
    }

    /**
     * Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. Besides the blank character this also includes tab, vertical tab, line feed, carriage return and form feed characters.
     *
     * @see https://php.net/ctype-space
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_space($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^\s]/', $text);
    }

    /**
     * Returns TRUE if every character in text is an uppercase letter.
     *
     * @see https://php.net/ctype-upper
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_upper($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^A-Z]/', $text);
    }

    /**
     * Returns TRUE if every character in text is a hexadecimal 'digit', that is a decimal digit or a character from [A-Fa-f] , FALSE otherwise.
     *
     * @see https://php.net/ctype-xdigit
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_xdigit($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^A-Fa-f0-9]/', $text);
    }

    /**
     * Converts integers to their char versions according to normal ctype behaviour, if needed.
     *
     * If an integer between -128 and 255 inclusive is provided,
     * it is interpreted as the ASCII value of a single character
     * (negative values have 256 added in order to allow characters in the Extended ASCII range).
     * Any other integer is interpreted as a string containing the decimal digits of the integer.
     *
     * @param string|int $int
     *
     * @return mixed
     */
    private static function convert_int_to_char_for_ctype($int)
    {
        if (!\is_int($int)) {
            return $int;
        }

        if ($int < -128 || $int > 255) {
            return (string) $int;
        }

        if ($int < 0) {
            $int += 256;
        }

        return \chr($int);
    }
}
{
    "name": "symfony/polyfill-ctype",
    "type": "library",
    "description": "Symfony polyfill for ctype functions",
    "keywords": ["polyfill", "compatibility", "portable", "ctype"],
    "homepage": "https://symfony.com",
    "license": "MIT",
    "authors": [
        {
            "name": "Gert de Pagter",
            "email": "BackEndTea@gmail.com"
        },
        {
            "name": "Symfony Community",
            "homepage": "https://symfony.com/contributors"
        }
    ],
    "require": {
        "php": ">=5.3.3"
    },
    "autoload": {
        "psr-4": { "Symfony\\Polyfill\\Ctype\\": "" },
        "files": [ "bootstrap.php" ]
    },
    "suggest": {
        "ext-ctype": "For best performance"
    },
    "minimum-stability": "dev",
    "extra": {
        "branch-alias": {
            "dev-master": "1.11-dev"
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config;

use Symfony\Component\Config\Resource\ResourceInterface;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Filesystem\Filesystem;

/**
 * ResourceCheckerConfigCache uses instances of ResourceCheckerInterface
 * to check whether cached data is still fresh.
 *
 * @author Matthias Pigulla <mp@webfactory.de>
 */
class ResourceCheckerConfigCache implements ConfigCacheInterface
{
    /**
     * @var string
     */
    private $file;

    /**
     * @var iterable|ResourceCheckerInterface[]
     */
    private $resourceCheckers;

    /**
     * @param string                              $file             The absolute cache path
     * @param iterable|ResourceCheckerInterface[] $resourceCheckers The ResourceCheckers to use for the freshness check
     */
    public function __construct(string $file, iterable $resourceCheckers = [])
    {
        $this->file = $file;
        $this->resourceCheckers = $resourceCheckers;
    }

    /**
     * {@inheritdoc}
     */
    public function getPath()
    {
        return $this->file;
    }

    /**
     * Checks if the cache is still fresh.
     *
     * This implementation will make a decision solely based on the ResourceCheckers
     * passed in the constructor.
     *
     * The first ResourceChecker that supports a given resource is considered authoritative.
     * Resources with no matching ResourceChecker will silently be ignored and considered fresh.
     *
     * @return bool true if the cache is fresh, false otherwise
     */
    public function isFresh()
    {
        if (!is_file($this->file)) {
            return false;
        }

        if ($this->resourceCheckers instanceof \Traversable && !$this->resourceCheckers instanceof \Countable) {
            $this->resourceCheckers = iterator_to_array($this->resourceCheckers);
        }

        if (!\count($this->resourceCheckers)) {
            return true; // shortcut - if we don't have any checkers we don't need to bother with the meta file at all
        }

        $metadata = $this->getMetaFile();

        if (!is_file($metadata)) {
            return false;
        }

        $meta = $this->safelyUnserialize($metadata);

        if (false === $meta) {
            return false;
        }

        $time = filemtime($this->file);

        foreach ($meta as $resource) {
            /* @var ResourceInterface $resource */
            foreach ($this->resourceCheckers as $checker) {
                if (!$checker->supports($resource)) {
                    continue; // next checker
                }
                if ($checker->isFresh($resource, $time)) {
                    break; // no need to further check this resource
                }

                return false; // cache is stale
            }
            // no suitable checker found, ignore this resource
        }

        return true;
    }

    /**
     * Writes cache.
     *
     * @param string              $content  The content to write in the cache
     * @param ResourceInterface[] $metadata An array of metadata
     *
     * @throws \RuntimeException When cache file can't be written
     */
    public function write($content, array $metadata = null)
    {
        $mode = 0666;
        $umask = umask();
        $filesystem = new Filesystem();
        $filesystem->dumpFile($this->file, $content);
        try {
            $filesystem->chmod($this->file, $mode, $umask);
        } catch (IOException $e) {
            // discard chmod failure (some filesystem may not support it)
        }

        if (null !== $metadata) {
            $filesystem->dumpFile($this->getMetaFile(), serialize($metadata));
            try {
                $filesystem->chmod($this->getMetaFile(), $mode, $umask);
            } catch (IOException $e) {
                // discard chmod failure (some filesystem may not support it)
            }
        }

        if (\function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN)) {
            @opcache_invalidate($this->file, true);
        }
    }

    /**
     * Gets the meta file path.
     *
     * @return string The meta file path
     */
    private function getMetaFile()
    {
        return $this->file.'.meta';
    }

    private function safelyUnserialize($file)
    {
        $e = null;
        $meta = false;
        $content = file_get_contents($file);
        $signalingException = new \UnexpectedValueException();
        $prevUnserializeHandler = ini_set('unserialize_callback_func', '');
        $prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) use (&$prevErrorHandler, $signalingException) {
            if (__FILE__ === $file) {
                throw $signalingException;
            }

            return $prevErrorHandler ? $prevErrorHandler($type, $msg, $file, $line, $context) : false;
        });

        try {
            $meta = unserialize($content);
        } catch (\Throwable $e) {
            if ($e !== $signalingException) {
                throw $e;
            }
        } finally {
            restore_error_handler();
            ini_set('unserialize_callback_func', $prevUnserializeHandler);
        }

        return $meta;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Util;

use Symfony\Component\Config\Util\Exception\InvalidXmlException;
use Symfony\Component\Config\Util\Exception\XmlParsingException;

/**
 * XMLUtils is a bunch of utility methods to XML operations.
 *
 * This class contains static methods only and is not meant to be instantiated.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Martin HasoÅˆ <martin.hason@gmail.com>
 * @author Ole RÃ¶ÃŸner <ole@roessner.it>
 */
class XmlUtils
{
    /**
     * This class should not be instantiated.
     */
    private function __construct()
    {
    }

    /**
     * Parses an XML string.
     *
     * @param string               $content          An XML string
     * @param string|callable|null $schemaOrCallable An XSD schema file path, a callable, or null to disable validation
     *
     * @return \DOMDocument
     *
     * @throws XmlParsingException When parsing of XML file returns error
     * @throws InvalidXmlException When parsing of XML with schema or callable produces any errors unrelated to the XML parsing itself
     * @throws \RuntimeException   When DOM extension is missing
     */
    public static function parse($content, $schemaOrCallable = null)
    {
        if (!\extension_loaded('dom')) {
            throw new \LogicException('Extension DOM is required.');
        }

        $internalErrors = libxml_use_internal_errors(true);
        $disableEntities = libxml_disable_entity_loader(true);
        libxml_clear_errors();

        $dom = new \DOMDocument();
        $dom->validateOnParse = true;
        if (!$dom->loadXML($content, LIBXML_NONET | (\defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0))) {
            libxml_disable_entity_loader($disableEntities);

            throw new XmlParsingException(implode("\n", static::getXmlErrors($internalErrors)));
        }

        $dom->normalizeDocument();

        libxml_use_internal_errors($internalErrors);
        libxml_disable_entity_loader($disableEntities);

        foreach ($dom->childNodes as $child) {
            if (XML_DOCUMENT_TYPE_NODE === $child->nodeType) {
                throw new XmlParsingException('Document types are not allowed.');
            }
        }

        if (null !== $schemaOrCallable) {
            $internalErrors = libxml_use_internal_errors(true);
            libxml_clear_errors();

            $e = null;
            if (\is_callable($schemaOrCallable)) {
                try {
                    $valid = $schemaOrCallable($dom, $internalErrors);
                } catch (\Exception $e) {
                    $valid = false;
                }
            } elseif (!\is_array($schemaOrCallable) && is_file((string) $schemaOrCallable)) {
                $schemaSource = file_get_contents((string) $schemaOrCallable);
                $valid = @$dom->schemaValidateSource($schemaSource);
            } else {
                libxml_use_internal_errors($internalErrors);

                throw new XmlParsingException('The schemaOrCallable argument has to be a valid path to XSD file or callable.');
            }

            if (!$valid) {
                $messages = static::getXmlErrors($internalErrors);
                if (empty($messages)) {
                    throw new InvalidXmlException('The XML is not valid.', 0, $e);
                }
                throw new XmlParsingException(implode("\n", $messages), 0, $e);
            }
        }

        libxml_clear_errors();
        libxml_use_internal_errors($internalErrors);

        return $dom;
    }

    /**
     * Loads an XML file.
     *
     * @param string               $file             An XML file path
     * @param string|callable|null $schemaOrCallable An XSD schema file path, a callable, or null to disable validation
     *
     * @return \DOMDocument
     *
     * @throws \InvalidArgumentException When loading of XML file returns error
     * @throws XmlParsingException       When XML parsing returns any errors
     * @throws \RuntimeException         When DOM extension is missing
     */
    public static function loadFile($file, $schemaOrCallable = null)
    {
        $content = @file_get_contents($file);
        if ('' === trim($content)) {
            throw new \InvalidArgumentException(sprintf('File %s does not contain valid XML, it is empty.', $file));
        }

        try {
            return static::parse($content, $schemaOrCallable);
        } catch (InvalidXmlException $e) {
            throw new XmlParsingException(sprintf('The XML file "%s" is not valid.', $file), 0, $e->getPrevious());
        }
    }

    /**
     * Converts a \DOMElement object to a PHP array.
     *
     * The following rules applies during the conversion:
     *
     *  * Each tag is converted to a key value or an array
     *    if there is more than one "value"
     *
     *  * The content of a tag is set under a "value" key (<foo>bar</foo>)
     *    if the tag also has some nested tags
     *
     *  * The attributes are converted to keys (<foo foo="bar"/>)
     *
     *  * The nested-tags are converted to keys (<foo><foo>bar</foo></foo>)
     *
     * @param \DOMElement $element     A \DOMElement instance
     * @param bool        $checkPrefix Check prefix in an element or an attribute name
     *
     * @return array A PHP array
     */
    public static function convertDomElementToArray(\DOMElement $element, $checkPrefix = true)
    {
        $prefix = (string) $element->prefix;
        $empty = true;
        $config = [];
        foreach ($element->attributes as $name => $node) {
            if ($checkPrefix && !\in_array((string) $node->prefix, ['', $prefix], true)) {
                continue;
            }
            $config[$name] = static::phpize($node->value);
            $empty = false;
        }

        $nodeValue = false;
        foreach ($element->childNodes as $node) {
            if ($node instanceof \DOMText) {
                if ('' !== trim($node->nodeValue)) {
                    $nodeValue = trim($node->nodeValue);
                    $empty = false;
                }
            } elseif ($checkPrefix && $prefix != (string) $node->prefix) {
                continue;
            } elseif (!$node instanceof \DOMComment) {
                $value = static::convertDomElementToArray($node, $checkPrefix);

                $key = $node->localName;
                if (isset($config[$key])) {
                    if (!\is_array($config[$key]) || !\is_int(key($config[$key]))) {
                        $config[$key] = [$config[$key]];
                    }
                    $config[$key][] = $value;
                } else {
                    $config[$key] = $value;
                }

                $empty = false;
            }
        }

        if (false !== $nodeValue) {
            $value = static::phpize($nodeValue);
            if (\count($config)) {
                $config['value'] = $value;
            } else {
                $config = $value;
            }
        }

        return !$empty ? $config : null;
    }

    /**
     * Converts an xml value to a PHP type.
     *
     * @param mixed $value
     *
     * @return mixed
     */
    public static function phpize($value)
    {
        $value = (string) $value;
        $lowercaseValue = strtolower($value);

        switch (true) {
            case 'null' === $lowercaseValue:
                return;
            case ctype_digit($value):
                $raw = $value;
                $cast = (int) $value;

                return '0' == $value[0] ? octdec($value) : (((string) $raw === (string) $cast) ? $cast : $raw);
            case isset($value[1]) && '-' === $value[0] && ctype_digit(substr($value, 1)):
                $raw = $value;
                $cast = (int) $value;

                return '0' == $value[1] ? octdec($value) : (((string) $raw === (string) $cast) ? $cast : $raw);
            case 'true' === $lowercaseValue:
                return true;
            case 'false' === $lowercaseValue:
                return false;
            case isset($value[1]) && '0b' == $value[0].$value[1]:
                return bindec($value);
            case is_numeric($value):
                return '0x' === $value[0].$value[1] ? hexdec($value) : (float) $value;
            case preg_match('/^0x[0-9a-f]++$/i', $value):
                return hexdec($value);
            case preg_match('/^[+-]?[0-9]+(\.[0-9]+)?$/', $value):
                return (float) $value;
            default:
                return $value;
        }
    }

    protected static function getXmlErrors($internalErrors)
    {
        $errors = [];
        foreach (libxml_get_errors() as $error) {
            $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)',
                LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR',
                $error->code,
                trim($error->message),
                $error->file ?: 'n/a',
                $error->line,
                $error->column
            );
        }

        libxml_clear_errors();
        libxml_use_internal_errors($internalErrors);

        return $errors;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Util\Exception;

/**
 * Exception class for when XML cannot be parsed properly.
 *
 * @author Ole RÃ¶ÃŸner <ole@roessner.it>
 */
class XmlParsingException extends \InvalidArgumentException
{
}
<?php
/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Util\Exception;

/**
 * Exception class for when XML parsing with an XSD schema file path or a callable validator produces errors unrelated
 * to the actual XML parsing.
 *
 * @author Ole RÃ¶ÃŸner <ole@roessner.it>
 */
class InvalidXmlException extends XmlParsingException
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config;

use Symfony\Component\Config\Resource\ResourceInterface;

/**
 * Interface for ConfigCache.
 *
 * @author Matthias Pigulla <mp@webfactory.de>
 */
interface ConfigCacheInterface
{
    /**
     * Gets the cache file path.
     *
     * @return string The cache file path
     */
    public function getPath();

    /**
     * Checks if the cache is still fresh.
     *
     * This check should take the metadata passed to the write() method into consideration.
     *
     * @return bool Whether the cache is still fresh
     */
    public function isFresh();

    /**
     * Writes the given content into the cache file. Metadata will be stored
     * independently and can be used to check cache freshness at a later time.
     *
     * @param string                   $content  The content to write into the cache
     * @param ResourceInterface[]|null $metadata An array of ResourceInterface instances
     *
     * @throws \RuntimeException When the cache file cannot be written
     */
    public function write($content, array $metadata = null);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config;

use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException;

/**
 * @author Fabien Potencier <fabien@symfony.com>
 */
interface FileLocatorInterface
{
    /**
     * Returns a full path for a given file name.
     *
     * @param string      $name        The file name to locate
     * @param string|null $currentPath The current path
     * @param bool        $first       Whether to return the first occurrence or an array of filenames
     *
     * @return string|array The full path to the file or an array of file paths
     *
     * @throws \InvalidArgumentException        If $name is empty
     * @throws FileLocatorFileNotFoundException If a file is not found
     */
    public function locate($name, $currentPath = null, $first = true);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config;

use Symfony\Component\Config\Resource\SelfCheckingResourceChecker;

/**
 * ConfigCache caches arbitrary content in files on disk.
 *
 * When in debug mode, those metadata resources that implement
 * \Symfony\Component\Config\Resource\SelfCheckingResourceInterface will
 * be used to check cache freshness.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Matthias Pigulla <mp@webfactory.de>
 */
class ConfigCache extends ResourceCheckerConfigCache
{
    private $debug;

    /**
     * @param string $file  The absolute cache path
     * @param bool   $debug Whether debugging is enabled or not
     */
    public function __construct(string $file, bool $debug)
    {
        $this->debug = $debug;

        $checkers = [];
        if (true === $this->debug) {
            $checkers = [new SelfCheckingResourceChecker()];
        }

        parent::__construct($file, $checkers);
    }

    /**
     * Checks if the cache is still fresh.
     *
     * This implementation always returns true when debug is off and the
     * cache file exists.
     *
     * @return bool true if the cache is fresh, false otherwise
     */
    public function isFresh()
    {
        if (!$this->debug && is_file($this->getPath())) {
            return true;
        }

        return parent::isFresh();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config;

/**
 * Interface for a ConfigCache factory. This factory creates
 * an instance of ConfigCacheInterface and initializes the
 * cache if necessary.
 *
 * @author Matthias Pigulla <mp@webfactory.de>
 */
interface ConfigCacheFactoryInterface
{
    /**
     * Creates a cache instance and (re-)initializes it if necessary.
     *
     * @param string   $file     The absolute cache file path
     * @param callable $callable The callable to be executed when the cache needs to be filled (i. e. is not fresh). The cache will be passed as the only parameter to this callback
     *
     * @return ConfigCacheInterface The cache instance
     */
    public function cache($file, $callable);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config;

use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException;

/**
 * FileLocator uses an array of pre-defined paths to find files.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class FileLocator implements FileLocatorInterface
{
    protected $paths;

    /**
     * @param string|string[] $paths A path or an array of paths where to look for resources
     */
    public function __construct($paths = [])
    {
        $this->paths = (array) $paths;
    }

    /**
     * {@inheritdoc}
     */
    public function locate($name, $currentPath = null, $first = true)
    {
        if ('' == $name) {
            throw new \InvalidArgumentException('An empty file name is not valid to be located.');
        }

        if ($this->isAbsolutePath($name)) {
            if (!file_exists($name)) {
                throw new FileLocatorFileNotFoundException(sprintf('The file "%s" does not exist.', $name), 0, null, [$name]);
            }

            return $name;
        }

        $paths = $this->paths;

        if (null !== $currentPath) {
            array_unshift($paths, $currentPath);
        }

        $paths = array_unique($paths);
        $filepaths = $notfound = [];

        foreach ($paths as $path) {
            if (@file_exists($file = $path.\DIRECTORY_SEPARATOR.$name)) {
                if (true === $first) {
                    return $file;
                }
                $filepaths[] = $file;
            } else {
                $notfound[] = $file;
            }
        }

        if (!$filepaths) {
            throw new FileLocatorFileNotFoundException(sprintf('The file "%s" does not exist (in: %s).', $name, implode(', ', $paths)), 0, null, $notfound);
        }

        return $filepaths;
    }

    /**
     * Returns whether the file path is an absolute path.
     *
     * @param string $file A file path
     *
     * @return bool
     */
    private function isAbsolutePath($file)
    {
        if ('/' === $file[0] || '\\' === $file[0]
            || (\strlen($file) > 3 && ctype_alpha($file[0])
                && ':' === $file[1]
                && ('\\' === $file[2] || '/' === $file[2])
            )
            || null !== parse_url($file, PHP_URL_SCHEME)
        ) {
            return true;
        }

        return false;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config;

use Symfony\Component\Config\Resource\ResourceInterface;

/**
 * Interface for ResourceCheckers.
 *
 * When a ResourceCheckerConfigCache instance is checked for freshness, all its associated
 * metadata resources are passed to ResourceCheckers. The ResourceCheckers
 * can then inspect the resources and decide whether the cache can be considered
 * fresh or not.
 *
 * @author Matthias Pigulla <mp@webfactory.de>
 * @author Benjamin Klotz <bk@webfactory.de>
 */
interface ResourceCheckerInterface
{
    /**
     * Queries the ResourceChecker whether it can validate a given
     * resource or not.
     *
     * @param ResourceInterface $metadata The resource to be checked for freshness
     *
     * @return bool True if the ResourceChecker can handle this resource type, false if not
     */
    public function supports(ResourceInterface $metadata);

    /**
     * Validates the resource.
     *
     * @param ResourceInterface $resource  The resource to be validated
     * @param int               $timestamp The timestamp at which the cache associated with this resource was created
     *
     * @return bool True if the resource has not changed since the given timestamp, false otherwise
     */
    public function isFresh(ResourceInterface $resource, $timestamp);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Loader;

use Symfony\Component\Config\Exception\LoaderLoadException;

/**
 * Loader is the abstract class used by all built-in loaders.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
abstract class Loader implements LoaderInterface
{
    protected $resolver;

    /**
     * {@inheritdoc}
     */
    public function getResolver()
    {
        return $this->resolver;
    }

    /**
     * {@inheritdoc}
     */
    public function setResolver(LoaderResolverInterface $resolver)
    {
        $this->resolver = $resolver;
    }

    /**
     * Imports a resource.
     *
     * @param mixed       $resource A resource
     * @param string|null $type     The resource type or null if unknown
     *
     * @return mixed
     */
    public function import($resource, $type = null)
    {
        return $this->resolve($resource, $type)->load($resource, $type);
    }

    /**
     * Finds a loader able to load an imported resource.
     *
     * @param mixed       $resource A resource
     * @param string|null $type     The resource type or null if unknown
     *
     * @return $this|LoaderInterface
     *
     * @throws LoaderLoadException If no loader is found
     */
    public function resolve($resource, $type = null)
    {
        if ($this->supports($resource, $type)) {
            return $this;
        }

        $loader = null === $this->resolver ? false : $this->resolver->resolve($resource, $type);

        if (false === $loader) {
            throw new LoaderLoadException($resource, null, null, null, $type);
        }

        return $loader;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Loader;

use Symfony\Component\Config\Exception\FileLoaderImportCircularReferenceException;
use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException;
use Symfony\Component\Config\Exception\LoaderLoadException;
use Symfony\Component\Config\FileLocatorInterface;
use Symfony\Component\Config\Resource\FileExistenceResource;
use Symfony\Component\Config\Resource\GlobResource;

/**
 * FileLoader is the abstract class used by all built-in loaders that are file based.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
abstract class FileLoader extends Loader
{
    protected static $loading = [];

    protected $locator;

    private $currentDir;

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

    /**
     * Sets the current directory.
     *
     * @param string $dir
     */
    public function setCurrentDir($dir)
    {
        $this->currentDir = $dir;
    }

    /**
     * Returns the file locator used by this loader.
     *
     * @return FileLocatorInterface
     */
    public function getLocator()
    {
        return $this->locator;
    }

    /**
     * Imports a resource.
     *
     * @param mixed       $resource       A Resource
     * @param string|null $type           The resource type or null if unknown
     * @param bool        $ignoreErrors   Whether to ignore import errors or not
     * @param string|null $sourceResource The original resource importing the new resource
     *
     * @return mixed
     *
     * @throws LoaderLoadException
     * @throws FileLoaderImportCircularReferenceException
     * @throws FileLocatorFileNotFoundException
     */
    public function import($resource, $type = null, $ignoreErrors = false, $sourceResource = null)
    {
        if (\is_string($resource) && \strlen($resource) !== $i = strcspn($resource, '*?{[')) {
            $ret = [];
            $isSubpath = 0 !== $i && false !== strpos(substr($resource, 0, $i), '/');
            foreach ($this->glob($resource, false, $_, $ignoreErrors || !$isSubpath) as $path => $info) {
                if (null !== $res = $this->doImport($path, $type, $ignoreErrors, $sourceResource)) {
                    $ret[] = $res;
                }
                $isSubpath = true;
            }

            if ($isSubpath) {
                return isset($ret[1]) ? $ret : (isset($ret[0]) ? $ret[0] : null);
            }
        }

        return $this->doImport($resource, $type, $ignoreErrors, $sourceResource);
    }

    /**
     * @internal
     */
    protected function glob(string $pattern, bool $recursive, &$resource = null, bool $ignoreErrors = false, bool $forExclusion = false, array $excluded = [])
    {
        if (\strlen($pattern) === $i = strcspn($pattern, '*?{[')) {
            $prefix = $pattern;
            $pattern = '';
        } elseif (0 === $i || false === strpos(substr($pattern, 0, $i), '/')) {
            $prefix = '.';
            $pattern = '/'.$pattern;
        } else {
            $prefix = \dirname(substr($pattern, 0, 1 + $i));
            $pattern = substr($pattern, \strlen($prefix));
        }

        try {
            $prefix = $this->locator->locate($prefix, $this->currentDir, true);
        } catch (FileLocatorFileNotFoundException $e) {
            if (!$ignoreErrors) {
                throw $e;
            }

            $resource = [];
            foreach ($e->getPaths() as $path) {
                $resource[] = new FileExistenceResource($path);
            }

            return;
        }
        $resource = new GlobResource($prefix, $pattern, $recursive, $forExclusion, $excluded);

        yield from $resource;
    }

    private function doImport($resource, $type = null, bool $ignoreErrors = false, $sourceResource = null)
    {
        try {
            $loader = $this->resolve($resource, $type);

            if ($loader instanceof self && null !== $this->currentDir) {
                $resource = $loader->getLocator()->locate($resource, $this->currentDir, false);
            }

            $resources = \is_array($resource) ? $resource : [$resource];
            for ($i = 0; $i < $resourcesCount = \count($resources); ++$i) {
                if (isset(self::$loading[$resources[$i]])) {
                    if ($i == $resourcesCount - 1) {
                        throw new FileLoaderImportCircularReferenceException(array_keys(self::$loading));
                    }
                } else {
                    $resource = $resources[$i];
                    break;
                }
            }
            self::$loading[$resource] = true;

            try {
                $ret = $loader->load($resource, $type);
            } finally {
                unset(self::$loading[$resource]);
            }

            return $ret;
        } catch (FileLoaderImportCircularReferenceException $e) {
            throw $e;
        } catch (\Exception $e) {
            if (!$ignoreErrors) {
                // prevent embedded imports from nesting multiple exceptions
                if ($e instanceof LoaderLoadException) {
                    throw $e;
                }

                throw new LoaderLoadException($resource, $sourceResource, null, $e, $type);
            }
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Loader;

/**
 * LoaderInterface is the interface implemented by all loader classes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
interface LoaderInterface
{
    /**
     * Loads a resource.
     *
     * @param mixed       $resource The resource
     * @param string|null $type     The resource type or null if unknown
     *
     * @throws \Exception If something went wrong
     */
    public function load($resource, $type = null);

    /**
     * Returns whether this class supports the given resource.
     *
     * @param mixed       $resource A resource
     * @param string|null $type     The resource type or null if unknown
     *
     * @return bool True if this class supports the given resource, false otherwise
     */
    public function supports($resource, $type = null);

    /**
     * Gets the loader resolver.
     *
     * @return LoaderResolverInterface A LoaderResolverInterface instance
     */
    public function getResolver();

    /**
     * Sets the loader resolver.
     */
    public function setResolver(LoaderResolverInterface $resolver);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Loader;

/**
 * LoaderResolverInterface selects a loader for a given resource.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
interface LoaderResolverInterface
{
    /**
     * Returns a loader able to load the resource.
     *
     * @param mixed       $resource A resource
     * @param string|null $type     The resource type or null if unknown
     *
     * @return LoaderInterface|false The loader or false if none is able to load the resource
     */
    public function resolve($resource, $type = null);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Loader;

/**
 * LoaderResolver selects a loader for a given resource.
 *
 * A resource can be anything (e.g. a full path to a config file or a Closure).
 * Each loader determines whether it can load a resource and how.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class LoaderResolver implements LoaderResolverInterface
{
    /**
     * @var LoaderInterface[] An array of LoaderInterface objects
     */
    private $loaders = [];

    /**
     * @param LoaderInterface[] $loaders An array of loaders
     */
    public function __construct(array $loaders = [])
    {
        foreach ($loaders as $loader) {
            $this->addLoader($loader);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function resolve($resource, $type = null)
    {
        foreach ($this->loaders as $loader) {
            if ($loader->supports($resource, $type)) {
                return $loader;
            }
        }

        return false;
    }

    public function addLoader(LoaderInterface $loader)
    {
        $this->loaders[] = $loader;
        $loader->setResolver($this);
    }

    /**
     * Returns the registered loaders.
     *
     * @return LoaderInterface[] An array of LoaderInterface instances
     */
    public function getLoaders()
    {
        return $this->loaders;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Loader;

use Symfony\Component\Config\Exception\LoaderLoadException;

/**
 * DelegatingLoader delegates loading to other loaders using a loader resolver.
 *
 * This loader acts as an array of LoaderInterface objects - each having
 * a chance to load a given resource (handled by the resolver)
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class DelegatingLoader extends Loader
{
    public function __construct(LoaderResolverInterface $resolver)
    {
        $this->resolver = $resolver;
    }

    /**
     * {@inheritdoc}
     */
    public function load($resource, $type = null)
    {
        if (false === $loader = $this->resolver->resolve($resource, $type)) {
            throw new LoaderLoadException($resource, null, null, null, $type);
        }

        return $loader->load($resource, $type);
    }

    /**
     * {@inheritdoc}
     */
    public function supports($resource, $type = null)
    {
        return false !== $this->resolver->resolve($resource, $type);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Loader;

/**
 * GlobFileLoader loads files from a glob pattern.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class GlobFileLoader extends FileLoader
{
    /**
     * {@inheritdoc}
     */
    public function load($resource, $type = null)
    {
        return $this->import($resource);
    }

    /**
     * {@inheritdoc}
     */
    public function supports($resource, $type = null)
    {
        return 'glob' === $type;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition;

use Symfony\Component\Config\Definition\Exception\InvalidTypeException;

/**
 * This node represents a float value in the config tree.
 *
 * @author Jeanmonod David <david.jeanmonod@gmail.com>
 */
class FloatNode extends NumericNode
{
    /**
     * {@inheritdoc}
     */
    protected function validateType($value)
    {
        // Integers are also accepted, we just cast them
        if (\is_int($value)) {
            $value = (float) $value;
        }

        if (!\is_float($value)) {
            $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected float, but got %s.', $this->getPath(), \gettype($value)));
            if ($hint = $this->getInfo()) {
                $ex->addHint($hint);
            }
            $ex->setPath($this->getPath());

            throw $ex;
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function getValidPlaceholderTypes(): array
    {
        return ['float'];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition;

use Symfony\Component\Config\Definition\Exception\Exception;
use Symfony\Component\Config\Definition\Exception\ForbiddenOverwriteException;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
use Symfony\Component\Config\Definition\Exception\UnsetKeyException;

/**
 * The base node class.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
abstract class BaseNode implements NodeInterface
{
    const DEFAULT_PATH_SEPARATOR = '.';

    private static $placeholderUniquePrefix;
    private static $placeholders = [];

    protected $name;
    protected $parent;
    protected $normalizationClosures = [];
    protected $finalValidationClosures = [];
    protected $allowOverwrite = true;
    protected $required = false;
    protected $deprecationMessage = null;
    protected $equivalentValues = [];
    protected $attributes = [];
    protected $pathSeparator;

    private $handlingPlaceholder;

    /**
     * @throws \InvalidArgumentException if the name contains a period
     */
    public function __construct(?string $name, NodeInterface $parent = null, string $pathSeparator = self::DEFAULT_PATH_SEPARATOR)
    {
        if (false !== strpos($name = (string) $name, $pathSeparator)) {
            throw new \InvalidArgumentException('The name must not contain "'.$pathSeparator.'".');
        }

        $this->name = $name;
        $this->parent = $parent;
        $this->pathSeparator = $pathSeparator;
    }

    /**
     * Register possible (dummy) values for a dynamic placeholder value.
     *
     * Matching configuration values will be processed with a provided value, one by one. After a provided value is
     * successfully processed the configuration value is returned as is, thus preserving the placeholder.
     *
     * @internal
     */
    public static function setPlaceholder(string $placeholder, array $values): void
    {
        if (!$values) {
            throw new \InvalidArgumentException('At least one value must be provided.');
        }

        self::$placeholders[$placeholder] = $values;
    }

    /**
     * Sets a common prefix for dynamic placeholder values.
     *
     * Matching configuration values will be skipped from being processed and are returned as is, thus preserving the
     * placeholder. An exact match provided by {@see setPlaceholder()} might take precedence.
     *
     * @internal
     */
    public static function setPlaceholderUniquePrefix(string $prefix): void
    {
        self::$placeholderUniquePrefix = $prefix;
    }

    /**
     * Resets all current placeholders available.
     *
     * @internal
     */
    public static function resetPlaceholders(): void
    {
        self::$placeholderUniquePrefix = null;
        self::$placeholders = [];
    }

    public function setAttribute($key, $value)
    {
        $this->attributes[$key] = $value;
    }

    public function getAttribute($key, $default = null)
    {
        return isset($this->attributes[$key]) ? $this->attributes[$key] : $default;
    }

    public function hasAttribute($key)
    {
        return isset($this->attributes[$key]);
    }

    public function getAttributes()
    {
        return $this->attributes;
    }

    public function setAttributes(array $attributes)
    {
        $this->attributes = $attributes;
    }

    public function removeAttribute($key)
    {
        unset($this->attributes[$key]);
    }

    /**
     * Sets an info message.
     *
     * @param string $info
     */
    public function setInfo($info)
    {
        $this->setAttribute('info', $info);
    }

    /**
     * Returns info message.
     *
     * @return string The info text
     */
    public function getInfo()
    {
        return $this->getAttribute('info');
    }

    /**
     * Sets the example configuration for this node.
     *
     * @param string|array $example
     */
    public function setExample($example)
    {
        $this->setAttribute('example', $example);
    }

    /**
     * Retrieves the example configuration for this node.
     *
     * @return string|array The example
     */
    public function getExample()
    {
        return $this->getAttribute('example');
    }

    /**
     * Adds an equivalent value.
     *
     * @param mixed $originalValue
     * @param mixed $equivalentValue
     */
    public function addEquivalentValue($originalValue, $equivalentValue)
    {
        $this->equivalentValues[] = [$originalValue, $equivalentValue];
    }

    /**
     * Set this node as required.
     *
     * @param bool $boolean Required node
     */
    public function setRequired($boolean)
    {
        $this->required = (bool) $boolean;
    }

    /**
     * Sets this node as deprecated.
     *
     * You can use %node% and %path% placeholders in your message to display,
     * respectively, the node name and its complete path.
     *
     * @param string|null $message Deprecated message
     */
    public function setDeprecated($message)
    {
        $this->deprecationMessage = $message;
    }

    /**
     * Sets if this node can be overridden.
     *
     * @param bool $allow
     */
    public function setAllowOverwrite($allow)
    {
        $this->allowOverwrite = (bool) $allow;
    }

    /**
     * Sets the closures used for normalization.
     *
     * @param \Closure[] $closures An array of Closures used for normalization
     */
    public function setNormalizationClosures(array $closures)
    {
        $this->normalizationClosures = $closures;
    }

    /**
     * Sets the closures used for final validation.
     *
     * @param \Closure[] $closures An array of Closures used for final validation
     */
    public function setFinalValidationClosures(array $closures)
    {
        $this->finalValidationClosures = $closures;
    }

    /**
     * {@inheritdoc}
     */
    public function isRequired()
    {
        return $this->required;
    }

    /**
     * Checks if this node is deprecated.
     *
     * @return bool
     */
    public function isDeprecated()
    {
        return null !== $this->deprecationMessage;
    }

    /**
     * Returns the deprecated message.
     *
     * @param string $node the configuration node name
     * @param string $path the path of the node
     *
     * @return string
     */
    public function getDeprecationMessage($node, $path)
    {
        return strtr($this->deprecationMessage, ['%node%' => $node, '%path%' => $path]);
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * {@inheritdoc}
     */
    public function getPath()
    {
        if (null !== $this->parent) {
            return $this->parent->getPath().$this->pathSeparator.$this->name;
        }

        return $this->name;
    }

    /**
     * {@inheritdoc}
     */
    final public function merge($leftSide, $rightSide)
    {
        if (!$this->allowOverwrite) {
            throw new ForbiddenOverwriteException(sprintf('Configuration path "%s" cannot be overwritten. You have to define all options for this path, and any of its sub-paths in one configuration section.', $this->getPath()));
        }

        if ($leftSide !== $leftPlaceholders = self::resolvePlaceholderValue($leftSide)) {
            foreach ($leftPlaceholders as $leftPlaceholder) {
                $this->handlingPlaceholder = $leftSide;
                try {
                    $this->merge($leftPlaceholder, $rightSide);
                } finally {
                    $this->handlingPlaceholder = null;
                }
            }

            return $rightSide;
        }

        if ($rightSide !== $rightPlaceholders = self::resolvePlaceholderValue($rightSide)) {
            foreach ($rightPlaceholders as $rightPlaceholder) {
                $this->handlingPlaceholder = $rightSide;
                try {
                    $this->merge($leftSide, $rightPlaceholder);
                } finally {
                    $this->handlingPlaceholder = null;
                }
            }

            return $rightSide;
        }

        $this->doValidateType($leftSide);
        $this->doValidateType($rightSide);

        return $this->mergeValues($leftSide, $rightSide);
    }

    /**
     * {@inheritdoc}
     */
    final public function normalize($value)
    {
        $value = $this->preNormalize($value);

        // run custom normalization closures
        foreach ($this->normalizationClosures as $closure) {
            $value = $closure($value);
        }

        // resolve placeholder value
        if ($value !== $placeholders = self::resolvePlaceholderValue($value)) {
            foreach ($placeholders as $placeholder) {
                $this->handlingPlaceholder = $value;
                try {
                    $this->normalize($placeholder);
                } finally {
                    $this->handlingPlaceholder = null;
                }
            }

            return $value;
        }

        // replace value with their equivalent
        foreach ($this->equivalentValues as $data) {
            if ($data[0] === $value) {
                $value = $data[1];
            }
        }

        // validate type
        $this->doValidateType($value);

        // normalize value
        return $this->normalizeValue($value);
    }

    /**
     * Normalizes the value before any other normalization is applied.
     *
     * @param $value
     *
     * @return The normalized array value
     */
    protected function preNormalize($value)
    {
        return $value;
    }

    /**
     * Returns parent node for this node.
     *
     * @return NodeInterface|null
     */
    public function getParent()
    {
        return $this->parent;
    }

    /**
     * {@inheritdoc}
     */
    final public function finalize($value)
    {
        if ($value !== $placeholders = self::resolvePlaceholderValue($value)) {
            foreach ($placeholders as $placeholder) {
                $this->handlingPlaceholder = $value;
                try {
                    $this->finalize($placeholder);
                } finally {
                    $this->handlingPlaceholder = null;
                }
            }

            return $value;
        }

        $this->doValidateType($value);

        $value = $this->finalizeValue($value);

        // Perform validation on the final value if a closure has been set.
        // The closure is also allowed to return another value.
        foreach ($this->finalValidationClosures as $closure) {
            try {
                $value = $closure($value);
            } catch (Exception $e) {
                if ($e instanceof UnsetKeyException && null !== $this->handlingPlaceholder) {
                    continue;
                }

                throw $e;
            } catch (\Exception $e) {
                throw new InvalidConfigurationException(sprintf('Invalid configuration for path "%s": %s', $this->getPath(), $e->getMessage()), $e->getCode(), $e);
            }
        }

        return $value;
    }

    /**
     * Validates the type of a Node.
     *
     * @param mixed $value The value to validate
     *
     * @throws InvalidTypeException when the value is invalid
     */
    abstract protected function validateType($value);

    /**
     * Normalizes the value.
     *
     * @param mixed $value The value to normalize
     *
     * @return mixed The normalized value
     */
    abstract protected function normalizeValue($value);

    /**
     * Merges two values together.
     *
     * @param mixed $leftSide
     * @param mixed $rightSide
     *
     * @return mixed The merged value
     */
    abstract protected function mergeValues($leftSide, $rightSide);

    /**
     * Finalizes a value.
     *
     * @param mixed $value The value to finalize
     *
     * @return mixed The finalized value
     */
    abstract protected function finalizeValue($value);

    /**
     * Tests if placeholder values are allowed for this node.
     */
    protected function allowPlaceholders(): bool
    {
        return true;
    }

    /**
     * Tests if a placeholder is being handled currently.
     */
    protected function isHandlingPlaceholder(): bool
    {
        return null !== $this->handlingPlaceholder;
    }

    /**
     * Gets allowed dynamic types for this node.
     */
    protected function getValidPlaceholderTypes(): array
    {
        return [];
    }

    private static function resolvePlaceholderValue($value)
    {
        if (\is_string($value)) {
            if (isset(self::$placeholders[$value])) {
                return self::$placeholders[$value];
            }

            if (self::$placeholderUniquePrefix && 0 === strpos($value, self::$placeholderUniquePrefix)) {
                return [];
            }
        }

        return $value;
    }

    private function doValidateType($value): void
    {
        if (null !== $this->handlingPlaceholder && !$this->allowPlaceholders()) {
            $e = new InvalidTypeException(sprintf('A dynamic value is not compatible with a "%s" node type at path "%s".', \get_class($this), $this->getPath()));
            $e->setPath($this->getPath());

            throw $e;
        }

        if (null === $this->handlingPlaceholder || null === $value) {
            $this->validateType($value);

            return;
        }

        $knownTypes = array_keys(self::$placeholders[$this->handlingPlaceholder]);
        $validTypes = $this->getValidPlaceholderTypes();

        if ($validTypes && array_diff($knownTypes, $validTypes)) {
            $e = new InvalidTypeException(sprintf(
                'Invalid type for path "%s". Expected %s, but got %s.',
                $this->getPath(),
                1 === \count($validTypes) ? '"'.reset($validTypes).'"' : 'one of "'.implode('", "', $validTypes).'"',
                1 === \count($knownTypes) ? '"'.reset($knownTypes).'"' : 'one of "'.implode('", "', $knownTypes).'"'
            ));
            if ($hint = $this->getInfo()) {
                $e->addHint($hint);
            }
            $e->setPath($this->getPath());

            throw $e;
        }

        $this->validateType($value);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition;

use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;

/**
 * This node represents a numeric value in the config tree.
 *
 * @author David Jeanmonod <david.jeanmonod@gmail.com>
 */
class NumericNode extends ScalarNode
{
    protected $min;
    protected $max;

    public function __construct(?string $name, NodeInterface $parent = null, $min = null, $max = null, string $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR)
    {
        parent::__construct($name, $parent, $pathSeparator);
        $this->min = $min;
        $this->max = $max;
    }

    /**
     * {@inheritdoc}
     */
    protected function finalizeValue($value)
    {
        $value = parent::finalizeValue($value);

        $errorMsg = null;
        if (isset($this->min) && $value < $this->min) {
            $errorMsg = sprintf('The value %s is too small for path "%s". Should be greater than or equal to %s', $value, $this->getPath(), $this->min);
        }
        if (isset($this->max) && $value > $this->max) {
            $errorMsg = sprintf('The value %s is too big for path "%s". Should be less than or equal to %s', $value, $this->getPath(), $this->max);
        }
        if (isset($errorMsg)) {
            $ex = new InvalidConfigurationException($errorMsg);
            $ex->setPath($this->getPath());
            throw $ex;
        }

        return $value;
    }

    /**
     * {@inheritdoc}
     */
    protected function isValueEmpty($value)
    {
        // a numeric value cannot be empty
        return false;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition;

use Symfony\Component\Config\Definition\Exception\InvalidTypeException;

/**
 * This node represents an integer value in the config tree.
 *
 * @author Jeanmonod David <david.jeanmonod@gmail.com>
 */
class IntegerNode extends NumericNode
{
    /**
     * {@inheritdoc}
     */
    protected function validateType($value)
    {
        if (!\is_int($value)) {
            $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected int, but got %s.', $this->getPath(), \gettype($value)));
            if ($hint = $this->getInfo()) {
                $ex->addHint($hint);
            }
            $ex->setPath($this->getPath());

            throw $ex;
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function getValidPlaceholderTypes(): array
    {
        return ['int'];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition;

/**
 * This interface must be implemented by nodes which can be used as prototypes.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
interface PrototypeNodeInterface extends NodeInterface
{
    /**
     * Sets the name of the node.
     *
     * @param string $name The name of the node
     */
    public function setName($name);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition;

use Symfony\Component\Config\Definition\Exception\DuplicateKeyException;
use Symfony\Component\Config\Definition\Exception\Exception;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\Config\Definition\Exception\UnsetKeyException;

/**
 * Represents a prototyped Array node in the config tree.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class PrototypedArrayNode extends ArrayNode
{
    protected $prototype;
    protected $keyAttribute;
    protected $removeKeyAttribute = false;
    protected $minNumberOfElements = 0;
    protected $defaultValue = [];
    protected $defaultChildren;
    /**
     * @var NodeInterface[] An array of the prototypes of the simplified value children
     */
    private $valuePrototypes = [];

    /**
     * Sets the minimum number of elements that a prototype based node must
     * contain. By default this is zero, meaning no elements.
     *
     * @param int $number
     */
    public function setMinNumberOfElements($number)
    {
        $this->minNumberOfElements = $number;
    }

    /**
     * Sets the attribute which value is to be used as key.
     *
     * This is useful when you have an indexed array that should be an
     * associative array. You can select an item from within the array
     * to be the key of the particular item. For example, if "id" is the
     * "key", then:
     *
     *     [
     *         ['id' => 'my_name', 'foo' => 'bar'],
     *     ];
     *
     *  becomes
     *
     *      [
     *          'my_name' => ['foo' => 'bar'],
     *      ];
     *
     * If you'd like "'id' => 'my_name'" to still be present in the resulting
     * array, then you can set the second argument of this method to false.
     *
     * @param string $attribute The name of the attribute which value is to be used as a key
     * @param bool   $remove    Whether or not to remove the key
     */
    public function setKeyAttribute($attribute, $remove = true)
    {
        $this->keyAttribute = $attribute;
        $this->removeKeyAttribute = $remove;
    }

    /**
     * Retrieves the name of the attribute which value should be used as key.
     *
     * @return string The name of the attribute
     */
    public function getKeyAttribute()
    {
        return $this->keyAttribute;
    }

    /**
     * Sets the default value of this node.
     *
     * @param string $value
     *
     * @throws \InvalidArgumentException if the default value is not an array
     */
    public function setDefaultValue($value)
    {
        if (!\is_array($value)) {
            throw new \InvalidArgumentException($this->getPath().': the default value of an array node has to be an array.');
        }

        $this->defaultValue = $value;
    }

    /**
     * {@inheritdoc}
     */
    public function hasDefaultValue()
    {
        return true;
    }

    /**
     * Adds default children when none are set.
     *
     * @param int|string|array|null $children The number of children|The child name|The children names to be added
     */
    public function setAddChildrenIfNoneSet($children = ['defaults'])
    {
        if (null === $children) {
            $this->defaultChildren = ['defaults'];
        } else {
            $this->defaultChildren = \is_int($children) && $children > 0 ? range(1, $children) : (array) $children;
        }
    }

    /**
     * {@inheritdoc}
     *
     * The default value could be either explicited or derived from the prototype
     * default value.
     */
    public function getDefaultValue()
    {
        if (null !== $this->defaultChildren) {
            $default = $this->prototype->hasDefaultValue() ? $this->prototype->getDefaultValue() : [];
            $defaults = [];
            foreach (array_values($this->defaultChildren) as $i => $name) {
                $defaults[null === $this->keyAttribute ? $i : $name] = $default;
            }

            return $defaults;
        }

        return $this->defaultValue;
    }

    /**
     * Sets the node prototype.
     */
    public function setPrototype(PrototypeNodeInterface $node)
    {
        $this->prototype = $node;
    }

    /**
     * Retrieves the prototype.
     *
     * @return PrototypeNodeInterface The prototype
     */
    public function getPrototype()
    {
        return $this->prototype;
    }

    /**
     * Disable adding concrete children for prototyped nodes.
     *
     * @throws Exception
     */
    public function addChild(NodeInterface $node)
    {
        throw new Exception('A prototyped array node can not have concrete children.');
    }

    /**
     * Finalizes the value of this node.
     *
     * @param mixed $value
     *
     * @return mixed The finalized value
     *
     * @throws UnsetKeyException
     * @throws InvalidConfigurationException if the node doesn't have enough children
     */
    protected function finalizeValue($value)
    {
        if (false === $value) {
            throw new UnsetKeyException(sprintf('Unsetting key for path "%s", value: %s', $this->getPath(), json_encode($value)));
        }

        foreach ($value as $k => $v) {
            $prototype = $this->getPrototypeForChild($k);
            try {
                $value[$k] = $prototype->finalize($v);
            } catch (UnsetKeyException $e) {
                unset($value[$k]);
            }
        }

        if (\count($value) < $this->minNumberOfElements) {
            $ex = new InvalidConfigurationException(sprintf('The path "%s" should have at least %d element(s) defined.', $this->getPath(), $this->minNumberOfElements));
            $ex->setPath($this->getPath());

            throw $ex;
        }

        return $value;
    }

    /**
     * Normalizes the value.
     *
     * @param mixed $value The value to normalize
     *
     * @return mixed The normalized value
     *
     * @throws InvalidConfigurationException
     * @throws DuplicateKeyException
     */
    protected function normalizeValue($value)
    {
        if (false === $value) {
            return $value;
        }

        $value = $this->remapXml($value);

        $isAssoc = array_keys($value) !== range(0, \count($value) - 1);
        $normalized = [];
        foreach ($value as $k => $v) {
            if (null !== $this->keyAttribute && \is_array($v)) {
                if (!isset($v[$this->keyAttribute]) && \is_int($k) && !$isAssoc) {
                    $ex = new InvalidConfigurationException(sprintf('The attribute "%s" must be set for path "%s".', $this->keyAttribute, $this->getPath()));
                    $ex->setPath($this->getPath());

                    throw $ex;
                } elseif (isset($v[$this->keyAttribute])) {
                    $k = $v[$this->keyAttribute];

                    // remove the key attribute when required
                    if ($this->removeKeyAttribute) {
                        unset($v[$this->keyAttribute]);
                    }

                    // if only "value" is left
                    if (array_keys($v) === ['value']) {
                        $v = $v['value'];
                        if ($this->prototype instanceof ArrayNode && ($children = $this->prototype->getChildren()) && \array_key_exists('value', $children)) {
                            $valuePrototype = current($this->valuePrototypes) ?: clone $children['value'];
                            $valuePrototype->parent = $this;
                            $originalClosures = $this->prototype->normalizationClosures;
                            if (\is_array($originalClosures)) {
                                $valuePrototypeClosures = $valuePrototype->normalizationClosures;
                                $valuePrototype->normalizationClosures = \is_array($valuePrototypeClosures) ? array_merge($originalClosures, $valuePrototypeClosures) : $originalClosures;
                            }
                            $this->valuePrototypes[$k] = $valuePrototype;
                        }
                    }
                }

                if (\array_key_exists($k, $normalized)) {
                    $ex = new DuplicateKeyException(sprintf('Duplicate key "%s" for path "%s".', $k, $this->getPath()));
                    $ex->setPath($this->getPath());

                    throw $ex;
                }
            }

            $prototype = $this->getPrototypeForChild($k);
            if (null !== $this->keyAttribute || $isAssoc) {
                $normalized[$k] = $prototype->normalize($v);
            } else {
                $normalized[] = $prototype->normalize($v);
            }
        }

        return $normalized;
    }

    /**
     * Merges values together.
     *
     * @param mixed $leftSide  The left side to merge
     * @param mixed $rightSide The right side to merge
     *
     * @return mixed The merged values
     *
     * @throws InvalidConfigurationException
     * @throws \RuntimeException
     */
    protected function mergeValues($leftSide, $rightSide)
    {
        if (false === $rightSide) {
            // if this is still false after the last config has been merged the
            // finalization pass will take care of removing this key entirely
            return false;
        }

        if (false === $leftSide || !$this->performDeepMerging) {
            return $rightSide;
        }

        foreach ($rightSide as $k => $v) {
            // prototype, and key is irrelevant, append the element
            if (null === $this->keyAttribute) {
                $leftSide[] = $v;
                continue;
            }

            // no conflict
            if (!\array_key_exists($k, $leftSide)) {
                if (!$this->allowNewKeys) {
                    $ex = new InvalidConfigurationException(sprintf('You are not allowed to define new elements for path "%s". Please define all elements for this path in one config file.', $this->getPath()));
                    $ex->setPath($this->getPath());

                    throw $ex;
                }

                $leftSide[$k] = $v;
                continue;
            }

            $prototype = $this->getPrototypeForChild($k);
            $leftSide[$k] = $prototype->merge($leftSide[$k], $v);
        }

        return $leftSide;
    }

    /**
     * Returns a prototype for the child node that is associated to $key in the value array.
     * For general child nodes, this will be $this->prototype.
     * But if $this->removeKeyAttribute is true and there are only two keys in the child node:
     * one is same as this->keyAttribute and the other is 'value', then the prototype will be different.
     *
     * For example, assume $this->keyAttribute is 'name' and the value array is as follows:
     *
     *     [
     *         [
     *             'name' => 'name001',
     *             'value' => 'value001'
     *         ]
     *     ]
     *
     * Now, the key is 0 and the child node is:
     *
     *     [
     *        'name' => 'name001',
     *        'value' => 'value001'
     *     ]
     *
     * When normalizing the value array, the 'name' element will removed from the child node
     * and its value becomes the new key of the child node:
     *
     *     [
     *         'name001' => ['value' => 'value001']
     *     ]
     *
     * Now only 'value' element is left in the child node which can be further simplified into a string:
     *
     *     ['name001' => 'value001']
     *
     * Now, the key becomes 'name001' and the child node becomes 'value001' and
     * the prototype of child node 'name001' should be a ScalarNode instead of an ArrayNode instance.
     *
     * @return mixed The prototype instance
     */
    private function getPrototypeForChild(string $key)
    {
        $prototype = isset($this->valuePrototypes[$key]) ? $this->valuePrototypes[$key] : $this->prototype;
        $prototype->setName($key);

        return $prototype;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition;

/**
 * This class is the entry point for config normalization/merging/finalization.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 *
 * @final since version 4.1
 */
class Processor
{
    /**
     * Processes an array of configurations.
     *
     * @param NodeInterface $configTree The node tree describing the configuration
     * @param array         $configs    An array of configuration items to process
     *
     * @return array The processed configuration
     */
    public function process(NodeInterface $configTree, array $configs)
    {
        $currentConfig = [];
        foreach ($configs as $config) {
            $config = $configTree->normalize($config);
            $currentConfig = $configTree->merge($currentConfig, $config);
        }

        return $configTree->finalize($currentConfig);
    }

    /**
     * Processes an array of configurations.
     *
     * @param ConfigurationInterface $configuration The configuration class
     * @param array                  $configs       An array of configuration items to process
     *
     * @return array The processed configuration
     */
    public function processConfiguration(ConfigurationInterface $configuration, array $configs)
    {
        return $this->process($configuration->getConfigTreeBuilder()->buildTree(), $configs);
    }

    /**
     * Normalizes a configuration entry.
     *
     * This method returns a normalize configuration array for a given key
     * to remove the differences due to the original format (YAML and XML mainly).
     *
     * Here is an example.
     *
     * The configuration in XML:
     *
     * <twig:extension>twig.extension.foo</twig:extension>
     * <twig:extension>twig.extension.bar</twig:extension>
     *
     * And the same configuration in YAML:
     *
     * extensions: ['twig.extension.foo', 'twig.extension.bar']
     *
     * @param array  $config A config array
     * @param string $key    The key to normalize
     * @param string $plural The plural form of the key if it is irregular
     *
     * @return array
     */
    public static function normalizeConfig($config, $key, $plural = null)
    {
        if (null === $plural) {
            $plural = $key.'s';
        }

        if (isset($config[$plural])) {
            return $config[$plural];
        }

        if (isset($config[$key])) {
            if (\is_string($config[$key]) || !\is_int(key($config[$key]))) {
                // only one
                return  [$config[$key]];
            }

            return  $config[$key];
        }

        return [];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition;

use Symfony\Component\Config\Definition\Exception\ForbiddenOverwriteException;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;

/**
 * Common Interface among all nodes.
 *
 * In most cases, it is better to inherit from BaseNode instead of implementing
 * this interface yourself.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
interface NodeInterface
{
    /**
     * Returns the name of the node.
     *
     * @return string The name of the node
     */
    public function getName();

    /**
     * Returns the path of the node.
     *
     * @return string The node path
     */
    public function getPath();

    /**
     * Returns true when the node is required.
     *
     * @return bool If the node is required
     */
    public function isRequired();

    /**
     * Returns true when the node has a default value.
     *
     * @return bool If the node has a default value
     */
    public function hasDefaultValue();

    /**
     * Returns the default value of the node.
     *
     * @return mixed The default value
     *
     * @throws \RuntimeException if the node has no default value
     */
    public function getDefaultValue();

    /**
     * Normalizes a value.
     *
     * @param mixed $value The value to normalize
     *
     * @return mixed The normalized value
     *
     * @throws InvalidTypeException if the value type is invalid
     */
    public function normalize($value);

    /**
     * Merges two values together.
     *
     * @param mixed $leftSide
     * @param mixed $rightSide
     *
     * @return mixed The merged value
     *
     * @throws ForbiddenOverwriteException if the configuration path cannot be overwritten
     * @throws InvalidTypeException        if the value type is invalid
     */
    public function merge($leftSide, $rightSide);

    /**
     * Finalizes a value.
     *
     * @param mixed $value The value to finalize
     *
     * @return mixed The finalized value
     *
     * @throws InvalidTypeException          if the value type is invalid
     * @throws InvalidConfigurationException if the value is invalid configuration
     */
    public function finalize($value);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition;

use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;

/**
 * Node which only allows a finite set of values.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class EnumNode extends ScalarNode
{
    private $values;

    public function __construct(?string $name, NodeInterface $parent = null, array $values = [], string $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR)
    {
        $values = array_unique($values);
        if (empty($values)) {
            throw new \InvalidArgumentException('$values must contain at least one element.');
        }

        parent::__construct($name, $parent, $pathSeparator);
        $this->values = $values;
    }

    public function getValues()
    {
        return $this->values;
    }

    protected function finalizeValue($value)
    {
        $value = parent::finalizeValue($value);

        if (!\in_array($value, $this->values, true)) {
            $ex = new InvalidConfigurationException(sprintf('The value %s is not allowed for path "%s". Permissible values: %s', json_encode($value), $this->getPath(), implode(', ', array_map('json_encode', $this->values))));
            $ex->setPath($this->getPath());

            throw $ex;
        }

        return $value;
    }

    /**
     * {@inheritdoc}
     */
    protected function allowPlaceholders(): bool
    {
        return false;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition;

use Symfony\Component\Config\Definition\Exception\InvalidTypeException;

/**
 * This node represents a Boolean value in the config tree.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class BooleanNode extends ScalarNode
{
    /**
     * {@inheritdoc}
     */
    protected function validateType($value)
    {
        if (!\is_bool($value)) {
            $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected boolean, but got %s.', $this->getPath(), \gettype($value)));
            if ($hint = $this->getInfo()) {
                $ex->addHint($hint);
            }
            $ex->setPath($this->getPath());

            throw $ex;
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function isValueEmpty($value)
    {
        // a boolean value cannot be empty
        return false;
    }

    /**
     * {@inheritdoc}
     */
    protected function getValidPlaceholderTypes(): array
    {
        return ['bool'];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition;

use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
use Symfony\Component\Config\Definition\Exception\UnsetKeyException;

/**
 * Represents an Array node in the config tree.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class ArrayNode extends BaseNode implements PrototypeNodeInterface
{
    protected $xmlRemappings = [];
    protected $children = [];
    protected $allowFalse = false;
    protected $allowNewKeys = true;
    protected $addIfNotSet = false;
    protected $performDeepMerging = true;
    protected $ignoreExtraKeys = false;
    protected $removeExtraKeys = true;
    protected $normalizeKeys = true;

    public function setNormalizeKeys($normalizeKeys)
    {
        $this->normalizeKeys = (bool) $normalizeKeys;
    }

    /**
     * Normalizes keys between the different configuration formats.
     *
     * Namely, you mostly have foo_bar in YAML while you have foo-bar in XML.
     * After running this method, all keys are normalized to foo_bar.
     *
     * If you have a mixed key like foo-bar_moo, it will not be altered.
     * The key will also not be altered if the target key already exists.
     *
     * @param mixed $value
     *
     * @return array The value with normalized keys
     */
    protected function preNormalize($value)
    {
        if (!$this->normalizeKeys || !\is_array($value)) {
            return $value;
        }

        $normalized = [];

        foreach ($value as $k => $v) {
            if (false !== strpos($k, '-') && false === strpos($k, '_') && !\array_key_exists($normalizedKey = str_replace('-', '_', $k), $value)) {
                $normalized[$normalizedKey] = $v;
            } else {
                $normalized[$k] = $v;
            }
        }

        return $normalized;
    }

    /**
     * Retrieves the children of this node.
     *
     * @return array The children
     */
    public function getChildren()
    {
        return $this->children;
    }

    /**
     * Sets the xml remappings that should be performed.
     *
     * @param array $remappings An array of the form [[string, string]]
     */
    public function setXmlRemappings(array $remappings)
    {
        $this->xmlRemappings = $remappings;
    }

    /**
     * Gets the xml remappings that should be performed.
     *
     * @return array an array of the form [[string, string]]
     */
    public function getXmlRemappings()
    {
        return $this->xmlRemappings;
    }

    /**
     * Sets whether to add default values for this array if it has not been
     * defined in any of the configuration files.
     *
     * @param bool $boolean
     */
    public function setAddIfNotSet($boolean)
    {
        $this->addIfNotSet = (bool) $boolean;
    }

    /**
     * Sets whether false is allowed as value indicating that the array should be unset.
     *
     * @param bool $allow
     */
    public function setAllowFalse($allow)
    {
        $this->allowFalse = (bool) $allow;
    }

    /**
     * Sets whether new keys can be defined in subsequent configurations.
     *
     * @param bool $allow
     */
    public function setAllowNewKeys($allow)
    {
        $this->allowNewKeys = (bool) $allow;
    }

    /**
     * Sets if deep merging should occur.
     *
     * @param bool $boolean
     */
    public function setPerformDeepMerging($boolean)
    {
        $this->performDeepMerging = (bool) $boolean;
    }

    /**
     * Whether extra keys should just be ignore without an exception.
     *
     * @param bool $boolean To allow extra keys
     * @param bool $remove  To remove extra keys
     */
    public function setIgnoreExtraKeys($boolean, $remove = true)
    {
        $this->ignoreExtraKeys = (bool) $boolean;
        $this->removeExtraKeys = $this->ignoreExtraKeys && $remove;
    }

    /**
     * {@inheritdoc}
     */
    public function setName($name)
    {
        $this->name = $name;
    }

    /**
     * {@inheritdoc}
     */
    public function hasDefaultValue()
    {
        return $this->addIfNotSet;
    }

    /**
     * {@inheritdoc}
     */
    public function getDefaultValue()
    {
        if (!$this->hasDefaultValue()) {
            throw new \RuntimeException(sprintf('The node at path "%s" has no default value.', $this->getPath()));
        }

        $defaults = [];
        foreach ($this->children as $name => $child) {
            if ($child->hasDefaultValue()) {
                $defaults[$name] = $child->getDefaultValue();
            }
        }

        return $defaults;
    }

    /**
     * Adds a child node.
     *
     * @throws \InvalidArgumentException when the child node has no name
     * @throws \InvalidArgumentException when the child node's name is not unique
     */
    public function addChild(NodeInterface $node)
    {
        $name = $node->getName();
        if (!\strlen($name)) {
            throw new \InvalidArgumentException('Child nodes must be named.');
        }
        if (isset($this->children[$name])) {
            throw new \InvalidArgumentException(sprintf('A child node named "%s" already exists.', $name));
        }

        $this->children[$name] = $node;
    }

    /**
     * Finalizes the value of this node.
     *
     * @param mixed $value
     *
     * @return mixed The finalised value
     *
     * @throws UnsetKeyException
     * @throws InvalidConfigurationException if the node doesn't have enough children
     */
    protected function finalizeValue($value)
    {
        if (false === $value) {
            throw new UnsetKeyException(sprintf('Unsetting key for path "%s", value: %s', $this->getPath(), json_encode($value)));
        }

        foreach ($this->children as $name => $child) {
            if (!\array_key_exists($name, $value)) {
                if ($child->isRequired()) {
                    $ex = new InvalidConfigurationException(sprintf('The child node "%s" at path "%s" must be configured.', $name, $this->getPath()));
                    $ex->setPath($this->getPath());

                    throw $ex;
                }

                if ($child->hasDefaultValue()) {
                    $value[$name] = $child->getDefaultValue();
                }

                continue;
            }

            if ($child->isDeprecated()) {
                @trigger_error($child->getDeprecationMessage($name, $this->getPath()), E_USER_DEPRECATED);
            }

            try {
                $value[$name] = $child->finalize($value[$name]);
            } catch (UnsetKeyException $e) {
                unset($value[$name]);
            }
        }

        return $value;
    }

    /**
     * Validates the type of the value.
     *
     * @param mixed $value
     *
     * @throws InvalidTypeException
     */
    protected function validateType($value)
    {
        if (!\is_array($value) && (!$this->allowFalse || false !== $value)) {
            $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected array, but got %s', $this->getPath(), \gettype($value)));
            if ($hint = $this->getInfo()) {
                $ex->addHint($hint);
            }
            $ex->setPath($this->getPath());

            throw $ex;
        }
    }

    /**
     * Normalizes the value.
     *
     * @param mixed $value The value to normalize
     *
     * @return mixed The normalized value
     *
     * @throws InvalidConfigurationException
     */
    protected function normalizeValue($value)
    {
        if (false === $value) {
            return $value;
        }

        $value = $this->remapXml($value);

        $normalized = [];
        foreach ($value as $name => $val) {
            if (isset($this->children[$name])) {
                try {
                    $normalized[$name] = $this->children[$name]->normalize($val);
                } catch (UnsetKeyException $e) {
                }
                unset($value[$name]);
            } elseif (!$this->removeExtraKeys) {
                $normalized[$name] = $val;
            }
        }

        // if extra fields are present, throw exception
        if (\count($value) && !$this->ignoreExtraKeys) {
            $proposals = array_keys($this->children);
            sort($proposals);
            $guesses = [];

            foreach (array_keys($value) as $subject) {
                $minScore = INF;
                foreach ($proposals as $proposal) {
                    $distance = levenshtein($subject, $proposal);
                    if ($distance <= $minScore && $distance < 3) {
                        $guesses[$proposal] = $distance;
                        $minScore = $distance;
                    }
                }
            }

            $msg = sprintf('Unrecognized option%s "%s" under "%s"', 1 === \count($value) ? '' : 's', implode(', ', array_keys($value)), $this->getPath());

            if (\count($guesses)) {
                asort($guesses);
                $msg .= sprintf('. Did you mean "%s"?', implode('", "', array_keys($guesses)));
            } else {
                $msg .= sprintf('. Available option%s %s "%s".', 1 === \count($proposals) ? '' : 's', 1 === \count($proposals) ? 'is' : 'are', implode('", "', $proposals));
            }

            $ex = new InvalidConfigurationException($msg);
            $ex->setPath($this->getPath());

            throw $ex;
        }

        return $normalized;
    }

    /**
     * Remaps multiple singular values to a single plural value.
     *
     * @param array $value The source values
     *
     * @return array The remapped values
     */
    protected function remapXml($value)
    {
        foreach ($this->xmlRemappings as list($singular, $plural)) {
            if (!isset($value[$singular])) {
                continue;
            }

            $value[$plural] = Processor::normalizeConfig($value, $singular, $plural);
            unset($value[$singular]);
        }

        return $value;
    }

    /**
     * Merges values together.
     *
     * @param mixed $leftSide  The left side to merge
     * @param mixed $rightSide The right side to merge
     *
     * @return mixed The merged values
     *
     * @throws InvalidConfigurationException
     * @throws \RuntimeException
     */
    protected function mergeValues($leftSide, $rightSide)
    {
        if (false === $rightSide) {
            // if this is still false after the last config has been merged the
            // finalization pass will take care of removing this key entirely
            return false;
        }

        if (false === $leftSide || !$this->performDeepMerging) {
            return $rightSide;
        }

        foreach ($rightSide as $k => $v) {
            // no conflict
            if (!\array_key_exists($k, $leftSide)) {
                if (!$this->allowNewKeys) {
                    $ex = new InvalidConfigurationException(sprintf('You are not allowed to define new elements for path "%s". Please define all elements for this path in one config file. If you are trying to overwrite an element, make sure you redefine it with the same name.', $this->getPath()));
                    $ex->setPath($this->getPath());

                    throw $ex;
                }

                $leftSide[$k] = $v;
                continue;
            }

            if (!isset($this->children[$k])) {
                throw new \RuntimeException('merge() expects a normalized config array.');
            }

            $leftSide[$k] = $this->children[$k]->merge($leftSide[$k], $v);
        }

        return $leftSide;
    }

    /**
     * {@inheritdoc}
     */
    protected function allowPlaceholders(): bool
    {
        return false;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition;

use Symfony\Component\Config\Definition\Exception\InvalidTypeException;

/**
 * This node represents a scalar value in the config tree.
 *
 * The following values are considered scalars:
 *   * booleans
 *   * strings
 *   * null
 *   * integers
 *   * floats
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class ScalarNode extends VariableNode
{
    /**
     * {@inheritdoc}
     */
    protected function validateType($value)
    {
        if (!is_scalar($value) && null !== $value) {
            $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected scalar, but got %s.', $this->getPath(), \gettype($value)));
            if ($hint = $this->getInfo()) {
                $ex->addHint($hint);
            }
            $ex->setPath($this->getPath());

            throw $ex;
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function isValueEmpty($value)
    {
        if ($this->isHandlingPlaceholder()) {
            return false;
        }

        return null === $value || '' === $value;
    }

    /**
     * {@inheritdoc}
     */
    protected function getValidPlaceholderTypes(): array
    {
        return ['bool', 'int', 'float', 'string'];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition;

/**
 * Configuration interface.
 *
 * @author Victor Berchet <victor@suumit.com>
 */
interface ConfigurationInterface
{
    /**
     * Generates the configuration tree builder.
     *
     * @return \Symfony\Component\Config\Definition\Builder\TreeBuilder The tree builder
     */
    public function getConfigTreeBuilder();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition\Builder;

use Symfony\Component\Config\Definition\Exception\UnsetKeyException;

/**
 * This class builds an if expression.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 * @author Christophe Coevoet <stof@notk.org>
 */
class ExprBuilder
{
    protected $node;
    public $ifPart;
    public $thenPart;

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

    /**
     * Marks the expression as being always used.
     *
     * @return $this
     */
    public function always(\Closure $then = null)
    {
        $this->ifPart = function ($v) { return true; };

        if (null !== $then) {
            $this->thenPart = $then;
        }

        return $this;
    }

    /**
     * Sets a closure to use as tests.
     *
     * The default one tests if the value is true.
     *
     * @return $this
     */
    public function ifTrue(\Closure $closure = null)
    {
        if (null === $closure) {
            $closure = function ($v) { return true === $v; };
        }

        $this->ifPart = $closure;

        return $this;
    }

    /**
     * Tests if the value is a string.
     *
     * @return $this
     */
    public function ifString()
    {
        $this->ifPart = function ($v) { return \is_string($v); };

        return $this;
    }

    /**
     * Tests if the value is null.
     *
     * @return $this
     */
    public function ifNull()
    {
        $this->ifPart = function ($v) { return null === $v; };

        return $this;
    }

    /**
     * Tests if the value is empty.
     *
     * @return ExprBuilder
     */
    public function ifEmpty()
    {
        $this->ifPart = function ($v) { return empty($v); };

        return $this;
    }

    /**
     * Tests if the value is an array.
     *
     * @return $this
     */
    public function ifArray()
    {
        $this->ifPart = function ($v) { return \is_array($v); };

        return $this;
    }

    /**
     * Tests if the value is in an array.
     *
     * @return $this
     */
    public function ifInArray(array $array)
    {
        $this->ifPart = function ($v) use ($array) { return \in_array($v, $array, true); };

        return $this;
    }

    /**
     * Tests if the value is not in an array.
     *
     * @return $this
     */
    public function ifNotInArray(array $array)
    {
        $this->ifPart = function ($v) use ($array) { return !\in_array($v, $array, true); };

        return $this;
    }

    /**
     * Transforms variables of any type into an array.
     *
     * @return $this
     */
    public function castToArray()
    {
        $this->ifPart = function ($v) { return !\is_array($v); };
        $this->thenPart = function ($v) { return [$v]; };

        return $this;
    }

    /**
     * Sets the closure to run if the test pass.
     *
     * @return $this
     */
    public function then(\Closure $closure)
    {
        $this->thenPart = $closure;

        return $this;
    }

    /**
     * Sets a closure returning an empty array.
     *
     * @return $this
     */
    public function thenEmptyArray()
    {
        $this->thenPart = function ($v) { return []; };

        return $this;
    }

    /**
     * Sets a closure marking the value as invalid at processing time.
     *
     * if you want to add the value of the node in your message just use a %s placeholder.
     *
     * @param string $message
     *
     * @return $this
     *
     * @throws \InvalidArgumentException
     */
    public function thenInvalid($message)
    {
        $this->thenPart = function ($v) use ($message) { throw new \InvalidArgumentException(sprintf($message, json_encode($v))); };

        return $this;
    }

    /**
     * Sets a closure unsetting this key of the array at processing time.
     *
     * @return $this
     *
     * @throws UnsetKeyException
     */
    public function thenUnset()
    {
        $this->thenPart = function ($v) { throw new UnsetKeyException('Unsetting key'); };

        return $this;
    }

    /**
     * Returns the related node.
     *
     * @return NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition
     *
     * @throws \RuntimeException
     */
    public function end()
    {
        if (null === $this->ifPart) {
            throw new \RuntimeException('You must specify an if part.');
        }
        if (null === $this->thenPart) {
            throw new \RuntimeException('You must specify a then part.');
        }

        return $this->node;
    }

    /**
     * Builds the expressions.
     *
     * @param ExprBuilder[] $expressions An array of ExprBuilder instances to build
     *
     * @return array
     */
    public static function buildExpressions(array $expressions)
    {
        foreach ($expressions as $k => $expr) {
            if ($expr instanceof self) {
                $if = $expr->ifPart;
                $then = $expr->thenPart;
                $expressions[$k] = function ($v) use ($if, $then) {
                    return $if($v) ? $then($v) : $v;
                };
            }
        }

        return $expressions;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition\Builder;

use Symfony\Component\Config\Definition\EnumNode;

/**
 * Enum Node Definition.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class EnumNodeDefinition extends ScalarNodeDefinition
{
    private $values;

    /**
     * @return $this
     */
    public function values(array $values)
    {
        $values = array_unique($values);

        if (empty($values)) {
            throw new \InvalidArgumentException('->values() must be called with at least one value.');
        }

        $this->values = $values;

        return $this;
    }

    /**
     * Instantiate a Node.
     *
     * @return EnumNode The node
     *
     * @throws \RuntimeException
     */
    protected function instantiateNode()
    {
        if (null === $this->values) {
            throw new \RuntimeException('You must call ->values() on enum nodes.');
        }

        return new EnumNode($this->name, $this->parent, $this->values, $this->pathSeparator);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition\Builder;

/**
 * This class builds merge conditions.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class MergeBuilder
{
    protected $node;
    public $allowFalse = false;
    public $allowOverwrite = true;

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

    /**
     * Sets whether the node can be unset.
     *
     * @param bool $allow
     *
     * @return $this
     */
    public function allowUnset($allow = true)
    {
        $this->allowFalse = $allow;

        return $this;
    }

    /**
     * Sets whether the node can be overwritten.
     *
     * @param bool $deny Whether the overwriting is forbidden or not
     *
     * @return $this
     */
    public function denyOverwrite($deny = true)
    {
        $this->allowOverwrite = !$deny;

        return $this;
    }

    /**
     * Returns the related node.
     *
     * @return NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition
     */
    public function end()
    {
        return $this->node;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition\Builder;

use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;

/**
 * Abstract class that contains common code of integer and float node definitions.
 *
 * @author David Jeanmonod <david.jeanmonod@gmail.com>
 */
abstract class NumericNodeDefinition extends ScalarNodeDefinition
{
    protected $min;
    protected $max;

    /**
     * Ensures that the value is smaller than the given reference.
     *
     * @param mixed $max
     *
     * @return $this
     *
     * @throws \InvalidArgumentException when the constraint is inconsistent
     */
    public function max($max)
    {
        if (isset($this->min) && $this->min > $max) {
            throw new \InvalidArgumentException(sprintf('You cannot define a max(%s) as you already have a min(%s)', $max, $this->min));
        }
        $this->max = $max;

        return $this;
    }

    /**
     * Ensures that the value is bigger than the given reference.
     *
     * @param mixed $min
     *
     * @return $this
     *
     * @throws \InvalidArgumentException when the constraint is inconsistent
     */
    public function min($min)
    {
        if (isset($this->max) && $this->max < $min) {
            throw new \InvalidArgumentException(sprintf('You cannot define a min(%s) as you already have a max(%s)', $min, $this->max));
        }
        $this->min = $min;

        return $this;
    }

    /**
     * {@inheritdoc}
     *
     * @throws InvalidDefinitionException
     */
    public function cannotBeEmpty()
    {
        throw new InvalidDefinitionException('->cannotBeEmpty() is not applicable to NumericNodeDefinition.');
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition\Builder;

/**
 * This class provides a fluent interface for building a node.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class NodeBuilder implements NodeParentInterface
{
    protected $parent;
    protected $nodeMapping;

    public function __construct()
    {
        $this->nodeMapping = [
            'variable' => __NAMESPACE__.'\\VariableNodeDefinition',
            'scalar' => __NAMESPACE__.'\\ScalarNodeDefinition',
            'boolean' => __NAMESPACE__.'\\BooleanNodeDefinition',
            'integer' => __NAMESPACE__.'\\IntegerNodeDefinition',
            'float' => __NAMESPACE__.'\\FloatNodeDefinition',
            'array' => __NAMESPACE__.'\\ArrayNodeDefinition',
            'enum' => __NAMESPACE__.'\\EnumNodeDefinition',
        ];
    }

    /**
     * Set the parent node.
     *
     * @return $this
     */
    public function setParent(ParentNodeDefinitionInterface $parent = null)
    {
        $this->parent = $parent;

        return $this;
    }

    /**
     * Creates a child array node.
     *
     * @param string $name The name of the node
     *
     * @return ArrayNodeDefinition The child node
     */
    public function arrayNode($name)
    {
        return $this->node($name, 'array');
    }

    /**
     * Creates a child scalar node.
     *
     * @param string $name The name of the node
     *
     * @return ScalarNodeDefinition The child node
     */
    public function scalarNode($name)
    {
        return $this->node($name, 'scalar');
    }

    /**
     * Creates a child Boolean node.
     *
     * @param string $name The name of the node
     *
     * @return BooleanNodeDefinition The child node
     */
    public function booleanNode($name)
    {
        return $this->node($name, 'boolean');
    }

    /**
     * Creates a child integer node.
     *
     * @param string $name The name of the node
     *
     * @return IntegerNodeDefinition The child node
     */
    public function integerNode($name)
    {
        return $this->node($name, 'integer');
    }

    /**
     * Creates a child float node.
     *
     * @param string $name The name of the node
     *
     * @return FloatNodeDefinition The child node
     */
    public function floatNode($name)
    {
        return $this->node($name, 'float');
    }

    /**
     * Creates a child EnumNode.
     *
     * @param string $name
     *
     * @return EnumNodeDefinition
     */
    public function enumNode($name)
    {
        return $this->node($name, 'enum');
    }

    /**
     * Creates a child variable node.
     *
     * @param string $name The name of the node
     *
     * @return VariableNodeDefinition The builder of the child node
     */
    public function variableNode($name)
    {
        return $this->node($name, 'variable');
    }

    /**
     * Returns the parent node.
     *
     * @return NodeDefinition&ParentNodeDefinitionInterface The parent node
     */
    public function end()
    {
        return $this->parent;
    }

    /**
     * Creates a child node.
     *
     * @param string|null $name The name of the node
     * @param string      $type The type of the node
     *
     * @return NodeDefinition The child node
     *
     * @throws \RuntimeException When the node type is not registered
     * @throws \RuntimeException When the node class is not found
     */
    public function node($name, $type)
    {
        $class = $this->getNodeClass($type);

        $node = new $class($name);

        $this->append($node);

        return $node;
    }

    /**
     * Appends a node definition.
     *
     * Usage:
     *
     *     $node = new ArrayNodeDefinition('name')
     *         ->children()
     *             ->scalarNode('foo')->end()
     *             ->scalarNode('baz')->end()
     *             ->append($this->getBarNodeDefinition())
     *         ->end()
     *     ;
     *
     * @return $this
     */
    public function append(NodeDefinition $node)
    {
        if ($node instanceof BuilderAwareInterface) {
            $builder = clone $this;
            $builder->setParent(null);
            $node->setBuilder($builder);
        }

        if (null !== $this->parent) {
            $this->parent->append($node);
            // Make this builder the node parent to allow for a fluid interface
            $node->setParent($this);
        }

        return $this;
    }

    /**
     * Adds or overrides a node Type.
     *
     * @param string $type  The name of the type
     * @param string $class The fully qualified name the node definition class
     *
     * @return $this
     */
    public function setNodeClass($type, $class)
    {
        $this->nodeMapping[strtolower($type)] = $class;

        return $this;
    }

    /**
     * Returns the class name of the node definition.
     *
     * @param string $type The node type
     *
     * @return string The node definition class name
     *
     * @throws \RuntimeException When the node type is not registered
     * @throws \RuntimeException When the node class is not found
     */
    protected function getNodeClass($type)
    {
        $type = strtolower($type);

        if (!isset($this->nodeMapping[$type])) {
            throw new \RuntimeException(sprintf('The node type "%s" is not registered.', $type));
        }

        $class = $this->nodeMapping[$type];

        if (!class_exists($class)) {
            throw new \RuntimeException(sprintf('The node class "%s" does not exist.', $class));
        }

        return $class;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition\Builder;

use Symfony\Component\Config\Definition\VariableNode;

/**
 * This class provides a fluent interface for defining a node.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class VariableNodeDefinition extends NodeDefinition
{
    /**
     * Instantiate a Node.
     *
     * @return VariableNode The node
     */
    protected function instantiateNode()
    {
        return new VariableNode($this->name, $this->parent, $this->pathSeparator);
    }

    /**
     * {@inheritdoc}
     */
    protected function createNode()
    {
        $node = $this->instantiateNode();

        if (null !== $this->normalization) {
            $node->setNormalizationClosures($this->normalization->before);
        }

        if (null !== $this->merge) {
            $node->setAllowOverwrite($this->merge->allowOverwrite);
        }

        if (true === $this->default) {
            $node->setDefaultValue($this->defaultValue);
        }

        $node->setAllowEmptyValue($this->allowEmptyValue);
        $node->addEquivalentValue(null, $this->nullEquivalent);
        $node->addEquivalentValue(true, $this->trueEquivalent);
        $node->addEquivalentValue(false, $this->falseEquivalent);
        $node->setRequired($this->required);
        $node->setDeprecated($this->deprecationMessage);

        if (null !== $this->validation) {
            $node->setFinalValidationClosures($this->validation->rules);
        }

        return $node;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition\Builder;

use Symfony\Component\Config\Definition\ArrayNode;
use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
use Symfony\Component\Config\Definition\PrototypedArrayNode;

/**
 * This class provides a fluent interface for defining an array node.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinitionInterface
{
    protected $performDeepMerging = true;
    protected $ignoreExtraKeys = false;
    protected $removeExtraKeys = true;
    protected $children = [];
    protected $prototype;
    protected $atLeastOne = false;
    protected $allowNewKeys = true;
    protected $key;
    protected $removeKeyItem;
    protected $addDefaults = false;
    protected $addDefaultChildren = false;
    protected $nodeBuilder;
    protected $normalizeKeys = true;

    /**
     * {@inheritdoc}
     */
    public function __construct(?string $name, NodeParentInterface $parent = null)
    {
        parent::__construct($name, $parent);

        $this->nullEquivalent = [];
        $this->trueEquivalent = [];
    }

    /**
     * {@inheritdoc}
     */
    public function setBuilder(NodeBuilder $builder)
    {
        $this->nodeBuilder = $builder;
    }

    /**
     * {@inheritdoc}
     */
    public function children()
    {
        return $this->getNodeBuilder();
    }

    /**
     * Sets a prototype for child nodes.
     *
     * @param string $type The type of node
     *
     * @return NodeDefinition
     */
    public function prototype($type)
    {
        return $this->prototype = $this->getNodeBuilder()->node(null, $type)->setParent($this);
    }

    /**
     * @return VariableNodeDefinition
     */
    public function variablePrototype()
    {
        return $this->prototype('variable');
    }

    /**
     * @return ScalarNodeDefinition
     */
    public function scalarPrototype()
    {
        return $this->prototype('scalar');
    }

    /**
     * @return BooleanNodeDefinition
     */
    public function booleanPrototype()
    {
        return $this->prototype('boolean');
    }

    /**
     * @return IntegerNodeDefinition
     */
    public function integerPrototype()
    {
        return $this->prototype('integer');
    }

    /**
     * @return FloatNodeDefinition
     */
    public function floatPrototype()
    {
        return $this->prototype('float');
    }

    /**
     * @return ArrayNodeDefinition
     */
    public function arrayPrototype()
    {
        return $this->prototype('array');
    }

    /**
     * @return EnumNodeDefinition
     */
    public function enumPrototype()
    {
        return $this->prototype('enum');
    }

    /**
     * Adds the default value if the node is not set in the configuration.
     *
     * This method is applicable to concrete nodes only (not to prototype nodes).
     * If this function has been called and the node is not set during the finalization
     * phase, it's default value will be derived from its children default values.
     *
     * @return $this
     */
    public function addDefaultsIfNotSet()
    {
        $this->addDefaults = true;

        return $this;
    }

    /**
     * Adds children with a default value when none are defined.
     *
     * This method is applicable to prototype nodes only.
     *
     * @param int|string|array|null $children The number of children|The child name|The children names to be added
     *
     * @return $this
     */
    public function addDefaultChildrenIfNoneSet($children = null)
    {
        $this->addDefaultChildren = $children;

        return $this;
    }

    /**
     * Requires the node to have at least one element.
     *
     * This method is applicable to prototype nodes only.
     *
     * @return $this
     */
    public function requiresAtLeastOneElement()
    {
        $this->atLeastOne = true;

        return $this;
    }

    /**
     * Disallows adding news keys in a subsequent configuration.
     *
     * If used all keys have to be defined in the same configuration file.
     *
     * @return $this
     */
    public function disallowNewKeysInSubsequentConfigs()
    {
        $this->allowNewKeys = false;

        return $this;
    }

    /**
     * Sets a normalization rule for XML configurations.
     *
     * @param string $singular The key to remap
     * @param string $plural   The plural of the key for irregular plurals
     *
     * @return $this
     */
    public function fixXmlConfig($singular, $plural = null)
    {
        $this->normalization()->remap($singular, $plural);

        return $this;
    }

    /**
     * Sets the attribute which value is to be used as key.
     *
     * This is useful when you have an indexed array that should be an
     * associative array. You can select an item from within the array
     * to be the key of the particular item. For example, if "id" is the
     * "key", then:
     *
     *     [
     *         ['id' => 'my_name', 'foo' => 'bar'],
     *     ];
     *
     *   becomes
     *
     *     [
     *         'my_name' => ['foo' => 'bar'],
     *     ];
     *
     * If you'd like "'id' => 'my_name'" to still be present in the resulting
     * array, then you can set the second argument of this method to false.
     *
     * This method is applicable to prototype nodes only.
     *
     * @param string $name          The name of the key
     * @param bool   $removeKeyItem Whether or not the key item should be removed
     *
     * @return $this
     */
    public function useAttributeAsKey($name, $removeKeyItem = true)
    {
        $this->key = $name;
        $this->removeKeyItem = $removeKeyItem;

        return $this;
    }

    /**
     * Sets whether the node can be unset.
     *
     * @param bool $allow
     *
     * @return $this
     */
    public function canBeUnset($allow = true)
    {
        $this->merge()->allowUnset($allow);

        return $this;
    }

    /**
     * Adds an "enabled" boolean to enable the current section.
     *
     * By default, the section is disabled. If any configuration is specified then
     * the node will be automatically enabled:
     *
     * enableableArrayNode: {enabled: true, ...}   # The config is enabled & default values get overridden
     * enableableArrayNode: ~                      # The config is enabled & use the default values
     * enableableArrayNode: true                   # The config is enabled & use the default values
     * enableableArrayNode: {other: value, ...}    # The config is enabled & default values get overridden
     * enableableArrayNode: {enabled: false, ...}  # The config is disabled
     * enableableArrayNode: false                  # The config is disabled
     *
     * @return $this
     */
    public function canBeEnabled()
    {
        $this
            ->addDefaultsIfNotSet()
            ->treatFalseLike(['enabled' => false])
            ->treatTrueLike(['enabled' => true])
            ->treatNullLike(['enabled' => true])
            ->beforeNormalization()
                ->ifArray()
                ->then(function ($v) {
                    $v['enabled'] = isset($v['enabled']) ? $v['enabled'] : true;

                    return $v;
                })
            ->end()
            ->children()
                ->booleanNode('enabled')
                    ->defaultFalse()
        ;

        return $this;
    }

    /**
     * Adds an "enabled" boolean to enable the current section.
     *
     * By default, the section is enabled.
     *
     * @return $this
     */
    public function canBeDisabled()
    {
        $this
            ->addDefaultsIfNotSet()
            ->treatFalseLike(['enabled' => false])
            ->treatTrueLike(['enabled' => true])
            ->treatNullLike(['enabled' => true])
            ->children()
                ->booleanNode('enabled')
                    ->defaultTrue()
        ;

        return $this;
    }

    /**
     * Disables the deep merging of the node.
     *
     * @return $this
     */
    public function performNoDeepMerging()
    {
        $this->performDeepMerging = false;

        return $this;
    }

    /**
     * Allows extra config keys to be specified under an array without
     * throwing an exception.
     *
     * Those config values are ignored and removed from the resulting
     * array. This should be used only in special cases where you want
     * to send an entire configuration array through a special tree that
     * processes only part of the array.
     *
     * @param bool $remove Whether to remove the extra keys
     *
     * @return $this
     */
    public function ignoreExtraKeys($remove = true)
    {
        $this->ignoreExtraKeys = true;
        $this->removeExtraKeys = $remove;

        return $this;
    }

    /**
     * Sets key normalization.
     *
     * @param bool $bool Whether to enable key normalization
     *
     * @return $this
     */
    public function normalizeKeys($bool)
    {
        $this->normalizeKeys = (bool) $bool;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function append(NodeDefinition $node)
    {
        $this->children[$node->name] = $node->setParent($this);

        return $this;
    }

    /**
     * Returns a node builder to be used to add children and prototype.
     *
     * @return NodeBuilder The node builder
     */
    protected function getNodeBuilder()
    {
        if (null === $this->nodeBuilder) {
            $this->nodeBuilder = new NodeBuilder();
        }

        return $this->nodeBuilder->setParent($this);
    }

    /**
     * {@inheritdoc}
     */
    protected function createNode()
    {
        if (null === $this->prototype) {
            $node = new ArrayNode($this->name, $this->parent, $this->pathSeparator);

            $this->validateConcreteNode($node);

            $node->setAddIfNotSet($this->addDefaults);

            foreach ($this->children as $child) {
                $child->parent = $node;
                $node->addChild($child->getNode());
            }
        } else {
            $node = new PrototypedArrayNode($this->name, $this->parent, $this->pathSeparator);

            $this->validatePrototypeNode($node);

            if (null !== $this->key) {
                $node->setKeyAttribute($this->key, $this->removeKeyItem);
            }

            if (true === $this->atLeastOne || false === $this->allowEmptyValue) {
                $node->setMinNumberOfElements(1);
            }

            if ($this->default) {
                $node->setDefaultValue($this->defaultValue);
            }

            if (false !== $this->addDefaultChildren) {
                $node->setAddChildrenIfNoneSet($this->addDefaultChildren);
                if ($this->prototype instanceof static && null === $this->prototype->prototype) {
                    $this->prototype->addDefaultsIfNotSet();
                }
            }

            $this->prototype->parent = $node;
            $node->setPrototype($this->prototype->getNode());
        }

        $node->setAllowNewKeys($this->allowNewKeys);
        $node->addEquivalentValue(null, $this->nullEquivalent);
        $node->addEquivalentValue(true, $this->trueEquivalent);
        $node->addEquivalentValue(false, $this->falseEquivalent);
        $node->setPerformDeepMerging($this->performDeepMerging);
        $node->setRequired($this->required);
        $node->setDeprecated($this->deprecationMessage);
        $node->setIgnoreExtraKeys($this->ignoreExtraKeys, $this->removeExtraKeys);
        $node->setNormalizeKeys($this->normalizeKeys);

        if (null !== $this->normalization) {
            $node->setNormalizationClosures($this->normalization->before);
            $node->setXmlRemappings($this->normalization->remappings);
        }

        if (null !== $this->merge) {
            $node->setAllowOverwrite($this->merge->allowOverwrite);
            $node->setAllowFalse($this->merge->allowFalse);
        }

        if (null !== $this->validation) {
            $node->setFinalValidationClosures($this->validation->rules);
        }

        return $node;
    }

    /**
     * Validate the configuration of a concrete node.
     *
     * @throws InvalidDefinitionException
     */
    protected function validateConcreteNode(ArrayNode $node)
    {
        $path = $node->getPath();

        if (null !== $this->key) {
            throw new InvalidDefinitionException(sprintf('->useAttributeAsKey() is not applicable to concrete nodes at path "%s"', $path));
        }

        if (false === $this->allowEmptyValue) {
            throw new InvalidDefinitionException(sprintf('->cannotBeEmpty() is not applicable to concrete nodes at path "%s"', $path));
        }

        if (true === $this->atLeastOne) {
            throw new InvalidDefinitionException(sprintf('->requiresAtLeastOneElement() is not applicable to concrete nodes at path "%s"', $path));
        }

        if ($this->default) {
            throw new InvalidDefinitionException(sprintf('->defaultValue() is not applicable to concrete nodes at path "%s"', $path));
        }

        if (false !== $this->addDefaultChildren) {
            throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() is not applicable to concrete nodes at path "%s"', $path));
        }
    }

    /**
     * Validate the configuration of a prototype node.
     *
     * @throws InvalidDefinitionException
     */
    protected function validatePrototypeNode(PrototypedArrayNode $node)
    {
        $path = $node->getPath();

        if ($this->addDefaults) {
            throw new InvalidDefinitionException(sprintf('->addDefaultsIfNotSet() is not applicable to prototype nodes at path "%s"', $path));
        }

        if (false !== $this->addDefaultChildren) {
            if ($this->default) {
                throw new InvalidDefinitionException(sprintf('A default value and default children might not be used together at path "%s"', $path));
            }

            if (null !== $this->key && (null === $this->addDefaultChildren || \is_int($this->addDefaultChildren) && $this->addDefaultChildren > 0)) {
                throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() should set default children names as ->useAttributeAsKey() is used at path "%s"', $path));
            }

            if (null === $this->key && (\is_string($this->addDefaultChildren) || \is_array($this->addDefaultChildren))) {
                throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() might not set default children names as ->useAttributeAsKey() is not used at path "%s"', $path));
            }
        }
    }

    /**
     * @return NodeDefinition[]
     */
    public function getChildNodeDefinitions()
    {
        return $this->children;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition\Builder;

/**
 * An interface that can be implemented by nodes which build other nodes.
 *
 * @author Roland Franssen <franssen.roland@gmail.com>
 */
interface BuilderAwareInterface
{
    /**
     * Sets a custom children builder.
     */
    public function setBuilder(NodeBuilder $builder);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition\Builder;

/**
 * This class builds validation conditions.
 *
 * @author Christophe Coevoet <stof@notk.org>
 */
class ValidationBuilder
{
    protected $node;
    public $rules = [];

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

    /**
     * Registers a closure to run as normalization or an expression builder to build it if null is provided.
     *
     * @return ExprBuilder|$this
     */
    public function rule(\Closure $closure = null)
    {
        if (null !== $closure) {
            $this->rules[] = $closure;

            return $this;
        }

        return $this->rules[] = new ExprBuilder($this->node);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition\Builder;

use Symfony\Component\Config\Definition\Exception\TreeWithoutRootNodeException;
use Symfony\Component\Config\Definition\NodeInterface;

/**
 * This is the entry class for building a config tree.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class TreeBuilder implements NodeParentInterface
{
    protected $tree;
    protected $root;

    public function __construct(string $name = null, string $type = 'array', NodeBuilder $builder = null)
    {
        if (null === $name) {
            @trigger_error('A tree builder without a root node is deprecated since Symfony 4.2 and will not be supported anymore in 5.0.', E_USER_DEPRECATED);
        } else {
            $this->root($name, $type, $builder);
        }
    }

    /**
     * Creates the root node.
     *
     * @param string      $name    The name of the root node
     * @param string      $type    The type of the root node
     * @param NodeBuilder $builder A custom node builder instance
     *
     * @return ArrayNodeDefinition|NodeDefinition The root node (as an ArrayNodeDefinition when the type is 'array')
     *
     * @throws \RuntimeException When the node type is not supported
     */
    public function root($name, $type = 'array', NodeBuilder $builder = null)
    {
        $builder = $builder ?: new NodeBuilder();

        return $this->root = $builder->node($name, $type)->setParent($this);
    }

    /**
     * @return NodeDefinition|ArrayNodeDefinition The root node (as an ArrayNodeDefinition when the type is 'array')
     */
    public function getRootNode(): NodeDefinition
    {
        if (null === $this->root) {
            throw new \RuntimeException(sprintf('Calling %s() before creating the root node is not supported, migrate to the new constructor signature instead.', __METHOD__));
        }

        return $this->root;
    }

    /**
     * Builds the tree.
     *
     * @return NodeInterface
     *
     * @throws \RuntimeException
     */
    public function buildTree()
    {
        $this->assertTreeHasRootNode();
        if (null !== $this->tree) {
            return $this->tree;
        }

        return $this->tree = $this->root->getNode(true);
    }

    public function setPathSeparator(string $separator)
    {
        $this->assertTreeHasRootNode();

        // unset last built as changing path separator changes all nodes
        $this->tree = null;

        $this->root->setPathSeparator($separator);
    }

    /**
     * @throws \RuntimeException if root node is not defined
     */
    private function assertTreeHasRootNode()
    {
        if (null === $this->root) {
            throw new TreeWithoutRootNodeException('The configuration tree has no root node.');
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition\Builder;

use Symfony\Component\Config\Definition\IntegerNode;

/**
 * This class provides a fluent interface for defining an integer node.
 *
 * @author Jeanmonod David <david.jeanmonod@gmail.com>
 */
class IntegerNodeDefinition extends NumericNodeDefinition
{
    /**
     * Instantiates a Node.
     *
     * @return IntegerNode The node
     */
    protected function instantiateNode()
    {
        return new IntegerNode($this->name, $this->parent, $this->min, $this->max, $this->pathSeparator);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition\Builder;

/**
 * An interface that must be implemented by all node parents.
 *
 * @author Victor Berchet <victor@suumit.com>
 */
interface NodeParentInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition\Builder;

/**
 * This class builds normalization conditions.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class NormalizationBuilder
{
    protected $node;
    public $before = [];
    public $remappings = [];

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

    /**
     * Registers a key to remap to its plural form.
     *
     * @param string $key    The key to remap
     * @param string $plural The plural of the key in case of irregular plural
     *
     * @return $this
     */
    public function remap($key, $plural = null)
    {
        $this->remappings[] = [$key, null === $plural ? $key.'s' : $plural];

        return $this;
    }

    /**
     * Registers a closure to run before the normalization or an expression builder to build it if null is provided.
     *
     * @return ExprBuilder|$this
     */
    public function before(\Closure $closure = null)
    {
        if (null !== $closure) {
            $this->before[] = $closure;

            return $this;
        }

        return $this->before[] = new ExprBuilder($this->node);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition\Builder;

use Symfony\Component\Config\Definition\BooleanNode;
use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;

/**
 * This class provides a fluent interface for defining a node.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class BooleanNodeDefinition extends ScalarNodeDefinition
{
    /**
     * {@inheritdoc}
     */
    public function __construct(?string $name, NodeParentInterface $parent = null)
    {
        parent::__construct($name, $parent);

        $this->nullEquivalent = true;
    }

    /**
     * Instantiate a Node.
     *
     * @return BooleanNode The node
     */
    protected function instantiateNode()
    {
        return new BooleanNode($this->name, $this->parent, $this->pathSeparator);
    }

    /**
     * {@inheritdoc}
     *
     * @throws InvalidDefinitionException
     */
    public function cannotBeEmpty()
    {
        throw new InvalidDefinitionException('->cannotBeEmpty() is not applicable to BooleanNodeDefinition.');
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition\Builder;

/**
 * An interface that must be implemented by nodes which can have children.
 *
 * @author Victor Berchet <victor@suumit.com>
 *
 * @method NodeDefinition[] getChildNodeDefinitions() Gets the child node definitions - not implementing it is deprecated since Symfony 4.2
 */
interface ParentNodeDefinitionInterface extends BuilderAwareInterface
{
    /**
     * Returns a builder to add children nodes.
     *
     * @return NodeBuilder
     */
    public function children();

    /**
     * Appends a node definition.
     *
     * Usage:
     *
     *     $node = $parentNode
     *         ->children()
     *             ->scalarNode('foo')->end()
     *             ->scalarNode('baz')->end()
     *             ->append($this->getBarNodeDefinition())
     *         ->end()
     *     ;
     *
     * @return $this
     */
    public function append(NodeDefinition $node);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition\Builder;

use Symfony\Component\Config\Definition\ScalarNode;

/**
 * This class provides a fluent interface for defining a node.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class ScalarNodeDefinition extends VariableNodeDefinition
{
    /**
     * Instantiate a Node.
     *
     * @return ScalarNode The node
     */
    protected function instantiateNode()
    {
        return new ScalarNode($this->name, $this->parent, $this->pathSeparator);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition\Builder;

use Symfony\Component\Config\Definition\FloatNode;

/**
 * This class provides a fluent interface for defining a float node.
 *
 * @author Jeanmonod David <david.jeanmonod@gmail.com>
 */
class FloatNodeDefinition extends NumericNodeDefinition
{
    /**
     * Instantiates a Node.
     *
     * @return FloatNode The node
     */
    protected function instantiateNode()
    {
        return new FloatNode($this->name, $this->parent, $this->min, $this->max, $this->pathSeparator);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition\Builder;

use Symfony\Component\Config\Definition\BaseNode;
use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
use Symfony\Component\Config\Definition\NodeInterface;

/**
 * This class provides a fluent interface for defining a node.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
abstract class NodeDefinition implements NodeParentInterface
{
    protected $name;
    protected $normalization;
    protected $validation;
    protected $defaultValue;
    protected $default = false;
    protected $required = false;
    protected $deprecationMessage = null;
    protected $merge;
    protected $allowEmptyValue = true;
    protected $nullEquivalent;
    protected $trueEquivalent = true;
    protected $falseEquivalent = false;
    protected $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR;
    protected $parent;
    protected $attributes = [];

    public function __construct(?string $name, NodeParentInterface $parent = null)
    {
        $this->parent = $parent;
        $this->name = $name;
    }

    /**
     * Sets the parent node.
     *
     * @return $this
     */
    public function setParent(NodeParentInterface $parent)
    {
        $this->parent = $parent;

        return $this;
    }

    /**
     * Sets info message.
     *
     * @param string $info The info text
     *
     * @return $this
     */
    public function info($info)
    {
        return $this->attribute('info', $info);
    }

    /**
     * Sets example configuration.
     *
     * @param string|array $example
     *
     * @return $this
     */
    public function example($example)
    {
        return $this->attribute('example', $example);
    }

    /**
     * Sets an attribute on the node.
     *
     * @param string $key
     * @param mixed  $value
     *
     * @return $this
     */
    public function attribute($key, $value)
    {
        $this->attributes[$key] = $value;

        return $this;
    }

    /**
     * Returns the parent node.
     *
     * @return NodeParentInterface|NodeBuilder|NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition|null The builder of the parent node
     */
    public function end()
    {
        return $this->parent;
    }

    /**
     * Creates the node.
     *
     * @param bool $forceRootNode Whether to force this node as the root node
     *
     * @return NodeInterface
     */
    public function getNode($forceRootNode = false)
    {
        if ($forceRootNode) {
            $this->parent = null;
        }

        if (null !== $this->normalization) {
            $this->normalization->before = ExprBuilder::buildExpressions($this->normalization->before);
        }

        if (null !== $this->validation) {
            $this->validation->rules = ExprBuilder::buildExpressions($this->validation->rules);
        }

        $node = $this->createNode();
        $node->setAttributes($this->attributes);

        return $node;
    }

    /**
     * Sets the default value.
     *
     * @param mixed $value The default value
     *
     * @return $this
     */
    public function defaultValue($value)
    {
        $this->default = true;
        $this->defaultValue = $value;

        return $this;
    }

    /**
     * Sets the node as required.
     *
     * @return $this
     */
    public function isRequired()
    {
        $this->required = true;

        return $this;
    }

    /**
     * Sets the node as deprecated.
     *
     * You can use %node% and %path% placeholders in your message to display,
     * respectively, the node name and its complete path.
     *
     * @param string $message Deprecation message
     *
     * @return $this
     */
    public function setDeprecated($message = 'The child node "%node%" at path "%path%" is deprecated.')
    {
        $this->deprecationMessage = $message;

        return $this;
    }

    /**
     * Sets the equivalent value used when the node contains null.
     *
     * @param mixed $value
     *
     * @return $this
     */
    public function treatNullLike($value)
    {
        $this->nullEquivalent = $value;

        return $this;
    }

    /**
     * Sets the equivalent value used when the node contains true.
     *
     * @param mixed $value
     *
     * @return $this
     */
    public function treatTrueLike($value)
    {
        $this->trueEquivalent = $value;

        return $this;
    }

    /**
     * Sets the equivalent value used when the node contains false.
     *
     * @param mixed $value
     *
     * @return $this
     */
    public function treatFalseLike($value)
    {
        $this->falseEquivalent = $value;

        return $this;
    }

    /**
     * Sets null as the default value.
     *
     * @return $this
     */
    public function defaultNull()
    {
        return $this->defaultValue(null);
    }

    /**
     * Sets true as the default value.
     *
     * @return $this
     */
    public function defaultTrue()
    {
        return $this->defaultValue(true);
    }

    /**
     * Sets false as the default value.
     *
     * @return $this
     */
    public function defaultFalse()
    {
        return $this->defaultValue(false);
    }

    /**
     * Sets an expression to run before the normalization.
     *
     * @return ExprBuilder
     */
    public function beforeNormalization()
    {
        return $this->normalization()->before();
    }

    /**
     * Denies the node value being empty.
     *
     * @return $this
     */
    public function cannotBeEmpty()
    {
        $this->allowEmptyValue = false;

        return $this;
    }

    /**
     * Sets an expression to run for the validation.
     *
     * The expression receives the value of the node and must return it. It can
     * modify it.
     * An exception should be thrown when the node is not valid.
     *
     * @return ExprBuilder
     */
    public function validate()
    {
        return $this->validation()->rule();
    }

    /**
     * Sets whether the node can be overwritten.
     *
     * @param bool $deny Whether the overwriting is forbidden or not
     *
     * @return $this
     */
    public function cannotBeOverwritten($deny = true)
    {
        $this->merge()->denyOverwrite($deny);

        return $this;
    }

    /**
     * Gets the builder for validation rules.
     *
     * @return ValidationBuilder
     */
    protected function validation()
    {
        if (null === $this->validation) {
            $this->validation = new ValidationBuilder($this);
        }

        return $this->validation;
    }

    /**
     * Gets the builder for merging rules.
     *
     * @return MergeBuilder
     */
    protected function merge()
    {
        if (null === $this->merge) {
            $this->merge = new MergeBuilder($this);
        }

        return $this->merge;
    }

    /**
     * Gets the builder for normalization rules.
     *
     * @return NormalizationBuilder
     */
    protected function normalization()
    {
        if (null === $this->normalization) {
            $this->normalization = new NormalizationBuilder($this);
        }

        return $this->normalization;
    }

    /**
     * Instantiate and configure the node according to this definition.
     *
     * @return NodeInterface The node instance
     *
     * @throws InvalidDefinitionException When the definition is invalid
     */
    abstract protected function createNode();

    /**
     * Set PathSeparator to use.
     *
     * @param string $separator
     *
     * @return $this
     */
    public function setPathSeparator(string $separator)
    {
        if ($this instanceof ParentNodeDefinitionInterface) {
            if (method_exists($this, 'getChildNodeDefinitions')) {
                foreach ($this->getChildNodeDefinitions() as $child) {
                    $child->setPathSeparator($separator);
                }
            } else {
                @trigger_error('Passing a ParentNodeDefinitionInterface without getChildNodeDefinitions() is deprecated since Symfony 4.1.', E_USER_DEPRECATED);
            }
        }

        $this->pathSeparator = $separator;

        return $this;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition\Exception;

/**
 * This exception is thrown whenever the key of an array is not unique. This can
 * only be the case if the configuration is coming from an XML file.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class DuplicateKeyException extends InvalidConfigurationException
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition\Exception;

/**
 * Thrown when an error is detected in a node Definition.
 *
 * @author Victor Berchet <victor.berchet@suumit.com>
 */
class InvalidDefinitionException extends Exception
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition\Exception;

/**
 * This exception is thrown if an invalid type is encountered.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class InvalidTypeException extends InvalidConfigurationException
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition\Exception;

/**
 * This exception is usually not encountered by the end-user, but only used
 * internally to signal the parent scope to unset a key.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class UnsetKeyException extends Exception
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition\Exception;

/**
 * A very general exception which can be thrown whenever non of the more specific
 * exceptions is suitable.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class InvalidConfigurationException extends Exception
{
    private $path;
    private $containsHints = false;

    public function setPath($path)
    {
        $this->path = $path;
    }

    public function getPath()
    {
        return $this->path;
    }

    /**
     * Adds extra information that is suffixed to the original exception message.
     *
     * @param string $hint
     */
    public function addHint($hint)
    {
        if (!$this->containsHints) {
            $this->message .= "\nHint: ".$hint;
            $this->containsHints = true;
        } else {
            $this->message .= ', '.$hint;
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition\Exception;

/**
 * This exception is thrown when a configuration path is overwritten from a
 * subsequent configuration file, but the entry node specifically forbids this.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class ForbiddenOverwriteException extends InvalidConfigurationException
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition\Exception;

/**
 * @author Roland Franssen <franssen.roland@gmail.com>
 */
class TreeWithoutRootNodeException extends \RuntimeException
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition\Exception;

/**
 * Base exception for all configuration exceptions.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class Exception extends \RuntimeException
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition;

use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;

/**
 * This node represents a value of variable type in the config tree.
 *
 * This node is intended for values of arbitrary type.
 * Any PHP type is accepted as a value.
 *
 * @author Jeremy Mikola <jmikola@gmail.com>
 */
class VariableNode extends BaseNode implements PrototypeNodeInterface
{
    protected $defaultValueSet = false;
    protected $defaultValue;
    protected $allowEmptyValue = true;

    public function setDefaultValue($value)
    {
        $this->defaultValueSet = true;
        $this->defaultValue = $value;
    }

    /**
     * {@inheritdoc}
     */
    public function hasDefaultValue()
    {
        return $this->defaultValueSet;
    }

    /**
     * {@inheritdoc}
     */
    public function getDefaultValue()
    {
        $v = $this->defaultValue;

        return $v instanceof \Closure ? $v() : $v;
    }

    /**
     * Sets if this node is allowed to have an empty value.
     *
     * @param bool $boolean True if this entity will accept empty values
     */
    public function setAllowEmptyValue($boolean)
    {
        $this->allowEmptyValue = (bool) $boolean;
    }

    /**
     * {@inheritdoc}
     */
    public function setName($name)
    {
        $this->name = $name;
    }

    /**
     * {@inheritdoc}
     */
    protected function validateType($value)
    {
    }

    /**
     * {@inheritdoc}
     */
    protected function finalizeValue($value)
    {
        if (!$this->allowEmptyValue && $this->isValueEmpty($value)) {
            $ex = new InvalidConfigurationException(sprintf('The path "%s" cannot contain an empty value, but got %s.', $this->getPath(), json_encode($value)));
            if ($hint = $this->getInfo()) {
                $ex->addHint($hint);
            }
            $ex->setPath($this->getPath());

            throw $ex;
        }

        return $value;
    }

    /**
     * {@inheritdoc}
     */
    protected function normalizeValue($value)
    {
        return $value;
    }

    /**
     * {@inheritdoc}
     */
    protected function mergeValues($leftSide, $rightSide)
    {
        return $rightSide;
    }

    /**
     * Evaluates if the given value is to be treated as empty.
     *
     * By default, PHP's empty() function is used to test for emptiness. This
     * method may be overridden by subtypes to better match their understanding
     * of empty data.
     *
     * @param mixed $value
     *
     * @return bool
     */
    protected function isValueEmpty($value)
    {
        return empty($value);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition\Dumper;

use Symfony\Component\Config\Definition\ArrayNode;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\EnumNode;
use Symfony\Component\Config\Definition\NodeInterface;
use Symfony\Component\Config\Definition\PrototypedArrayNode;
use Symfony\Component\Config\Definition\ScalarNode;
use Symfony\Component\Yaml\Inline;

/**
 * Dumps a Yaml reference configuration for the given configuration/node instance.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
class YamlReferenceDumper
{
    private $reference;

    public function dump(ConfigurationInterface $configuration)
    {
        return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree());
    }

    public function dumpAtPath(ConfigurationInterface $configuration, $path)
    {
        $rootNode = $node = $configuration->getConfigTreeBuilder()->buildTree();

        foreach (explode('.', $path) as $step) {
            if (!$node instanceof ArrayNode) {
                throw new \UnexpectedValueException(sprintf('Unable to find node at path "%s.%s"', $rootNode->getName(), $path));
            }

            /** @var NodeInterface[] $children */
            $children = $node instanceof PrototypedArrayNode ? $this->getPrototypeChildren($node) : $node->getChildren();

            foreach ($children as $child) {
                if ($child->getName() === $step) {
                    $node = $child;

                    continue 2;
                }
            }

            throw new \UnexpectedValueException(sprintf('Unable to find node at path "%s.%s"', $rootNode->getName(), $path));
        }

        return $this->dumpNode($node);
    }

    public function dumpNode(NodeInterface $node)
    {
        $this->reference = '';
        $this->writeNode($node);
        $ref = $this->reference;
        $this->reference = null;

        return $ref;
    }

    private function writeNode(NodeInterface $node, NodeInterface $parentNode = null, int $depth = 0, bool $prototypedArray = false)
    {
        $comments = [];
        $default = '';
        $defaultArray = null;
        $children = null;
        $example = $node->getExample();

        // defaults
        if ($node instanceof ArrayNode) {
            $children = $node->getChildren();

            if ($node instanceof PrototypedArrayNode) {
                $children = $this->getPrototypeChildren($node);
            }

            if (!$children) {
                if ($node->hasDefaultValue() && \count($defaultArray = $node->getDefaultValue())) {
                    $default = '';
                } elseif (!\is_array($example)) {
                    $default = '[]';
                }
            }
        } elseif ($node instanceof EnumNode) {
            $comments[] = 'One of '.implode('; ', array_map('json_encode', $node->getValues()));
            $default = $node->hasDefaultValue() ? Inline::dump($node->getDefaultValue()) : '~';
        } else {
            $default = '~';

            if ($node->hasDefaultValue()) {
                $default = $node->getDefaultValue();

                if (\is_array($default)) {
                    if (\count($defaultArray = $node->getDefaultValue())) {
                        $default = '';
                    } elseif (!\is_array($example)) {
                        $default = '[]';
                    }
                } else {
                    $default = Inline::dump($default);
                }
            }
        }

        // required?
        if ($node->isRequired()) {
            $comments[] = 'Required';
        }

        // deprecated?
        if ($node->isDeprecated()) {
            $comments[] = sprintf('Deprecated (%s)', $node->getDeprecationMessage($node->getName(), $parentNode ? $parentNode->getPath() : $node->getPath()));
        }

        // example
        if ($example && !\is_array($example)) {
            $comments[] = 'Example: '.$example;
        }

        $default = '' != (string) $default ? ' '.$default : '';
        $comments = \count($comments) ? '# '.implode(', ', $comments) : '';

        $key = $prototypedArray ? '-' : $node->getName().':';
        $text = rtrim(sprintf('%-21s%s %s', $key, $default, $comments), ' ');

        if ($info = $node->getInfo()) {
            $this->writeLine('');
            // indenting multi-line info
            $info = str_replace("\n", sprintf("\n%".($depth * 4).'s# ', ' '), $info);
            $this->writeLine('# '.$info, $depth * 4);
        }

        $this->writeLine($text, $depth * 4);

        // output defaults
        if ($defaultArray) {
            $this->writeLine('');

            $message = \count($defaultArray) > 1 ? 'Defaults' : 'Default';

            $this->writeLine('# '.$message.':', $depth * 4 + 4);

            $this->writeArray($defaultArray, $depth + 1);
        }

        if (\is_array($example)) {
            $this->writeLine('');

            $message = \count($example) > 1 ? 'Examples' : 'Example';

            $this->writeLine('# '.$message.':', $depth * 4 + 4);

            $this->writeArray($example, $depth + 1);
        }

        if ($children) {
            foreach ($children as $childNode) {
                $this->writeNode($childNode, $node, $depth + 1, $node instanceof PrototypedArrayNode && !$node->getKeyAttribute());
            }
        }
    }

    /**
     * Outputs a single config reference line.
     */
    private function writeLine(string $text, int $indent = 0)
    {
        $indent = \strlen($text) + $indent;
        $format = '%'.$indent.'s';

        $this->reference .= sprintf($format, $text)."\n";
    }

    private function writeArray(array $array, $depth)
    {
        $isIndexed = array_values($array) === $array;

        foreach ($array as $key => $value) {
            if (\is_array($value)) {
                $val = '';
            } else {
                $val = $value;
            }

            if ($isIndexed) {
                $this->writeLine('- '.$val, $depth * 4);
            } else {
                $this->writeLine(sprintf('%-20s %s', $key.':', $val), $depth * 4);
            }

            if (\is_array($value)) {
                $this->writeArray($value, $depth + 1);
            }
        }
    }

    private function getPrototypeChildren(PrototypedArrayNode $node): array
    {
        $prototype = $node->getPrototype();
        $key = $node->getKeyAttribute();

        // Do not expand prototype if it isn't an array node nor uses attribute as key
        if (!$key && !$prototype instanceof ArrayNode) {
            return $node->getChildren();
        }

        if ($prototype instanceof ArrayNode) {
            $keyNode = new ArrayNode($key, $node);
            $children = $prototype->getChildren();

            if ($prototype instanceof PrototypedArrayNode && $prototype->getKeyAttribute()) {
                $children = $this->getPrototypeChildren($prototype);
            }

            // add children
            foreach ($children as $childNode) {
                $keyNode->addChild($childNode);
            }
        } else {
            $keyNode = new ScalarNode($key, $node);
        }

        $info = 'Prototype';
        if (null !== $prototype->getInfo()) {
            $info .= ': '.$prototype->getInfo();
        }
        $keyNode->setInfo($info);

        return [$key => $keyNode];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Definition\Dumper;

use Symfony\Component\Config\Definition\ArrayNode;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\EnumNode;
use Symfony\Component\Config\Definition\NodeInterface;
use Symfony\Component\Config\Definition\PrototypedArrayNode;

/**
 * Dumps a XML reference configuration for the given configuration/node instance.
 *
 * @author Wouter J <waldio.webdesign@gmail.com>
 */
class XmlReferenceDumper
{
    private $reference;

    public function dump(ConfigurationInterface $configuration, $namespace = null)
    {
        return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree(), $namespace);
    }

    public function dumpNode(NodeInterface $node, $namespace = null)
    {
        $this->reference = '';
        $this->writeNode($node, 0, true, $namespace);
        $ref = $this->reference;
        $this->reference = null;

        return $ref;
    }

    private function writeNode(NodeInterface $node, int $depth = 0, bool $root = false, string $namespace = null)
    {
        $rootName = ($root ? 'config' : $node->getName());
        $rootNamespace = ($namespace ?: ($root ? 'http://example.org/schema/dic/'.$node->getName() : null));

        // xml remapping
        if ($node->getParent()) {
            $remapping = array_filter($node->getParent()->getXmlRemappings(), function ($mapping) use ($rootName) {
                return $rootName === $mapping[1];
            });

            if (\count($remapping)) {
                list($singular) = current($remapping);
                $rootName = $singular;
            }
        }
        $rootName = str_replace('_', '-', $rootName);

        $rootAttributes = [];
        $rootAttributeComments = [];
        $rootChildren = [];
        $rootComments = [];

        if ($node instanceof ArrayNode) {
            $children = $node->getChildren();

            // comments about the root node
            if ($rootInfo = $node->getInfo()) {
                $rootComments[] = $rootInfo;
            }

            if ($rootNamespace) {
                $rootComments[] = 'Namespace: '.$rootNamespace;
            }

            // render prototyped nodes
            if ($node instanceof PrototypedArrayNode) {
                $prototype = $node->getPrototype();

                $info = 'prototype';
                if (null !== $prototype->getInfo()) {
                    $info .= ': '.$prototype->getInfo();
                }
                array_unshift($rootComments, $info);

                if ($key = $node->getKeyAttribute()) {
                    $rootAttributes[$key] = str_replace('-', ' ', $rootName).' '.$key;
                }

                if ($prototype instanceof PrototypedArrayNode) {
                    $prototype->setName($key);
                    $children = [$key => $prototype];
                } elseif ($prototype instanceof ArrayNode) {
                    $children = $prototype->getChildren();
                } else {
                    if ($prototype->hasDefaultValue()) {
                        $prototypeValue = $prototype->getDefaultValue();
                    } else {
                        switch (\get_class($prototype)) {
                            case 'Symfony\Component\Config\Definition\ScalarNode':
                                $prototypeValue = 'scalar value';
                                break;

                            case 'Symfony\Component\Config\Definition\FloatNode':
                            case 'Symfony\Component\Config\Definition\IntegerNode':
                                $prototypeValue = 'numeric value';
                                break;

                            case 'Symfony\Component\Config\Definition\BooleanNode':
                                $prototypeValue = 'true|false';
                                break;

                            case 'Symfony\Component\Config\Definition\EnumNode':
                                $prototypeValue = implode('|', array_map('json_encode', $prototype->getValues()));
                                break;

                            default:
                                $prototypeValue = 'value';
                        }
                    }
                }
            }

            // get attributes and elements
            foreach ($children as $child) {
                if (!$child instanceof ArrayNode) {
                    // get attributes

                    // metadata
                    $name = str_replace('_', '-', $child->getName());
                    $value = '%%%%not_defined%%%%'; // use a string which isn't used in the normal world

                    // comments
                    $comments = [];
                    if ($info = $child->getInfo()) {
                        $comments[] = $info;
                    }

                    if ($example = $child->getExample()) {
                        $comments[] = 'Example: '.$example;
                    }

                    if ($child->isRequired()) {
                        $comments[] = 'Required';
                    }

                    if ($child->isDeprecated()) {
                        $comments[] = sprintf('Deprecated (%s)', $child->getDeprecationMessage($child->getName(), $node->getPath()));
                    }

                    if ($child instanceof EnumNode) {
                        $comments[] = 'One of '.implode('; ', array_map('json_encode', $child->getValues()));
                    }

                    if (\count($comments)) {
                        $rootAttributeComments[$name] = implode(";\n", $comments);
                    }

                    // default values
                    if ($child->hasDefaultValue()) {
                        $value = $child->getDefaultValue();
                    }

                    // append attribute
                    $rootAttributes[$name] = $value;
                } else {
                    // get elements
                    $rootChildren[] = $child;
                }
            }
        }

        // render comments

        // root node comment
        if (\count($rootComments)) {
            foreach ($rootComments as $comment) {
                $this->writeLine('<!-- '.$comment.' -->', $depth);
            }
        }

        // attribute comments
        if (\count($rootAttributeComments)) {
            foreach ($rootAttributeComments as $attrName => $comment) {
                $commentDepth = $depth + 4 + \strlen($attrName) + 2;
                $commentLines = explode("\n", $comment);
                $multiline = (\count($commentLines) > 1);
                $comment = implode(PHP_EOL.str_repeat(' ', $commentDepth), $commentLines);

                if ($multiline) {
                    $this->writeLine('<!--', $depth);
                    $this->writeLine($attrName.': '.$comment, $depth + 4);
                    $this->writeLine('-->', $depth);
                } else {
                    $this->writeLine('<!-- '.$attrName.': '.$comment.' -->', $depth);
                }
            }
        }

        // render start tag + attributes
        $rootIsVariablePrototype = isset($prototypeValue);
        $rootIsEmptyTag = (0 === \count($rootChildren) && !$rootIsVariablePrototype);
        $rootOpenTag = '<'.$rootName;
        if (1 >= ($attributesCount = \count($rootAttributes))) {
            if (1 === $attributesCount) {
                $rootOpenTag .= sprintf(' %s="%s"', current(array_keys($rootAttributes)), $this->writeValue(current($rootAttributes)));
            }

            $rootOpenTag .= $rootIsEmptyTag ? ' />' : '>';

            if ($rootIsVariablePrototype) {
                $rootOpenTag .= $prototypeValue.'</'.$rootName.'>';
            }

            $this->writeLine($rootOpenTag, $depth);
        } else {
            $this->writeLine($rootOpenTag, $depth);

            $i = 1;

            foreach ($rootAttributes as $attrName => $attrValue) {
                $attr = sprintf('%s="%s"', $attrName, $this->writeValue($attrValue));

                $this->writeLine($attr, $depth + 4);

                if ($attributesCount === $i++) {
                    $this->writeLine($rootIsEmptyTag ? '/>' : '>', $depth);

                    if ($rootIsVariablePrototype) {
                        $rootOpenTag .= $prototypeValue.'</'.$rootName.'>';
                    }
                }
            }
        }

        // render children tags
        foreach ($rootChildren as $child) {
            $this->writeLine('');
            $this->writeNode($child, $depth + 4);
        }

        // render end tag
        if (!$rootIsEmptyTag && !$rootIsVariablePrototype) {
            $this->writeLine('');

            $rootEndTag = '</'.$rootName.'>';
            $this->writeLine($rootEndTag, $depth);
        }
    }

    /**
     * Outputs a single config reference line.
     */
    private function writeLine(string $text, int $indent = 0)
    {
        $indent = \strlen($text) + $indent;
        $format = '%'.$indent.'s';

        $this->reference .= sprintf($format, $text).PHP_EOL;
    }

    /**
     * Renders the string conversion of the value.
     *
     * @param mixed $value
     */
    private function writeValue($value): string
    {
        if ('%%%%not_defined%%%%' === $value) {
            return '';
        }

        if (\is_string($value) || is_numeric($value)) {
            return $value;
        }

        if (false === $value) {
            return 'false';
        }

        if (true === $value) {
            return 'true';
        }

        if (null === $value) {
            return 'null';
        }

        if (empty($value)) {
            return '';
        }

        if (\is_array($value)) {
            return implode(',', $value);
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Resource;

/**
 * Interface for Resources that can check for freshness autonomously,
 * without special support from external services.
 *
 * @author Matthias Pigulla <mp@webfactory.de>
 */
interface SelfCheckingResourceInterface extends ResourceInterface
{
    /**
     * Returns true if the resource has not been updated since the given timestamp.
     *
     * @param int $timestamp The last time the resource was loaded
     *
     * @return bool True if the resource has not been updated, false otherwise
     */
    public function isFresh($timestamp);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Resource;

/**
 * FileResource represents a resource stored on the filesystem.
 *
 * The resource can be a file or a directory.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class FileResource implements SelfCheckingResourceInterface, \Serializable
{
    /**
     * @var string|false
     */
    private $resource;

    /**
     * @param string $resource The file path to the resource
     *
     * @throws \InvalidArgumentException
     */
    public function __construct(string $resource)
    {
        $this->resource = realpath($resource) ?: (file_exists($resource) ? $resource : false);

        if (false === $this->resource) {
            throw new \InvalidArgumentException(sprintf('The file "%s" does not exist.', $resource));
        }
    }

    /**
     * {@inheritdoc}
     */
    public function __toString()
    {
        return $this->resource;
    }

    /**
     * @return string The canonicalized, absolute path to the resource
     */
    public function getResource()
    {
        return $this->resource;
    }

    /**
     * {@inheritdoc}
     */
    public function isFresh($timestamp)
    {
        return false !== ($filemtime = @filemtime($this->resource)) && $filemtime <= $timestamp;
    }

    /**
     * @internal
     */
    public function serialize()
    {
        return serialize($this->resource);
    }

    /**
     * @internal
     */
    public function unserialize($serialized)
    {
        $this->resource = unserialize($serialized);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Resource;

use Symfony\Component\DependencyInjection\ServiceSubscriberInterface as LegacyServiceSubscriberInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;

/**
 * @author Nicolas Grekas <p@tchwork.com>
 */
class ReflectionClassResource implements SelfCheckingResourceInterface, \Serializable
{
    private $files = [];
    private $className;
    private $classReflector;
    private $excludedVendors = [];
    private $hash;

    public function __construct(\ReflectionClass $classReflector, array $excludedVendors = [])
    {
        $this->className = $classReflector->name;
        $this->classReflector = $classReflector;
        $this->excludedVendors = $excludedVendors;
    }

    public function isFresh($timestamp)
    {
        if (null === $this->hash) {
            $this->hash = $this->computeHash();
            $this->loadFiles($this->classReflector);
        }

        foreach ($this->files as $file => $v) {
            if (false === $filemtime = @filemtime($file)) {
                return false;
            }

            if ($filemtime > $timestamp) {
                return $this->hash === $this->computeHash();
            }
        }

        return true;
    }

    public function __toString()
    {
        return 'reflection.'.$this->className;
    }

    /**
     * @internal
     */
    public function serialize()
    {
        if (null === $this->hash) {
            $this->hash = $this->computeHash();
            $this->loadFiles($this->classReflector);
        }

        return serialize([$this->files, $this->className, $this->hash]);
    }

    /**
     * @internal
     */
    public function unserialize($serialized)
    {
        list($this->files, $this->className, $this->hash) = unserialize($serialized);
    }

    private function loadFiles(\ReflectionClass $class)
    {
        foreach ($class->getInterfaces() as $v) {
            $this->loadFiles($v);
        }
        do {
            $file = $class->getFileName();
            if (false !== $file && file_exists($file)) {
                foreach ($this->excludedVendors as $vendor) {
                    if (0 === strpos($file, $vendor) && false !== strpbrk(substr($file, \strlen($vendor), 1), '/'.\DIRECTORY_SEPARATOR)) {
                        $file = false;
                        break;
                    }
                }
                if ($file) {
                    $this->files[$file] = null;
                }
            }
            foreach ($class->getTraits() as $v) {
                $this->loadFiles($v);
            }
        } while ($class = $class->getParentClass());
    }

    private function computeHash()
    {
        if (null === $this->classReflector) {
            try {
                $this->classReflector = new \ReflectionClass($this->className);
            } catch (\ReflectionException $e) {
                // the class does not exist anymore
                return false;
            }
        }
        $hash = hash_init('md5');

        foreach ($this->generateSignature($this->classReflector) as $info) {
            hash_update($hash, $info);
        }

        return hash_final($hash);
    }

    private function generateSignature(\ReflectionClass $class)
    {
        yield $class->getDocComment();
        yield (int) $class->isFinal();
        yield (int) $class->isAbstract();

        if ($class->isTrait()) {
            yield print_r(class_uses($class->name), true);
        } else {
            yield print_r(class_parents($class->name), true);
            yield print_r(class_implements($class->name), true);
            yield print_r($class->getConstants(), true);
        }

        if (!$class->isInterface()) {
            $defaults = $class->getDefaultProperties();

            foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED) as $p) {
                yield $p->getDocComment().$p;
                yield print_r($defaults[$p->name], true);
            }
        }

        foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $m) {
            yield preg_replace('/^  @@.*/m', '', $m);

            $defaults = [];
            foreach ($m->getParameters() as $p) {
                $defaults[$p->name] = $p->isDefaultValueAvailable() ? $p->getDefaultValue() : null;
            }
            yield print_r($defaults, true);
        }

        if ($class->isAbstract() || $class->isInterface() || $class->isTrait()) {
            return;
        }

        if (interface_exists(EventSubscriberInterface::class, false) && $class->isSubclassOf(EventSubscriberInterface::class)) {
            yield EventSubscriberInterface::class;
            yield print_r($class->name::getSubscribedEvents(), true);
        }

        if (interface_exists(LegacyServiceSubscriberInterface::class, false) && $class->isSubclassOf(LegacyServiceSubscriberInterface::class)) {
            yield LegacyServiceSubscriberInterface::class;
            yield print_r([$class->name, 'getSubscribedServices'](), true);
        } elseif (interface_exists(ServiceSubscriberInterface::class, false) && $class->isSubclassOf(ServiceSubscriberInterface::class)) {
            yield ServiceSubscriberInterface::class;
            yield print_r($class->name::getSubscribedServices(), true);
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Resource;

use Symfony\Component\Config\ResourceCheckerInterface;

/**
 * Resource checker for instances of SelfCheckingResourceInterface.
 *
 * As these resources perform the actual check themselves, we can provide
 * this class as a standard way of validating them.
 *
 * @author Matthias Pigulla <mp@webfactory.de>
 */
class SelfCheckingResourceChecker implements ResourceCheckerInterface
{
    public function supports(ResourceInterface $metadata)
    {
        return $metadata instanceof SelfCheckingResourceInterface;
    }

    public function isFresh(ResourceInterface $resource, $timestamp)
    {
        /* @var SelfCheckingResourceInterface $resource */
        return $resource->isFresh($timestamp);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Resource;

/**
 * ComposerResource tracks the PHP version and Composer dependencies.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class ComposerResource implements SelfCheckingResourceInterface, \Serializable
{
    private $vendors;

    private static $runtimeVendors;

    public function __construct()
    {
        self::refresh();
        $this->vendors = self::$runtimeVendors;
    }

    public function getVendors()
    {
        return array_keys($this->vendors);
    }

    /**
     * {@inheritdoc}
     */
    public function __toString()
    {
        return __CLASS__;
    }

    /**
     * {@inheritdoc}
     */
    public function isFresh($timestamp)
    {
        self::refresh();

        return self::$runtimeVendors === $this->vendors;
    }

    /**
     * @internal
     */
    public function serialize()
    {
        return serialize($this->vendors);
    }

    /**
     * @internal
     */
    public function unserialize($serialized)
    {
        $this->vendors = unserialize($serialized);
    }

    private static function refresh()
    {
        self::$runtimeVendors = [];

        foreach (get_declared_classes() as $class) {
            if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) {
                $r = new \ReflectionClass($class);
                $v = \dirname(\dirname($r->getFileName()));
                if (file_exists($v.'/composer/installed.json')) {
                    self::$runtimeVendors[$v] = @filemtime($v.'/composer/installed.json');
                }
            }
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Resource;

/**
 * FileExistenceResource represents a resource stored on the filesystem.
 * Freshness is only evaluated against resource creation or deletion.
 *
 * The resource can be a file or a directory.
 *
 * @author Charles-Henri Bruyand <charleshenri.bruyand@gmail.com>
 */
class FileExistenceResource implements SelfCheckingResourceInterface, \Serializable
{
    private $resource;

    private $exists;

    /**
     * @param string $resource The file path to the resource
     */
    public function __construct(string $resource)
    {
        $this->resource = $resource;
        $this->exists = file_exists($resource);
    }

    /**
     * {@inheritdoc}
     */
    public function __toString()
    {
        return $this->resource;
    }

    /**
     * @return string The file path to the resource
     */
    public function getResource()
    {
        return $this->resource;
    }

    /**
     * {@inheritdoc}
     */
    public function isFresh($timestamp)
    {
        return file_exists($this->resource) === $this->exists;
    }

    /**
     * @internal
     */
    public function serialize()
    {
        return serialize([$this->resource, $this->exists]);
    }

    /**
     * @internal
     */
    public function unserialize($serialized)
    {
        list($this->resource, $this->exists) = unserialize($serialized);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Resource;

/**
 * ResourceInterface is the interface that must be implemented by all Resource classes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
interface ResourceInterface
{
    /**
     * Returns a string representation of the Resource.
     *
     * This method is necessary to allow for resource de-duplication, for example by means
     * of array_unique(). The string returned need not have a particular meaning, but has
     * to be identical for different ResourceInterface instances referring to the same
     * resource; and it should be unlikely to collide with that of other, unrelated
     * resource instances.
     *
     * @return string A string representation unique to the underlying Resource
     */
    public function __toString();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Resource;

/**
 * ClassExistenceResource represents a class existence.
 * Freshness is only evaluated against resource existence.
 *
 * The resource must be a fully-qualified class name.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializable
{
    private $resource;
    private $exists;

    private static $autoloadLevel = 0;
    private static $autoloadedClass;
    private static $existsCache = [];

    /**
     * @param string    $resource The fully-qualified class name
     * @param bool|null $exists   Boolean when the existency check has already been done
     */
    public function __construct(string $resource, bool $exists = null)
    {
        $this->resource = $resource;
        $this->exists = $exists;
    }

    /**
     * {@inheritdoc}
     */
    public function __toString()
    {
        return $this->resource;
    }

    /**
     * @return string The file path to the resource
     */
    public function getResource()
    {
        return $this->resource;
    }

    /**
     * {@inheritdoc}
     *
     * @throws \ReflectionException when a parent class/interface/trait is not found
     */
    public function isFresh($timestamp)
    {
        $loaded = class_exists($this->resource, false) || interface_exists($this->resource, false) || trait_exists($this->resource, false);

        if (null !== $exists = &self::$existsCache[(int) (0 >= $timestamp)][$this->resource]) {
            $exists = $exists || $loaded;
        } elseif (!$exists = $loaded) {
            if (!self::$autoloadLevel++) {
                spl_autoload_register(__CLASS__.'::throwOnRequiredClass');
            }
            $autoloadedClass = self::$autoloadedClass;
            self::$autoloadedClass = $this->resource;

            try {
                $exists = class_exists($this->resource) || interface_exists($this->resource, false) || trait_exists($this->resource, false);
            } catch (\ReflectionException $e) {
                if (0 >= $timestamp) {
                    unset(self::$existsCache[1][$this->resource]);
                    throw $e;
                }
            } finally {
                self::$autoloadedClass = $autoloadedClass;
                if (!--self::$autoloadLevel) {
                    spl_autoload_unregister(__CLASS__.'::throwOnRequiredClass');
                }
            }
        }

        if (null === $this->exists) {
            $this->exists = $exists;
        }

        return $this->exists xor !$exists;
    }

    /**
     * @internal
     */
    public function serialize()
    {
        if (null === $this->exists) {
            $this->isFresh(0);
        }

        return serialize([$this->resource, $this->exists]);
    }

    /**
     * @internal
     */
    public function unserialize($serialized)
    {
        list($this->resource, $this->exists) = unserialize($serialized);
    }

    /**
     * @throws \ReflectionException When $class is not found and is required
     *
     * @internal
     */
    public static function throwOnRequiredClass($class)
    {
        if (self::$autoloadedClass === $class) {
            return;
        }
        $e = new \ReflectionException("Class $class not found");
        $trace = $e->getTrace();
        $autoloadFrame = [
            'function' => 'spl_autoload_call',
            'args' => [$class],
        ];
        $i = 1 + array_search($autoloadFrame, $trace, true);

        if (isset($trace[$i]['function']) && !isset($trace[$i]['class'])) {
            switch ($trace[$i]['function']) {
                case 'get_class_methods':
                case 'get_class_vars':
                case 'get_parent_class':
                case 'is_a':
                case 'is_subclass_of':
                case 'class_exists':
                case 'class_implements':
                case 'class_parents':
                case 'trait_exists':
                case 'defined':
                case 'interface_exists':
                case 'method_exists':
                case 'property_exists':
                case 'is_callable':
                    return;
            }

            $props = [
                'file' => $trace[$i]['file'],
                'line' => $trace[$i]['line'],
                'trace' => \array_slice($trace, 1 + $i),
            ];

            foreach ($props as $p => $v) {
                $r = new \ReflectionProperty('Exception', $p);
                $r->setAccessible(true);
                $r->setValue($e, $v);
            }
        }

        throw $e;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Resource;

/**
 * DirectoryResource represents a resources stored in a subdirectory tree.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class DirectoryResource implements SelfCheckingResourceInterface, \Serializable
{
    private $resource;
    private $pattern;

    /**
     * @param string      $resource The file path to the resource
     * @param string|null $pattern  A pattern to restrict monitored files
     *
     * @throws \InvalidArgumentException
     */
    public function __construct(string $resource, string $pattern = null)
    {
        $this->resource = realpath($resource) ?: (file_exists($resource) ? $resource : false);
        $this->pattern = $pattern;

        if (false === $this->resource || !is_dir($this->resource)) {
            throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist.', $resource));
        }
    }

    /**
     * {@inheritdoc}
     */
    public function __toString()
    {
        return md5(serialize([$this->resource, $this->pattern]));
    }

    /**
     * @return string The file path to the resource
     */
    public function getResource()
    {
        return $this->resource;
    }

    /**
     * Returns the pattern to restrict monitored files.
     *
     * @return string|null
     */
    public function getPattern()
    {
        return $this->pattern;
    }

    /**
     * {@inheritdoc}
     */
    public function isFresh($timestamp)
    {
        if (!is_dir($this->resource)) {
            return false;
        }

        if ($timestamp < filemtime($this->resource)) {
            return false;
        }

        foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->resource), \RecursiveIteratorIterator::SELF_FIRST) as $file) {
            // if regex filtering is enabled only check matching files
            if ($this->pattern && $file->isFile() && !preg_match($this->pattern, $file->getBasename())) {
                continue;
            }

            // always monitor directories for changes, except the .. entries
            // (otherwise deleted files wouldn't get detected)
            if ($file->isDir() && '/..' === substr($file, -3)) {
                continue;
            }

            // for broken links
            try {
                $fileMTime = $file->getMTime();
            } catch (\RuntimeException $e) {
                continue;
            }

            // early return if a file's mtime exceeds the passed timestamp
            if ($timestamp < $fileMTime) {
                return false;
            }
        }

        return true;
    }

    /**
     * @internal
     */
    public function serialize()
    {
        return serialize([$this->resource, $this->pattern]);
    }

    /**
     * @internal
     */
    public function unserialize($serialized)
    {
        list($this->resource, $this->pattern) = unserialize($serialized);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Resource;

use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\Glob;

/**
 * GlobResource represents a set of resources stored on the filesystem.
 *
 * Only existence/removal is tracked (not mtimes.)
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class GlobResource implements \IteratorAggregate, SelfCheckingResourceInterface, \Serializable
{
    private $prefix;
    private $pattern;
    private $recursive;
    private $hash;
    private $forExclusion;
    private $excludedPrefixes;

    /**
     * @param string $prefix    A directory prefix
     * @param string $pattern   A glob pattern
     * @param bool   $recursive Whether directories should be scanned recursively or not
     *
     * @throws \InvalidArgumentException
     */
    public function __construct(?string $prefix, string $pattern, bool $recursive, bool $forExclusion = false, array $excludedPrefixes = [])
    {
        $this->prefix = realpath($prefix) ?: (file_exists($prefix) ? $prefix : false);
        $this->pattern = $pattern;
        $this->recursive = $recursive;
        $this->forExclusion = $forExclusion;
        $this->excludedPrefixes = $excludedPrefixes;

        if (false === $this->prefix) {
            throw new \InvalidArgumentException(sprintf('The path "%s" does not exist.', $prefix));
        }
    }

    public function getPrefix()
    {
        return $this->prefix;
    }

    /**
     * {@inheritdoc}
     */
    public function __toString()
    {
        return 'glob.'.$this->prefix.$this->pattern.(int) $this->recursive;
    }

    /**
     * {@inheritdoc}
     */
    public function isFresh($timestamp)
    {
        $hash = $this->computeHash();

        if (null === $this->hash) {
            $this->hash = $hash;
        }

        return $this->hash === $hash;
    }

    /**
     * @internal
     */
    public function serialize()
    {
        if (null === $this->hash) {
            $this->hash = $this->computeHash();
        }

        return serialize([$this->prefix, $this->pattern, $this->recursive, $this->hash, $this->forExclusion, $this->excludedPrefixes]);
    }

    /**
     * @internal
     */
    public function unserialize($serialized)
    {
        list($this->prefix, $this->pattern, $this->recursive, $this->hash, $this->forExclusion, $this->excludedPrefixes) = unserialize($serialized) + [4 => false, []];
    }

    public function getIterator()
    {
        if (!file_exists($this->prefix) || (!$this->recursive && '' === $this->pattern)) {
            return;
        }
        $prefix = str_replace('\\', '/', $this->prefix);

        if (0 !== strpos($this->prefix, 'phar://') && false === strpos($this->pattern, '/**/') && (\defined('GLOB_BRACE') || false === strpos($this->pattern, '{'))) {
            foreach (glob($this->prefix.$this->pattern, \defined('GLOB_BRACE') ? GLOB_BRACE : 0) as $path) {
                if ($this->excludedPrefixes) {
                    $normalizedPath = str_replace('\\', '/', $path);
                    do {
                        if (isset($this->excludedPrefixes[$dirPath = $normalizedPath])) {
                            continue 2;
                        }
                    } while ($prefix !== $dirPath && $dirPath !== $normalizedPath = \dirname($dirPath));
                }

                if (is_file($path)) {
                    yield $path => new \SplFileInfo($path);
                }
                if (!is_dir($path)) {
                    continue;
                }
                if ($this->forExclusion) {
                    yield $path => new \SplFileInfo($path);
                    continue;
                }
                if (!$this->recursive || isset($this->excludedPrefixes[str_replace('\\', '/', $path)])) {
                    continue;
                }
                $files = iterator_to_array(new \RecursiveIteratorIterator(
                    new \RecursiveCallbackFilterIterator(
                        new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS),
                        function (\SplFileInfo $file, $path) {
                            return !isset($this->excludedPrefixes[str_replace('\\', '/', $path)]) && '.' !== $file->getBasename()[0];
                        }
                    ),
                    \RecursiveIteratorIterator::LEAVES_ONLY
                ));
                uasort($files, 'strnatcmp');

                foreach ($files as $path => $info) {
                    if ($info->isFile()) {
                        yield $path => $info;
                    }
                }
            }

            return;
        }

        if (!class_exists(Finder::class)) {
            throw new \LogicException(sprintf('Extended glob pattern "%s" cannot be used as the Finder component is not installed.', $this->pattern));
        }

        $finder = new Finder();
        $regex = Glob::toRegex($this->pattern);
        if ($this->recursive) {
            $regex = substr_replace($regex, '(/|$)', -2, 1);
        }

        $prefixLen = \strlen($this->prefix);
        foreach ($finder->followLinks()->sortByName()->in($this->prefix) as $path => $info) {
            $normalizedPath = str_replace('\\', '/', $path);
            if (!preg_match($regex, substr($normalizedPath, $prefixLen)) || !$info->isFile()) {
                continue;
            }
            if ($this->excludedPrefixes) {
                do {
                    if (isset($this->excludedPrefixes[$dirPath = $normalizedPath])) {
                        continue 2;
                    }
                } while ($prefix !== $dirPath && $dirPath !== $normalizedPath = \dirname($dirPath));
            }

            yield $path => $info;
        }
    }

    private function computeHash()
    {
        $hash = hash_init('md5');

        foreach ($this->getIterator() as $path => $info) {
            hash_update($hash, $path."\n");
        }

        return hash_final($hash);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Exception;

/**
 * Exception class for when a resource cannot be loaded or imported.
 *
 * @author Ryan Weaver <ryan@thatsquality.com>
 */
class LoaderLoadException extends FileLoaderLoadException
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Exception;

/**
 * Exception class for when a resource cannot be loaded or imported.
 *
 * @author Ryan Weaver <ryan@thatsquality.com>
 *
 * @deprecated since Symfony 4.2, use LoaderLoadException instead.
 */
class FileLoaderLoadException extends \Exception
{
    /**
     * @param string     $resource       The resource that could not be imported
     * @param string     $sourceResource The original resource importing the new resource
     * @param int        $code           The error code
     * @param \Exception $previous       A previous exception
     * @param string     $type           The type of resource
     */
    public function __construct(string $resource, string $sourceResource = null, int $code = null, \Exception $previous = null, string $type = null)
    {
        $message = '';
        if ($previous) {
            // Include the previous exception, to help the user see what might be the underlying cause

            // Trim the trailing period of the previous message. We only want 1 period remove so no rtrim...
            if ('.' === substr($previous->getMessage(), -1)) {
                $trimmedMessage = substr($previous->getMessage(), 0, -1);
                $message .= sprintf('%s', $trimmedMessage).' in ';
            } else {
                $message .= sprintf('%s', $previous->getMessage()).' in ';
            }
            $message .= $resource.' ';

            // show tweaked trace to complete the human readable sentence
            if (null === $sourceResource) {
                $message .= sprintf('(which is loaded in resource "%s")', $resource);
            } else {
                $message .= sprintf('(which is being imported from "%s")', $sourceResource);
            }
            $message .= '.';

        // if there's no previous message, present it the default way
        } elseif (null === $sourceResource) {
            $message .= sprintf('Cannot load resource "%s".', $resource);
        } else {
            $message .= sprintf('Cannot import resource "%s" from "%s".', $resource, $sourceResource);
        }

        // Is the resource located inside a bundle?
        if ('@' === $resource[0]) {
            $parts = explode(\DIRECTORY_SEPARATOR, $resource);
            $bundle = substr($parts[0], 1);
            $message .= sprintf(' Make sure the "%s" bundle is correctly registered and loaded in the application kernel class.', $bundle);
            $message .= sprintf(' If the bundle is registered, make sure the bundle path "%s" is not empty.', $resource);
        } elseif (null !== $type) {
            // maybe there is no loader for this specific type
            if ('annotation' === $type) {
                $message .= ' Make sure annotations are installed and enabled.';
            } else {
                $message .= sprintf(' Make sure there is a loader supporting the "%s" type.', $type);
            }
        }

        parent::__construct($message, $code, $previous);
    }

    protected function varToString($var)
    {
        if (\is_object($var)) {
            return sprintf('Object(%s)', \get_class($var));
        }

        if (\is_array($var)) {
            $a = [];
            foreach ($var as $k => $v) {
                $a[] = sprintf('%s => %s', $k, $this->varToString($v));
            }

            return sprintf('Array(%s)', implode(', ', $a));
        }

        if (\is_resource($var)) {
            return sprintf('Resource(%s)', get_resource_type($var));
        }

        if (null === $var) {
            return 'null';
        }

        if (false === $var) {
            return 'false';
        }

        if (true === $var) {
            return 'true';
        }

        return (string) $var;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Exception;

/**
 * Exception class for when a circular reference is detected when importing resources.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class FileLoaderImportCircularReferenceException extends LoaderLoadException
{
    public function __construct(array $resources, int $code = null, \Exception $previous = null)
    {
        $message = sprintf('Circular reference detected in "%s" ("%s" > "%s").', $this->varToString($resources[0]), implode('" > "', $resources), $resources[0]);

        \Exception::__construct($message, $code, $previous);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config\Exception;

/**
 * File locator exception if a file does not exist.
 *
 * @author Leo Feyer <https://github.com/leofeyer>
 */
class FileLocatorFileNotFoundException extends \InvalidArgumentException
{
    private $paths;

    public function __construct(string $message = '', int $code = 0, \Exception $previous = null, array $paths = [])
    {
        parent::__construct($message, $code, $previous);

        $this->paths = $paths;
    }

    public function getPaths()
    {
        return $this->paths;
    }
}
{
    "name": "symfony/config",
    "type": "library",
    "description": "Symfony Config Component",
    "keywords": [],
    "homepage": "https://symfony.com",
    "license": "MIT",
    "authors": [
        {
            "name": "Fabien Potencier",
            "email": "fabien@symfony.com"
        },
        {
            "name": "Symfony Community",
            "homepage": "https://symfony.com/contributors"
        }
    ],
    "require": {
        "php": "^7.1.3",
        "symfony/filesystem": "~3.4|~4.0",
        "symfony/polyfill-ctype": "~1.8"
    },
    "require-dev": {
        "symfony/dependency-injection": "~3.4|~4.0",
        "symfony/event-dispatcher": "~3.4|~4.0",
        "symfony/finder": "~3.4|~4.0",
        "symfony/yaml": "~3.4|~4.0"
    },
    "conflict": {
        "symfony/finder": "<3.4"
    },
    "suggest": {
        "symfony/yaml": "To use the yaml reference dumper"
    },
    "autoload": {
        "psr-4": { "Symfony\\Component\\Config\\": "" },
        "exclude-from-classmap": [
            "/Tests/"
        ]
    },
    "minimum-stability": "dev",
    "extra": {
        "branch-alias": {
            "dev-master": "4.2-dev"
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config;

/**
 * Basic implementation of ConfigCacheFactoryInterface that
 * creates an instance of the default ConfigCache.
 *
 * This factory and/or cache <em>do not</em> support cache validation
 * by means of ResourceChecker instances (that is, service-based).
 *
 * @author Matthias Pigulla <mp@webfactory.de>
 */
class ConfigCacheFactory implements ConfigCacheFactoryInterface
{
    private $debug;

    /**
     * @param bool $debug The debug flag to pass to ConfigCache
     */
    public function __construct(bool $debug)
    {
        $this->debug = $debug;
    }

    /**
     * {@inheritdoc}
     */
    public function cache($file, $callback)
    {
        if (!\is_callable($callback)) {
            throw new \InvalidArgumentException(sprintf('Invalid type for callback argument. Expected callable, but got "%s".', \gettype($callback)));
        }

        $cache = new ConfigCache($file, $this->debug);
        if (!$cache->isFresh()) {
            $callback($cache);
        }

        return $cache;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Config;

/**
 * A ConfigCacheFactory implementation that validates the
 * cache with an arbitrary set of ResourceCheckers.
 *
 * @author Matthias Pigulla <mp@webfactory.de>
 */
class ResourceCheckerConfigCacheFactory implements ConfigCacheFactoryInterface
{
    private $resourceCheckers = [];

    /**
     * @param iterable|ResourceCheckerInterface[] $resourceCheckers
     */
    public function __construct(iterable $resourceCheckers = [])
    {
        $this->resourceCheckers = $resourceCheckers;
    }

    /**
     * {@inheritdoc}
     */
    public function cache($file, $callback)
    {
        if (!\is_callable($callback)) {
            throw new \InvalidArgumentException(sprintf('Invalid type for callback argument. Expected callable, but got "%s".', \gettype($callback)));
        }

        $cache = new ResourceCheckerConfigCache($file, $this->resourceCheckers);
        if (!$cache->isFresh()) {
            $callback($cache);
        }

        return $cache;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Filesystem;

use Symfony\Component\Filesystem\Exception\FileNotFoundException;
use Symfony\Component\Filesystem\Exception\InvalidArgumentException;
use Symfony\Component\Filesystem\Exception\IOException;

/**
 * Provides basic utility to manipulate the file system.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Filesystem
{
    private static $lastError;

    /**
     * Copies a file.
     *
     * If the target file is older than the origin file, it's always overwritten.
     * If the target file is newer, it is overwritten only when the
     * $overwriteNewerFiles option is set to true.
     *
     * @param string $originFile          The original filename
     * @param string $targetFile          The target filename
     * @param bool   $overwriteNewerFiles If true, target files newer than origin files are overwritten
     *
     * @throws FileNotFoundException When originFile doesn't exist
     * @throws IOException           When copy fails
     */
    public function copy($originFile, $targetFile, $overwriteNewerFiles = false)
    {
        $originIsLocal = stream_is_local($originFile) || 0 === stripos($originFile, 'file://');
        if ($originIsLocal && !is_file($originFile)) {
            throw new FileNotFoundException(sprintf('Failed to copy "%s" because file does not exist.', $originFile), 0, null, $originFile);
        }

        $this->mkdir(\dirname($targetFile));

        $doCopy = true;
        if (!$overwriteNewerFiles && null === parse_url($originFile, PHP_URL_HOST) && is_file($targetFile)) {
            $doCopy = filemtime($originFile) > filemtime($targetFile);
        }

        if ($doCopy) {
            // https://bugs.php.net/bug.php?id=64634
            if (false === $source = @fopen($originFile, 'r')) {
                throw new IOException(sprintf('Failed to copy "%s" to "%s" because source file could not be opened for reading.', $originFile, $targetFile), 0, null, $originFile);
            }

            // Stream context created to allow files overwrite when using FTP stream wrapper - disabled by default
            if (false === $target = @fopen($targetFile, 'w', null, stream_context_create(['ftp' => ['overwrite' => true]]))) {
                throw new IOException(sprintf('Failed to copy "%s" to "%s" because target file could not be opened for writing.', $originFile, $targetFile), 0, null, $originFile);
            }

            $bytesCopied = stream_copy_to_stream($source, $target);
            fclose($source);
            fclose($target);
            unset($source, $target);

            if (!is_file($targetFile)) {
                throw new IOException(sprintf('Failed to copy "%s" to "%s".', $originFile, $targetFile), 0, null, $originFile);
            }

            if ($originIsLocal) {
                // Like `cp`, preserve executable permission bits
                @chmod($targetFile, fileperms($targetFile) | (fileperms($originFile) & 0111));

                if ($bytesCopied !== $bytesOrigin = filesize($originFile)) {
                    throw new IOException(sprintf('Failed to copy the whole content of "%s" to "%s" (%g of %g bytes copied).', $originFile, $targetFile, $bytesCopied, $bytesOrigin), 0, null, $originFile);
                }
            }
        }
    }

    /**
     * Creates a directory recursively.
     *
     * @param string|iterable $dirs The directory path
     * @param int             $mode The directory mode
     *
     * @throws IOException On any directory creation failure
     */
    public function mkdir($dirs, $mode = 0777)
    {
        foreach ($this->toIterable($dirs) as $dir) {
            if (is_dir($dir)) {
                continue;
            }

            if (!self::box('mkdir', $dir, $mode, true)) {
                if (!is_dir($dir)) {
                    // The directory was not created by a concurrent process. Let's throw an exception with a developer friendly error message if we have one
                    if (self::$lastError) {
                        throw new IOException(sprintf('Failed to create "%s": %s.', $dir, self::$lastError), 0, null, $dir);
                    }
                    throw new IOException(sprintf('Failed to create "%s"', $dir), 0, null, $dir);
                }
            }
        }
    }

    /**
     * Checks the existence of files or directories.
     *
     * @param string|iterable $files A filename, an array of files, or a \Traversable instance to check
     *
     * @return bool true if the file exists, false otherwise
     */
    public function exists($files)
    {
        $maxPathLength = PHP_MAXPATHLEN - 2;

        foreach ($this->toIterable($files) as $file) {
            if (\strlen($file) > $maxPathLength) {
                throw new IOException(sprintf('Could not check if file exist because path length exceeds %d characters.', $maxPathLength), 0, null, $file);
            }

            if (!file_exists($file)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Sets access and modification time of file.
     *
     * @param string|iterable $files A filename, an array of files, or a \Traversable instance to create
     * @param int|null        $time  The touch time as a Unix timestamp, if not supplied the current system time is used
     * @param int|null        $atime The access time as a Unix timestamp, if not supplied the current system time is used
     *
     * @throws IOException When touch fails
     */
    public function touch($files, $time = null, $atime = null)
    {
        foreach ($this->toIterable($files) as $file) {
            $touch = $time ? @touch($file, $time, $atime) : @touch($file);
            if (true !== $touch) {
                throw new IOException(sprintf('Failed to touch "%s".', $file), 0, null, $file);
            }
        }
    }

    /**
     * Removes files or directories.
     *
     * @param string|iterable $files A filename, an array of files, or a \Traversable instance to remove
     *
     * @throws IOException When removal fails
     */
    public function remove($files)
    {
        if ($files instanceof \Traversable) {
            $files = iterator_to_array($files, false);
        } elseif (!\is_array($files)) {
            $files = [$files];
        }
        $files = array_reverse($files);
        foreach ($files as $file) {
            if (is_link($file)) {
                // See https://bugs.php.net/52176
                if (!(self::box('unlink', $file) || '\\' !== \DIRECTORY_SEPARATOR || self::box('rmdir', $file)) && file_exists($file)) {
                    throw new IOException(sprintf('Failed to remove symlink "%s": %s.', $file, self::$lastError));
                }
            } elseif (is_dir($file)) {
                $this->remove(new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS));

                if (!self::box('rmdir', $file) && file_exists($file)) {
                    throw new IOException(sprintf('Failed to remove directory "%s": %s.', $file, self::$lastError));
                }
            } elseif (!self::box('unlink', $file) && file_exists($file)) {
                throw new IOException(sprintf('Failed to remove file "%s": %s.', $file, self::$lastError));
            }
        }
    }

    /**
     * Change mode for an array of files or directories.
     *
     * @param string|iterable $files     A filename, an array of files, or a \Traversable instance to change mode
     * @param int             $mode      The new mode (octal)
     * @param int             $umask     The mode mask (octal)
     * @param bool            $recursive Whether change the mod recursively or not
     *
     * @throws IOException When the change fails
     */
    public function chmod($files, $mode, $umask = 0000, $recursive = false)
    {
        foreach ($this->toIterable($files) as $file) {
            if (true !== @chmod($file, $mode & ~$umask)) {
                throw new IOException(sprintf('Failed to chmod file "%s".', $file), 0, null, $file);
            }
            if ($recursive && is_dir($file) && !is_link($file)) {
                $this->chmod(new \FilesystemIterator($file), $mode, $umask, true);
            }
        }
    }

    /**
     * Change the owner of an array of files or directories.
     *
     * @param string|iterable $files     A filename, an array of files, or a \Traversable instance to change owner
     * @param string          $user      The new owner user name
     * @param bool            $recursive Whether change the owner recursively or not
     *
     * @throws IOException When the change fails
     */
    public function chown($files, $user, $recursive = false)
    {
        foreach ($this->toIterable($files) as $file) {
            if ($recursive && is_dir($file) && !is_link($file)) {
                $this->chown(new \FilesystemIterator($file), $user, true);
            }
            if (is_link($file) && \function_exists('lchown')) {
                if (true !== @lchown($file, $user)) {
                    throw new IOException(sprintf('Failed to chown file "%s".', $file), 0, null, $file);
                }
            } else {
                if (true !== @chown($file, $user)) {
                    throw new IOException(sprintf('Failed to chown file "%s".', $file), 0, null, $file);
                }
            }
        }
    }

    /**
     * Change the group of an array of files or directories.
     *
     * @param string|iterable $files     A filename, an array of files, or a \Traversable instance to change group
     * @param string          $group     The group name
     * @param bool            $recursive Whether change the group recursively or not
     *
     * @throws IOException When the change fails
     */
    public function chgrp($files, $group, $recursive = false)
    {
        foreach ($this->toIterable($files) as $file) {
            if ($recursive && is_dir($file) && !is_link($file)) {
                $this->chgrp(new \FilesystemIterator($file), $group, true);
            }
            if (is_link($file) && \function_exists('lchgrp')) {
                if (true !== @lchgrp($file, $group)) {
                    throw new IOException(sprintf('Failed to chgrp file "%s".', $file), 0, null, $file);
                }
            } else {
                if (true !== @chgrp($file, $group)) {
                    throw new IOException(sprintf('Failed to chgrp file "%s".', $file), 0, null, $file);
                }
            }
        }
    }

    /**
     * Renames a file or a directory.
     *
     * @param string $origin    The origin filename or directory
     * @param string $target    The new filename or directory
     * @param bool   $overwrite Whether to overwrite the target if it already exists
     *
     * @throws IOException When target file or directory already exists
     * @throws IOException When origin cannot be renamed
     */
    public function rename($origin, $target, $overwrite = false)
    {
        // we check that target does not exist
        if (!$overwrite && $this->isReadable($target)) {
            throw new IOException(sprintf('Cannot rename because the target "%s" already exists.', $target), 0, null, $target);
        }

        if (true !== @rename($origin, $target)) {
            if (is_dir($origin)) {
                // See https://bugs.php.net/bug.php?id=54097 & http://php.net/manual/en/function.rename.php#113943
                $this->mirror($origin, $target, null, ['override' => $overwrite, 'delete' => $overwrite]);
                $this->remove($origin);

                return;
            }
            throw new IOException(sprintf('Cannot rename "%s" to "%s".', $origin, $target), 0, null, $target);
        }
    }

    /**
     * Tells whether a file exists and is readable.
     *
     * @param string $filename Path to the file
     *
     * @return bool
     *
     * @throws IOException When windows path is longer than 258 characters
     */
    private function isReadable($filename)
    {
        $maxPathLength = PHP_MAXPATHLEN - 2;

        if (\strlen($filename) > $maxPathLength) {
            throw new IOException(sprintf('Could not check if file is readable because path length exceeds %d characters.', $maxPathLength), 0, null, $filename);
        }

        return is_readable($filename);
    }

    /**
     * Creates a symbolic link or copy a directory.
     *
     * @param string $originDir     The origin directory path
     * @param string $targetDir     The symbolic link name
     * @param bool   $copyOnWindows Whether to copy files if on Windows
     *
     * @throws IOException When symlink fails
     */
    public function symlink($originDir, $targetDir, $copyOnWindows = false)
    {
        if ('\\' === \DIRECTORY_SEPARATOR) {
            $originDir = strtr($originDir, '/', '\\');
            $targetDir = strtr($targetDir, '/', '\\');

            if ($copyOnWindows) {
                $this->mirror($originDir, $targetDir);

                return;
            }
        }

        $this->mkdir(\dirname($targetDir));

        if (is_link($targetDir)) {
            if (readlink($targetDir) === $originDir) {
                return;
            }
            $this->remove($targetDir);
        }

        if (!self::box('symlink', $originDir, $targetDir)) {
            $this->linkException($originDir, $targetDir, 'symbolic');
        }
    }

    /**
     * Creates a hard link, or several hard links to a file.
     *
     * @param string          $originFile  The original file
     * @param string|string[] $targetFiles The target file(s)
     *
     * @throws FileNotFoundException When original file is missing or not a file
     * @throws IOException           When link fails, including if link already exists
     */
    public function hardlink($originFile, $targetFiles)
    {
        if (!$this->exists($originFile)) {
            throw new FileNotFoundException(null, 0, null, $originFile);
        }

        if (!is_file($originFile)) {
            throw new FileNotFoundException(sprintf('Origin file "%s" is not a file', $originFile));
        }

        foreach ($this->toIterable($targetFiles) as $targetFile) {
            if (is_file($targetFile)) {
                if (fileinode($originFile) === fileinode($targetFile)) {
                    continue;
                }
                $this->remove($targetFile);
            }

            if (!self::box('link', $originFile, $targetFile)) {
                $this->linkException($originFile, $targetFile, 'hard');
            }
        }
    }

    /**
     * @param string $origin
     * @param string $target
     * @param string $linkType Name of the link type, typically 'symbolic' or 'hard'
     */
    private function linkException($origin, $target, $linkType)
    {
        if (self::$lastError) {
            if ('\\' === \DIRECTORY_SEPARATOR && false !== strpos(self::$lastError, 'error code(1314)')) {
                throw new IOException(sprintf('Unable to create %s link due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?', $linkType), 0, null, $target);
            }
        }
        throw new IOException(sprintf('Failed to create %s link from "%s" to "%s".', $linkType, $origin, $target), 0, null, $target);
    }

    /**
     * Resolves links in paths.
     *
     * With $canonicalize = false (default)
     *      - if $path does not exist or is not a link, returns null
     *      - if $path is a link, returns the next direct target of the link without considering the existence of the target
     *
     * With $canonicalize = true
     *      - if $path does not exist, returns null
     *      - if $path exists, returns its absolute fully resolved final version
     *
     * @param string $path         A filesystem path
     * @param bool   $canonicalize Whether or not to return a canonicalized path
     *
     * @return string|null
     */
    public function readlink($path, $canonicalize = false)
    {
        if (!$canonicalize && !is_link($path)) {
            return;
        }

        if ($canonicalize) {
            if (!$this->exists($path)) {
                return;
            }

            if ('\\' === \DIRECTORY_SEPARATOR) {
                $path = readlink($path);
            }

            return realpath($path);
        }

        if ('\\' === \DIRECTORY_SEPARATOR) {
            return realpath($path);
        }

        return readlink($path);
    }

    /**
     * Given an existing path, convert it to a path relative to a given starting path.
     *
     * @param string $endPath   Absolute path of target
     * @param string $startPath Absolute path where traversal begins
     *
     * @return string Path of target relative to starting path
     */
    public function makePathRelative($endPath, $startPath)
    {
        if (!$this->isAbsolutePath($startPath)) {
            throw new InvalidArgumentException(sprintf('The start path "%s" is not absolute.', $startPath));
        }

        if (!$this->isAbsolutePath($endPath)) {
            throw new InvalidArgumentException(sprintf('The end path "%s" is not absolute.', $endPath));
        }

        // Normalize separators on Windows
        if ('\\' === \DIRECTORY_SEPARATOR) {
            $endPath = str_replace('\\', '/', $endPath);
            $startPath = str_replace('\\', '/', $startPath);
        }

        $stripDriveLetter = function ($path) {
            if (\strlen($path) > 2 && ':' === $path[1] && '/' === $path[2] && ctype_alpha($path[0])) {
                return substr($path, 2);
            }

            return $path;
        };

        $endPath = $stripDriveLetter($endPath);
        $startPath = $stripDriveLetter($startPath);

        // Split the paths into arrays
        $startPathArr = explode('/', trim($startPath, '/'));
        $endPathArr = explode('/', trim($endPath, '/'));

        $normalizePathArray = function ($pathSegments) {
            $result = [];

            foreach ($pathSegments as $segment) {
                if ('..' === $segment) {
                    array_pop($result);
                } elseif ('.' !== $segment) {
                    $result[] = $segment;
                }
            }

            return $result;
        };

        $startPathArr = $normalizePathArray($startPathArr);
        $endPathArr = $normalizePathArray($endPathArr);

        // Find for which directory the common path stops
        $index = 0;
        while (isset($startPathArr[$index]) && isset($endPathArr[$index]) && $startPathArr[$index] === $endPathArr[$index]) {
            ++$index;
        }

        // Determine how deep the start path is relative to the common path (ie, "web/bundles" = 2 levels)
        if (1 === \count($startPathArr) && '' === $startPathArr[0]) {
            $depth = 0;
        } else {
            $depth = \count($startPathArr) - $index;
        }

        // Repeated "../" for each level need to reach the common path
        $traverser = str_repeat('../', $depth);

        $endPathRemainder = implode('/', \array_slice($endPathArr, $index));

        // Construct $endPath from traversing to the common path, then to the remaining $endPath
        $relativePath = $traverser.('' !== $endPathRemainder ? $endPathRemainder.'/' : '');

        return '' === $relativePath ? './' : $relativePath;
    }

    /**
     * Mirrors a directory to another.
     *
     * Copies files and directories from the origin directory into the target directory. By default:
     *
     *  - existing files in the target directory will be overwritten, except if they are newer (see the `override` option)
     *  - files in the target directory that do not exist in the source directory will not be deleted (see the `delete` option)
     *
     * @param string            $originDir The origin directory
     * @param string            $targetDir The target directory
     * @param \Traversable|null $iterator  Iterator that filters which files and directories to copy, if null a recursive iterator is created
     * @param array             $options   An array of boolean options
     *                                     Valid options are:
     *                                     - $options['override'] If true, target files newer than origin files are overwritten (see copy(), defaults to false)
     *                                     - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink(), defaults to false)
     *                                     - $options['delete'] Whether to delete files that are not in the source directory (defaults to false)
     *
     * @throws IOException When file type is unknown
     */
    public function mirror($originDir, $targetDir, \Traversable $iterator = null, $options = [])
    {
        $targetDir = rtrim($targetDir, '/\\');
        $originDir = rtrim($originDir, '/\\');
        $originDirLen = \strlen($originDir);

        // Iterate in destination folder to remove obsolete entries
        if ($this->exists($targetDir) && isset($options['delete']) && $options['delete']) {
            $deleteIterator = $iterator;
            if (null === $deleteIterator) {
                $flags = \FilesystemIterator::SKIP_DOTS;
                $deleteIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($targetDir, $flags), \RecursiveIteratorIterator::CHILD_FIRST);
            }
            $targetDirLen = \strlen($targetDir);
            foreach ($deleteIterator as $file) {
                $origin = $originDir.substr($file->getPathname(), $targetDirLen);
                if (!$this->exists($origin)) {
                    $this->remove($file);
                }
            }
        }

        $copyOnWindows = $options['copy_on_windows'] ?? false;

        if (null === $iterator) {
            $flags = $copyOnWindows ? \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS : \FilesystemIterator::SKIP_DOTS;
            $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($originDir, $flags), \RecursiveIteratorIterator::SELF_FIRST);
        }

        if ($this->exists($originDir)) {
            $this->mkdir($targetDir);
        }

        foreach ($iterator as $file) {
            if (false === strpos($file->getPath(), $originDir)) {
                throw new IOException(sprintf('Unable to mirror "%s" directory. If the origin directory is relative, try using "realpath" before calling the mirror method.', $originDir), 0, null, $originDir);
            }

            $target = $targetDir.substr($file->getPathname(), $originDirLen);

            if ($copyOnWindows) {
                if (is_file($file)) {
                    $this->copy($file, $target, isset($options['override']) ? $options['override'] : false);
                } elseif (is_dir($file)) {
                    $this->mkdir($target);
                } else {
                    throw new IOException(sprintf('Unable to guess "%s" file type.', $file), 0, null, $file);
                }
            } else {
                if (is_link($file)) {
                    $this->symlink($file->getLinkTarget(), $target);
                } elseif (is_dir($file)) {
                    $this->mkdir($target);
                } elseif (is_file($file)) {
                    $this->copy($file, $target, isset($options['override']) ? $options['override'] : false);
                } else {
                    throw new IOException(sprintf('Unable to guess "%s" file type.', $file), 0, null, $file);
                }
            }
        }
    }

    /**
     * Returns whether the file path is an absolute path.
     *
     * @param string $file A file path
     *
     * @return bool
     */
    public function isAbsolutePath($file)
    {
        return strspn($file, '/\\', 0, 1)
            || (\strlen($file) > 3 && ctype_alpha($file[0])
                && ':' === $file[1]
                && strspn($file, '/\\', 2, 1)
            )
            || null !== parse_url($file, PHP_URL_SCHEME)
        ;
    }

    /**
     * Creates a temporary file with support for custom stream wrappers.
     *
     * @param string $dir    The directory where the temporary filename will be created
     * @param string $prefix The prefix of the generated temporary filename
     *                       Note: Windows uses only the first three characters of prefix
     *
     * @return string The new temporary filename (with path), or throw an exception on failure
     */
    public function tempnam($dir, $prefix)
    {
        list($scheme, $hierarchy) = $this->getSchemeAndHierarchy($dir);

        // If no scheme or scheme is "file" or "gs" (Google Cloud) create temp file in local filesystem
        if (null === $scheme || 'file' === $scheme || 'gs' === $scheme) {
            $tmpFile = @tempnam($hierarchy, $prefix);

            // If tempnam failed or no scheme return the filename otherwise prepend the scheme
            if (false !== $tmpFile) {
                if (null !== $scheme && 'gs' !== $scheme) {
                    return $scheme.'://'.$tmpFile;
                }

                return $tmpFile;
            }

            throw new IOException('A temporary file could not be created.');
        }

        // Loop until we create a valid temp file or have reached 10 attempts
        for ($i = 0; $i < 10; ++$i) {
            // Create a unique filename
            $tmpFile = $dir.'/'.$prefix.uniqid(mt_rand(), true);

            // Use fopen instead of file_exists as some streams do not support stat
            // Use mode 'x+' to atomically check existence and create to avoid a TOCTOU vulnerability
            $handle = @fopen($tmpFile, 'x+');

            // If unsuccessful restart the loop
            if (false === $handle) {
                continue;
            }

            // Close the file if it was successfully opened
            @fclose($handle);

            return $tmpFile;
        }

        throw new IOException('A temporary file could not be created.');
    }

    /**
     * Atomically dumps content into a file.
     *
     * @param string $filename The file to be written to
     * @param string $content  The data to write into the file
     *
     * @throws IOException if the file cannot be written to
     */
    public function dumpFile($filename, $content)
    {
        $dir = \dirname($filename);

        if (!is_dir($dir)) {
            $this->mkdir($dir);
        }

        if (!is_writable($dir)) {
            throw new IOException(sprintf('Unable to write to the "%s" directory.', $dir), 0, null, $dir);
        }

        // Will create a temp file with 0600 access rights
        // when the filesystem supports chmod.
        $tmpFile = $this->tempnam($dir, basename($filename));

        if (false === @file_put_contents($tmpFile, $content)) {
            throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename);
        }

        @chmod($tmpFile, file_exists($filename) ? fileperms($filename) : 0666 & ~umask());

        $this->rename($tmpFile, $filename, true);
    }

    /**
     * Appends content to an existing file.
     *
     * @param string $filename The file to which to append content
     * @param string $content  The content to append
     *
     * @throws IOException If the file is not writable
     */
    public function appendToFile($filename, $content)
    {
        $dir = \dirname($filename);

        if (!is_dir($dir)) {
            $this->mkdir($dir);
        }

        if (!is_writable($dir)) {
            throw new IOException(sprintf('Unable to write to the "%s" directory.', $dir), 0, null, $dir);
        }

        if (false === @file_put_contents($filename, $content, FILE_APPEND)) {
            throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename);
        }
    }

    private function toIterable($files): iterable
    {
        return \is_array($files) || $files instanceof \Traversable ? $files : [$files];
    }

    /**
     * Gets a 2-tuple of scheme (may be null) and hierarchical part of a filename (e.g. file:///tmp -> [file, tmp]).
     */
    private function getSchemeAndHierarchy(string $filename): array
    {
        $components = explode('://', $filename, 2);

        return 2 === \count($components) ? [$components[0], $components[1]] : [null, $components[0]];
    }

    private static function box($func)
    {
        self::$lastError = null;
        \set_error_handler(__CLASS__.'::handleError');
        try {
            $result = $func(...\array_slice(\func_get_args(), 1));
            \restore_error_handler();

            return $result;
        } catch (\Throwable $e) {
        } catch (\Exception $e) {
        }
        \restore_error_handler();

        throw $e;
    }

    /**
     * @internal
     */
    public static function handleError($type, $msg)
    {
        self::$lastError = $msg;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Filesystem\Exception;

/**
 * IOException interface for file and input/output stream related exceptions thrown by the component.
 *
 * @author Christian GÃ¤rtner <christiangaertner.film@googlemail.com>
 */
interface IOExceptionInterface extends ExceptionInterface
{
    /**
     * Returns the associated path for the exception.
     *
     * @return string|null The path
     */
    public function getPath();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Filesystem\Exception;

/**
 * Exception interface for all exceptions thrown by the component.
 *
 * @author Romain Neutron <imprec@gmail.com>
 */
interface ExceptionInterface extends \Throwable
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Filesystem\Exception;

/**
 * Exception class thrown when a filesystem operation failure happens.
 *
 * @author Romain Neutron <imprec@gmail.com>
 * @author Christian GÃ¤rtner <christiangaertner.film@googlemail.com>
 * @author Fabien Potencier <fabien@symfony.com>
 */
class IOException extends \RuntimeException implements IOExceptionInterface
{
    private $path;

    public function __construct(string $message, int $code = 0, \Exception $previous = null, string $path = null)
    {
        $this->path = $path;

        parent::__construct($message, $code, $previous);
    }

    /**
     * {@inheritdoc}
     */
    public function getPath()
    {
        return $this->path;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Filesystem\Exception;

/**
 * @author Christian Flothmann <christian.flothmann@sensiolabs.de>
 */
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Filesystem\Exception;

/**
 * Exception class thrown when a file couldn't be found.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Christian GÃ¤rtner <christiangaertner.film@googlemail.com>
 */
class FileNotFoundException extends IOException
{
    public function __construct(string $message = null, int $code = 0, \Exception $previous = null, string $path = null)
    {
        if (null === $message) {
            if (null === $path) {
                $message = 'File could not be found.';
            } else {
                $message = sprintf('File "%s" could not be found.', $path);
            }
        }

        parent::__construct($message, $code, $previous, $path);
    }
}
{
    "name": "symfony/filesystem",
    "type": "library",
    "description": "Symfony Filesystem Component",
    "keywords": [],
    "homepage": "https://symfony.com",
    "license": "MIT",
    "authors": [
        {
            "name": "Fabien Potencier",
            "email": "fabien@symfony.com"
        },
        {
            "name": "Symfony Community",
            "homepage": "https://symfony.com/contributors"
        }
    ],
    "require": {
        "php": "^7.1.3",
        "symfony/polyfill-ctype": "~1.8"
    },
    "autoload": {
        "psr-4": { "Symfony\\Component\\Filesystem\\": "" },
        "exclude-from-classmap": [
            "/Tests/"
        ]
    },
    "minimum-stability": "dev",
    "extra": {
        "branch-alias": {
            "dev-master": "4.2-dev"
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Yaml;

use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Tag\TaggedValue;

/**
 * Parser parses YAML strings to convert them to PHP arrays.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @final since version 3.4
 */
class Parser
{
    const TAG_PATTERN = '(?P<tag>![\w!.\/:-]+)';
    const BLOCK_SCALAR_HEADER_PATTERN = '(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?';

    private $filename;
    private $offset = 0;
    private $totalNumberOfLines;
    private $lines = [];
    private $currentLineNb = -1;
    private $currentLine = '';
    private $refs = [];
    private $skippedLineNumbers = [];
    private $locallySkippedLineNumbers = [];
    private $refsBeingParsed = [];

    public function __construct()
    {
        if (\func_num_args() > 0) {
            @trigger_error(sprintf('The constructor arguments $offset, $totalNumberOfLines, $skippedLineNumbers of %s are deprecated and will be removed in 4.0', self::class), E_USER_DEPRECATED);

            $this->offset = func_get_arg(0);
            if (\func_num_args() > 1) {
                $this->totalNumberOfLines = func_get_arg(1);
            }
            if (\func_num_args() > 2) {
                $this->skippedLineNumbers = func_get_arg(2);
            }
        }
    }

    /**
     * Parses a YAML file into a PHP value.
     *
     * @param string $filename The path to the YAML file to be parsed
     * @param int    $flags    A bit field of PARSE_* constants to customize the YAML parser behavior
     *
     * @return mixed The YAML converted to a PHP value
     *
     * @throws ParseException If the file could not be read or the YAML is not valid
     */
    public function parseFile($filename, $flags = 0)
    {
        if (!is_file($filename)) {
            throw new ParseException(sprintf('File "%s" does not exist.', $filename));
        }

        if (!is_readable($filename)) {
            throw new ParseException(sprintf('File "%s" cannot be read.', $filename));
        }

        $this->filename = $filename;

        try {
            return $this->parse(file_get_contents($filename), $flags);
        } finally {
            $this->filename = null;
        }
    }

    /**
     * Parses a YAML string to a PHP value.
     *
     * @param string $value A YAML string
     * @param int    $flags A bit field of PARSE_* constants to customize the YAML parser behavior
     *
     * @return mixed A PHP value
     *
     * @throws ParseException If the YAML is not valid
     */
    public function parse($value, $flags = 0)
    {
        if (\is_bool($flags)) {
            @trigger_error('Passing a boolean flag to toggle exception handling is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE flag instead.', E_USER_DEPRECATED);

            if ($flags) {
                $flags = Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE;
            } else {
                $flags = 0;
            }
        }

        if (\func_num_args() >= 3) {
            @trigger_error('Passing a boolean flag to toggle object support is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT flag instead.', E_USER_DEPRECATED);

            if (func_get_arg(2)) {
                $flags |= Yaml::PARSE_OBJECT;
            }
        }

        if (\func_num_args() >= 4) {
            @trigger_error('Passing a boolean flag to toggle object for map support is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT_FOR_MAP flag instead.', E_USER_DEPRECATED);

            if (func_get_arg(3)) {
                $flags |= Yaml::PARSE_OBJECT_FOR_MAP;
            }
        }

        if (Yaml::PARSE_KEYS_AS_STRINGS & $flags) {
            @trigger_error('Using the Yaml::PARSE_KEYS_AS_STRINGS flag is deprecated since Symfony 3.4 as it will be removed in 4.0. Quote your keys when they are evaluable instead.', E_USER_DEPRECATED);
        }

        if (false === preg_match('//u', $value)) {
            throw new ParseException('The YAML value does not appear to be valid UTF-8.', -1, null, $this->filename);
        }

        $this->refs = [];

        $mbEncoding = null;
        $e = null;
        $data = null;

        if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) {
            $mbEncoding = mb_internal_encoding();
            mb_internal_encoding('UTF-8');
        }

        try {
            $data = $this->doParse($value, $flags);
        } catch (\Exception $e) {
        } catch (\Throwable $e) {
        }

        if (null !== $mbEncoding) {
            mb_internal_encoding($mbEncoding);
        }

        $this->lines = [];
        $this->currentLine = '';
        $this->refs = [];
        $this->skippedLineNumbers = [];
        $this->locallySkippedLineNumbers = [];

        if (null !== $e) {
            throw $e;
        }

        return $data;
    }

    private function doParse($value, $flags)
    {
        $this->currentLineNb = -1;
        $this->currentLine = '';
        $value = $this->cleanup($value);
        $this->lines = explode("\n", $value);
        $this->locallySkippedLineNumbers = [];

        if (null === $this->totalNumberOfLines) {
            $this->totalNumberOfLines = \count($this->lines);
        }

        if (!$this->moveToNextLine()) {
            return null;
        }

        $data = [];
        $context = null;
        $allowOverwrite = false;

        while ($this->isCurrentLineEmpty()) {
            if (!$this->moveToNextLine()) {
                return null;
            }
        }

        // Resolves the tag and returns if end of the document
        if (null !== ($tag = $this->getLineTag($this->currentLine, $flags, false)) && !$this->moveToNextLine()) {
            return new TaggedValue($tag, '');
        }

        do {
            if ($this->isCurrentLineEmpty()) {
                continue;
            }

            // tab?
            if ("\t" === $this->currentLine[0]) {
                throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
            }

            Inline::initialize($flags, $this->getRealCurrentLineNb(), $this->filename);

            $isRef = $mergeNode = false;
            if (self::preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+))?$#u', rtrim($this->currentLine), $values)) {
                if ($context && 'mapping' == $context) {
                    throw new ParseException('You cannot define a sequence item when in a mapping', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
                }
                $context = 'sequence';

                if (isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
                    $isRef = $matches['ref'];
                    $this->refsBeingParsed[] = $isRef;
                    $values['value'] = $matches['value'];
                }

                if (isset($values['value'][1]) && '?' === $values['value'][0] && ' ' === $values['value'][1]) {
                    @trigger_error($this->getDeprecationMessage('Starting an unquoted string with a question mark followed by a space is deprecated since Symfony 3.3 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.'), E_USER_DEPRECATED);
                }

                // array
                if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
                    $data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $flags);
                } elseif (null !== $subTag = $this->getLineTag(ltrim($values['value'], ' '), $flags)) {
                    $data[] = new TaggedValue(
                        $subTag,
                        $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $flags)
                    );
                } else {
                    if (isset($values['leadspaces'])
                        && self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+?))?\s*$#u', $this->trimTag($values['value']), $matches)
                    ) {
                        // this is a compact notation element, add to next block and parse
                        $block = $values['value'];
                        if ($this->isNextLineIndented()) {
                            $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + \strlen($values['leadspaces']) + 1);
                        }

                        $data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $flags);
                    } else {
                        $data[] = $this->parseValue($values['value'], $flags, $context);
                    }
                }
                if ($isRef) {
                    $this->refs[$isRef] = end($data);
                    array_pop($this->refsBeingParsed);
                }
            } elseif (
                self::preg_match('#^(?P<key>(?:![^\s]++\s++)?(?:'.Inline::REGEX_QUOTED_STRING.'|(?:!?!php/const:)?[^ \'"\[\{!].*?)) *\:(\s++(?P<value>.+))?$#u', rtrim($this->currentLine), $values)
                && (false === strpos($values['key'], ' #') || \in_array($values['key'][0], ['"', "'"]))
            ) {
                if ($context && 'sequence' == $context) {
                    throw new ParseException('You cannot define a mapping item when in a sequence', $this->currentLineNb + 1, $this->currentLine, $this->filename);
                }
                $context = 'mapping';

                try {
                    $i = 0;
                    $evaluateKey = !(Yaml::PARSE_KEYS_AS_STRINGS & $flags);

                    // constants in key will be evaluated anyway
                    if (isset($values['key'][0]) && '!' === $values['key'][0] && Yaml::PARSE_CONSTANT & $flags) {
                        $evaluateKey = true;
                    }

                    $key = Inline::parseScalar($values['key'], 0, null, $i, $evaluateKey);
                } catch (ParseException $e) {
                    $e->setParsedLine($this->getRealCurrentLineNb() + 1);
                    $e->setSnippet($this->currentLine);

                    throw $e;
                }

                if (!\is_string($key) && !\is_int($key)) {
                    $keyType = is_numeric($key) ? 'numeric key' : 'non-string key';
                    @trigger_error($this->getDeprecationMessage(sprintf('Implicit casting of %s to string is deprecated since Symfony 3.3 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0. Quote your evaluable mapping keys instead.', $keyType)), E_USER_DEPRECATED);
                }

                // Convert float keys to strings, to avoid being converted to integers by PHP
                if (\is_float($key)) {
                    $key = (string) $key;
                }

                if ('<<' === $key && (!isset($values['value']) || !self::preg_match('#^&(?P<ref>[^ ]+)#u', $values['value'], $refMatches))) {
                    $mergeNode = true;
                    $allowOverwrite = true;
                    if (isset($values['value'][0]) && '*' === $values['value'][0]) {
                        $refName = substr(rtrim($values['value']), 1);
                        if (!\array_key_exists($refName, $this->refs)) {
                            if (false !== $pos = array_search($refName, $this->refsBeingParsed, true)) {
                                throw new ParseException(sprintf('Circular reference [%s, %s] detected for reference "%s".', implode(', ', \array_slice($this->refsBeingParsed, $pos)), $refName, $refName), $this->currentLineNb + 1, $this->currentLine, $this->filename);
                            }

                            throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
                        }

                        $refValue = $this->refs[$refName];

                        if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $refValue instanceof \stdClass) {
                            $refValue = (array) $refValue;
                        }

                        if (!\is_array($refValue)) {
                            throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
                        }

                        $data += $refValue; // array union
                    } else {
                        if (isset($values['value']) && '' !== $values['value']) {
                            $value = $values['value'];
                        } else {
                            $value = $this->getNextEmbedBlock();
                        }
                        $parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $flags);

                        if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsed instanceof \stdClass) {
                            $parsed = (array) $parsed;
                        }

                        if (!\is_array($parsed)) {
                            throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
                        }

                        if (isset($parsed[0])) {
                            // If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes
                            // and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier
                            // in the sequence override keys specified in later mapping nodes.
                            foreach ($parsed as $parsedItem) {
                                if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsedItem instanceof \stdClass) {
                                    $parsedItem = (array) $parsedItem;
                                }

                                if (!\is_array($parsedItem)) {
                                    throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem, $this->filename);
                                }

                                $data += $parsedItem; // array union
                            }
                        } else {
                            // If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the
                            // current mapping, unless the key already exists in it.
                            $data += $parsed; // array union
                        }
                    }
                } elseif ('<<' !== $key && isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]++) *+(?P<value>.*)#u', $values['value'], $matches)) {
                    $isRef = $matches['ref'];
                    $this->refsBeingParsed[] = $isRef;
                    $values['value'] = $matches['value'];
                }

                $subTag = null;
                if ($mergeNode) {
                    // Merge keys
                } elseif (!isset($values['value']) || '' === $values['value'] || 0 === strpos($values['value'], '#') || (null !== $subTag = $this->getLineTag($values['value'], $flags)) || '<<' === $key) {
                    // hash
                    // if next line is less indented or equal, then it means that the current value is null
                    if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) {
                        // Spec: Keys MUST be unique; first one wins.
                        // But overwriting is allowed when a merge node is used in current block.
                        if ($allowOverwrite || !isset($data[$key])) {
                            if (null !== $subTag) {
                                $data[$key] = new TaggedValue($subTag, '');
                            } else {
                                $data[$key] = null;
                            }
                        } else {
                            @trigger_error($this->getDeprecationMessage(sprintf('Duplicate key "%s" detected whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since Symfony 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key)), E_USER_DEPRECATED);
                        }
                    } else {
                        $value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $flags);
                        if ('<<' === $key) {
                            $this->refs[$refMatches['ref']] = $value;

                            if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $value instanceof \stdClass) {
                                $value = (array) $value;
                            }

                            $data += $value;
                        } elseif ($allowOverwrite || !isset($data[$key])) {
                            // Spec: Keys MUST be unique; first one wins.
                            // But overwriting is allowed when a merge node is used in current block.
                            if (null !== $subTag) {
                                $data[$key] = new TaggedValue($subTag, $value);
                            } else {
                                $data[$key] = $value;
                            }
                        } else {
                            @trigger_error($this->getDeprecationMessage(sprintf('Duplicate key "%s" detected whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since Symfony 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key)), E_USER_DEPRECATED);
                        }
                    }
                } else {
                    $value = $this->parseValue(rtrim($values['value']), $flags, $context);
                    // Spec: Keys MUST be unique; first one wins.
                    // But overwriting is allowed when a merge node is used in current block.
                    if ($allowOverwrite || !isset($data[$key])) {
                        $data[$key] = $value;
                    } else {
                        @trigger_error($this->getDeprecationMessage(sprintf('Duplicate key "%s" detected whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since Symfony 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key)), E_USER_DEPRECATED);
                    }
                }
                if ($isRef) {
                    $this->refs[$isRef] = $data[$key];
                    array_pop($this->refsBeingParsed);
                }
            } else {
                // multiple documents are not supported
                if ('---' === $this->currentLine) {
                    throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine, $this->filename);
                }

                if ($deprecatedUsage = (isset($this->currentLine[1]) && '?' === $this->currentLine[0] && ' ' === $this->currentLine[1])) {
                    @trigger_error($this->getDeprecationMessage('Starting an unquoted string with a question mark followed by a space is deprecated since Symfony 3.3 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.'), E_USER_DEPRECATED);
                }

                // 1-liner optionally followed by newline(s)
                if (\is_string($value) && $this->lines[0] === trim($value)) {
                    try {
                        $value = Inline::parse($this->lines[0], $flags, $this->refs);
                    } catch (ParseException $e) {
                        $e->setParsedLine($this->getRealCurrentLineNb() + 1);
                        $e->setSnippet($this->currentLine);

                        throw $e;
                    }

                    return $value;
                }

                // try to parse the value as a multi-line string as a last resort
                if (0 === $this->currentLineNb) {
                    $previousLineWasNewline = false;
                    $previousLineWasTerminatedWithBackslash = false;
                    $value = '';

                    foreach ($this->lines as $line) {
                        // If the indentation is not consistent at offset 0, it is to be considered as a ParseError
                        if (0 === $this->offset && !$deprecatedUsage && isset($line[0]) && ' ' === $line[0]) {
                            throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
                        }
                        if ('' === trim($line)) {
                            $value .= "\n";
                        } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) {
                            $value .= ' ';
                        }

                        if ('' !== trim($line) && '\\' === substr($line, -1)) {
                            $value .= ltrim(substr($line, 0, -1));
                        } elseif ('' !== trim($line)) {
                            $value .= trim($line);
                        }

                        if ('' === trim($line)) {
                            $previousLineWasNewline = true;
                            $previousLineWasTerminatedWithBackslash = false;
                        } elseif ('\\' === substr($line, -1)) {
                            $previousLineWasNewline = false;
                            $previousLineWasTerminatedWithBackslash = true;
                        } else {
                            $previousLineWasNewline = false;
                            $previousLineWasTerminatedWithBackslash = false;
                        }
                    }

                    try {
                        return Inline::parse(trim($value));
                    } catch (ParseException $e) {
                        // fall-through to the ParseException thrown below
                    }
                }

                throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
            }
        } while ($this->moveToNextLine());

        if (null !== $tag) {
            $data = new TaggedValue($tag, $data);
        }

        if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && !\is_object($data) && 'mapping' === $context) {
            $object = new \stdClass();

            foreach ($data as $key => $value) {
                $object->$key = $value;
            }

            $data = $object;
        }

        return empty($data) ? null : $data;
    }

    private function parseBlock($offset, $yaml, $flags)
    {
        $skippedLineNumbers = $this->skippedLineNumbers;

        foreach ($this->locallySkippedLineNumbers as $lineNumber) {
            if ($lineNumber < $offset) {
                continue;
            }

            $skippedLineNumbers[] = $lineNumber;
        }

        $parser = new self();
        $parser->offset = $offset;
        $parser->totalNumberOfLines = $this->totalNumberOfLines;
        $parser->skippedLineNumbers = $skippedLineNumbers;
        $parser->refs = &$this->refs;
        $parser->refsBeingParsed = $this->refsBeingParsed;

        return $parser->doParse($yaml, $flags);
    }

    /**
     * Returns the current line number (takes the offset into account).
     *
     * @internal
     *
     * @return int The current line number
     */
    public function getRealCurrentLineNb()
    {
        $realCurrentLineNumber = $this->currentLineNb + $this->offset;

        foreach ($this->skippedLineNumbers as $skippedLineNumber) {
            if ($skippedLineNumber > $realCurrentLineNumber) {
                break;
            }

            ++$realCurrentLineNumber;
        }

        return $realCurrentLineNumber;
    }

    /**
     * Returns the current line indentation.
     *
     * @return int The current line indentation
     */
    private function getCurrentLineIndentation()
    {
        return \strlen($this->currentLine) - \strlen(ltrim($this->currentLine, ' '));
    }

    /**
     * Returns the next embed block of YAML.
     *
     * @param int  $indentation The indent level at which the block is to be read, or null for default
     * @param bool $inSequence  True if the enclosing data structure is a sequence
     *
     * @return string A YAML string
     *
     * @throws ParseException When indentation problem are detected
     */
    private function getNextEmbedBlock($indentation = null, $inSequence = false)
    {
        $oldLineIndentation = $this->getCurrentLineIndentation();

        if (!$this->moveToNextLine()) {
            return;
        }

        if (null === $indentation) {
            $newIndent = null;
            $movements = 0;

            do {
                $EOF = false;

                // empty and comment-like lines do not influence the indentation depth
                if ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) {
                    $EOF = !$this->moveToNextLine();

                    if (!$EOF) {
                        ++$movements;
                    }
                } else {
                    $newIndent = $this->getCurrentLineIndentation();
                }
            } while (!$EOF && null === $newIndent);

            for ($i = 0; $i < $movements; ++$i) {
                $this->moveToPreviousLine();
            }

            $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem();

            if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) {
                throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
            }
        } else {
            $newIndent = $indentation;
        }

        $data = [];
        if ($this->getCurrentLineIndentation() >= $newIndent) {
            $data[] = substr($this->currentLine, $newIndent);
        } elseif ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) {
            $data[] = $this->currentLine;
        } else {
            $this->moveToPreviousLine();

            return;
        }

        if ($inSequence && $oldLineIndentation === $newIndent && isset($data[0][0]) && '-' === $data[0][0]) {
            // the previous line contained a dash but no item content, this line is a sequence item with the same indentation
            // and therefore no nested list or mapping
            $this->moveToPreviousLine();

            return;
        }

        $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem();

        while ($this->moveToNextLine()) {
            $indent = $this->getCurrentLineIndentation();

            if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) {
                $this->moveToPreviousLine();
                break;
            }

            if ($this->isCurrentLineBlank()) {
                $data[] = substr($this->currentLine, $newIndent);
                continue;
            }

            if ($indent >= $newIndent) {
                $data[] = substr($this->currentLine, $newIndent);
            } elseif ($this->isCurrentLineComment()) {
                $data[] = $this->currentLine;
            } elseif (0 == $indent) {
                $this->moveToPreviousLine();

                break;
            } else {
                throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
            }
        }

        return implode("\n", $data);
    }

    /**
     * Moves the parser to the next line.
     *
     * @return bool
     */
    private function moveToNextLine()
    {
        if ($this->currentLineNb >= \count($this->lines) - 1) {
            return false;
        }

        $this->currentLine = $this->lines[++$this->currentLineNb];

        return true;
    }

    /**
     * Moves the parser to the previous line.
     *
     * @return bool
     */
    private function moveToPreviousLine()
    {
        if ($this->currentLineNb < 1) {
            return false;
        }

        $this->currentLine = $this->lines[--$this->currentLineNb];

        return true;
    }

    /**
     * Parses a YAML value.
     *
     * @param string $value   A YAML value
     * @param int    $flags   A bit field of PARSE_* constants to customize the YAML parser behavior
     * @param string $context The parser context (either sequence or mapping)
     *
     * @return mixed A PHP value
     *
     * @throws ParseException When reference does not exist
     */
    private function parseValue($value, $flags, $context)
    {
        if (0 === strpos($value, '*')) {
            if (false !== $pos = strpos($value, '#')) {
                $value = substr($value, 1, $pos - 2);
            } else {
                $value = substr($value, 1);
            }

            if (!\array_key_exists($value, $this->refs)) {
                if (false !== $pos = array_search($value, $this->refsBeingParsed, true)) {
                    throw new ParseException(sprintf('Circular reference [%s, %s] detected for reference "%s".', implode(', ', \array_slice($this->refsBeingParsed, $pos)), $value, $value), $this->currentLineNb + 1, $this->currentLine, $this->filename);
                }

                throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine, $this->filename);
            }

            return $this->refs[$value];
        }

        if (self::preg_match('/^(?:'.self::TAG_PATTERN.' +)?'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) {
            $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : '';

            $data = $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), (int) abs($modifiers));

            if ('' !== $matches['tag']) {
                if ('!!binary' === $matches['tag']) {
                    return Inline::evaluateBinaryScalar($data);
                } elseif ('tagged' === $matches['tag']) {
                    return new TaggedValue(substr($matches['tag'], 1), $data);
                } elseif ('!' !== $matches['tag']) {
                    @trigger_error($this->getDeprecationMessage(sprintf('Using the custom tag "%s" for the value "%s" is deprecated since Symfony 3.3. It will be replaced by an instance of %s in 4.0.', $matches['tag'], $data, TaggedValue::class)), E_USER_DEPRECATED);
                }
            }

            return $data;
        }

        try {
            $quotation = '' !== $value && ('"' === $value[0] || "'" === $value[0]) ? $value[0] : null;

            // do not take following lines into account when the current line is a quoted single line value
            if (null !== $quotation && self::preg_match('/^'.$quotation.'.*'.$quotation.'(\s*#.*)?$/', $value)) {
                return Inline::parse($value, $flags, $this->refs);
            }

            $lines = [];

            while ($this->moveToNextLine()) {
                // unquoted strings end before the first unindented line
                if (null === $quotation && 0 === $this->getCurrentLineIndentation()) {
                    $this->moveToPreviousLine();

                    break;
                }

                $lines[] = trim($this->currentLine);

                // quoted string values end with a line that is terminated with the quotation character
                if ('' !== $this->currentLine && substr($this->currentLine, -1) === $quotation) {
                    break;
                }
            }

            for ($i = 0, $linesCount = \count($lines), $previousLineBlank = false; $i < $linesCount; ++$i) {
                if ('' === $lines[$i]) {
                    $value .= "\n";
                    $previousLineBlank = true;
                } elseif ($previousLineBlank) {
                    $value .= $lines[$i];
                    $previousLineBlank = false;
                } else {
                    $value .= ' '.$lines[$i];
                    $previousLineBlank = false;
                }
            }

            Inline::$parsedLineNumber = $this->getRealCurrentLineNb();

            $parsedValue = Inline::parse($value, $flags, $this->refs);

            if ('mapping' === $context && \is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) {
                throw new ParseException('A colon cannot be used in an unquoted mapping value.', $this->getRealCurrentLineNb() + 1, $value, $this->filename);
            }

            return $parsedValue;
        } catch (ParseException $e) {
            $e->setParsedLine($this->getRealCurrentLineNb() + 1);
            $e->setSnippet($this->currentLine);

            throw $e;
        }
    }

    /**
     * Parses a block scalar.
     *
     * @param string $style       The style indicator that was used to begin this block scalar (| or >)
     * @param string $chomping    The chomping indicator that was used to begin this block scalar (+ or -)
     * @param int    $indentation The indentation indicator that was used to begin this block scalar
     *
     * @return string The text value
     */
    private function parseBlockScalar($style, $chomping = '', $indentation = 0)
    {
        $notEOF = $this->moveToNextLine();
        if (!$notEOF) {
            return '';
        }

        $isCurrentLineBlank = $this->isCurrentLineBlank();
        $blockLines = [];

        // leading blank lines are consumed before determining indentation
        while ($notEOF && $isCurrentLineBlank) {
            // newline only if not EOF
            if ($notEOF = $this->moveToNextLine()) {
                $blockLines[] = '';
                $isCurrentLineBlank = $this->isCurrentLineBlank();
            }
        }

        // determine indentation if not specified
        if (0 === $indentation) {
            if (self::preg_match('/^ +/', $this->currentLine, $matches)) {
                $indentation = \strlen($matches[0]);
            }
        }

        if ($indentation > 0) {
            $pattern = sprintf('/^ {%d}(.*)$/', $indentation);

            while (
                $notEOF && (
                    $isCurrentLineBlank ||
                    self::preg_match($pattern, $this->currentLine, $matches)
                )
            ) {
                if ($isCurrentLineBlank && \strlen($this->currentLine) > $indentation) {
                    $blockLines[] = substr($this->currentLine, $indentation);
                } elseif ($isCurrentLineBlank) {
                    $blockLines[] = '';
                } else {
                    $blockLines[] = $matches[1];
                }

                // newline only if not EOF
                if ($notEOF = $this->moveToNextLine()) {
                    $isCurrentLineBlank = $this->isCurrentLineBlank();
                }
            }
        } elseif ($notEOF) {
            $blockLines[] = '';
        }

        if ($notEOF) {
            $blockLines[] = '';
            $this->moveToPreviousLine();
        } elseif (!$notEOF && !$this->isCurrentLineLastLineInDocument()) {
            $blockLines[] = '';
        }

        // folded style
        if ('>' === $style) {
            $text = '';
            $previousLineIndented = false;
            $previousLineBlank = false;

            for ($i = 0, $blockLinesCount = \count($blockLines); $i < $blockLinesCount; ++$i) {
                if ('' === $blockLines[$i]) {
                    $text .= "\n";
                    $previousLineIndented = false;
                    $previousLineBlank = true;
                } elseif (' ' === $blockLines[$i][0]) {
                    $text .= "\n".$blockLines[$i];
                    $previousLineIndented = true;
                    $previousLineBlank = false;
                } elseif ($previousLineIndented) {
                    $text .= "\n".$blockLines[$i];
                    $previousLineIndented = false;
                    $previousLineBlank = false;
                } elseif ($previousLineBlank || 0 === $i) {
                    $text .= $blockLines[$i];
                    $previousLineIndented = false;
                    $previousLineBlank = false;
                } else {
                    $text .= ' '.$blockLines[$i];
                    $previousLineIndented = false;
                    $previousLineBlank = false;
                }
            }
        } else {
            $text = implode("\n", $blockLines);
        }

        // deal with trailing newlines
        if ('' === $chomping) {
            $text = preg_replace('/\n+$/', "\n", $text);
        } elseif ('-' === $chomping) {
            $text = preg_replace('/\n+$/', '', $text);
        }

        return $text;
    }

    /**
     * Returns true if the next line is indented.
     *
     * @return bool Returns true if the next line is indented, false otherwise
     */
    private function isNextLineIndented()
    {
        $currentIndentation = $this->getCurrentLineIndentation();
        $movements = 0;

        do {
            $EOF = !$this->moveToNextLine();

            if (!$EOF) {
                ++$movements;
            }
        } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()));

        if ($EOF) {
            return false;
        }

        $ret = $this->getCurrentLineIndentation() > $currentIndentation;

        for ($i = 0; $i < $movements; ++$i) {
            $this->moveToPreviousLine();
        }

        return $ret;
    }

    /**
     * Returns true if the current line is blank or if it is a comment line.
     *
     * @return bool Returns true if the current line is empty or if it is a comment line, false otherwise
     */
    private function isCurrentLineEmpty()
    {
        return $this->isCurrentLineBlank() || $this->isCurrentLineComment();
    }

    /**
     * Returns true if the current line is blank.
     *
     * @return bool Returns true if the current line is blank, false otherwise
     */
    private function isCurrentLineBlank()
    {
        return '' == trim($this->currentLine, ' ');
    }

    /**
     * Returns true if the current line is a comment line.
     *
     * @return bool Returns true if the current line is a comment line, false otherwise
     */
    private function isCurrentLineComment()
    {
        //checking explicitly the first char of the trim is faster than loops or strpos
        $ltrimmedLine = ltrim($this->currentLine, ' ');

        return '' !== $ltrimmedLine && '#' === $ltrimmedLine[0];
    }

    private function isCurrentLineLastLineInDocument()
    {
        return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1);
    }

    /**
     * Cleanups a YAML string to be parsed.
     *
     * @param string $value The input YAML string
     *
     * @return string A cleaned up YAML string
     */
    private function cleanup($value)
    {
        $value = str_replace(["\r\n", "\r"], "\n", $value);

        // strip YAML header
        $count = 0;
        $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count);
        $this->offset += $count;

        // remove leading comments
        $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count);
        if (1 === $count) {
            // items have been removed, update the offset
            $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
            $value = $trimmedValue;
        }

        // remove start of the document marker (---)
        $trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count);
        if (1 === $count) {
            // items have been removed, update the offset
            $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
            $value = $trimmedValue;

            // remove end of the document marker (...)
            $value = preg_replace('#\.\.\.\s*$#', '', $value);
        }

        return $value;
    }

    /**
     * Returns true if the next line starts unindented collection.
     *
     * @return bool Returns true if the next line starts unindented collection, false otherwise
     */
    private function isNextLineUnIndentedCollection()
    {
        $currentIndentation = $this->getCurrentLineIndentation();
        $movements = 0;

        do {
            $EOF = !$this->moveToNextLine();

            if (!$EOF) {
                ++$movements;
            }
        } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()));

        if ($EOF) {
            return false;
        }

        $ret = $this->getCurrentLineIndentation() === $currentIndentation && $this->isStringUnIndentedCollectionItem();

        for ($i = 0; $i < $movements; ++$i) {
            $this->moveToPreviousLine();
        }

        return $ret;
    }

    /**
     * Returns true if the string is un-indented collection item.
     *
     * @return bool Returns true if the string is un-indented collection item, false otherwise
     */
    private function isStringUnIndentedCollectionItem()
    {
        return '-' === rtrim($this->currentLine) || 0 === strpos($this->currentLine, '- ');
    }

    /**
     * A local wrapper for `preg_match` which will throw a ParseException if there
     * is an internal error in the PCRE engine.
     *
     * This avoids us needing to check for "false" every time PCRE is used
     * in the YAML engine
     *
     * @throws ParseException on a PCRE internal error
     *
     * @see preg_last_error()
     *
     * @internal
     */
    public static function preg_match($pattern, $subject, &$matches = null, $flags = 0, $offset = 0)
    {
        if (false === $ret = preg_match($pattern, $subject, $matches, $flags, $offset)) {
            switch (preg_last_error()) {
                case PREG_INTERNAL_ERROR:
                    $error = 'Internal PCRE error.';
                    break;
                case PREG_BACKTRACK_LIMIT_ERROR:
                    $error = 'pcre.backtrack_limit reached.';
                    break;
                case PREG_RECURSION_LIMIT_ERROR:
                    $error = 'pcre.recursion_limit reached.';
                    break;
                case PREG_BAD_UTF8_ERROR:
                    $error = 'Malformed UTF-8 data.';
                    break;
                case PREG_BAD_UTF8_OFFSET_ERROR:
                    $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.';
                    break;
                default:
                    $error = 'Error.';
            }

            throw new ParseException($error);
        }

        return $ret;
    }

    /**
     * Trim the tag on top of the value.
     *
     * Prevent values such as `!foo {quz: bar}` to be considered as
     * a mapping block.
     */
    private function trimTag($value)
    {
        if ('!' === $value[0]) {
            return ltrim(substr($value, 1, strcspn($value, " \r\n", 1)), ' ');
        }

        return $value;
    }

    private function getLineTag($value, $flags, $nextLineCheck = true)
    {
        if ('' === $value || '!' !== $value[0] || 1 !== self::preg_match('/^'.self::TAG_PATTERN.' *( +#.*)?$/', $value, $matches)) {
            return;
        }

        if ($nextLineCheck && !$this->isNextLineIndented()) {
            return;
        }

        $tag = substr($matches['tag'], 1);

        // Built-in tags
        if ($tag && '!' === $tag[0]) {
            throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), $this->getRealCurrentLineNb() + 1, $value, $this->filename);
        }

        if (Yaml::PARSE_CUSTOM_TAGS & $flags) {
            return $tag;
        }

        throw new ParseException(sprintf('Tags support is not enabled. You must use the flag `Yaml::PARSE_CUSTOM_TAGS` to use "%s".', $matches['tag']), $this->getRealCurrentLineNb() + 1, $value, $this->filename);
    }

    private function getDeprecationMessage($message)
    {
        $message = rtrim($message, '.');

        if (null !== $this->filename) {
            $message .= ' in '.$this->filename;
        }

        $message .= ' on line '.($this->getRealCurrentLineNb() + 1);

        return $message.'.';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Yaml;

/**
 * Escaper encapsulates escaping rules for single and double-quoted
 * YAML strings.
 *
 * @author Matthew Lewinski <matthew@lewinski.org>
 *
 * @internal
 */
class Escaper
{
    // Characters that would cause a dumped string to require double quoting.
    const REGEX_CHARACTER_TO_ESCAPE = "[\\x00-\\x1f]|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9";

    // Mapping arrays for escaping a double quoted string. The backslash is
    // first to ensure proper escaping because str_replace operates iteratively
    // on the input arrays. This ordering of the characters avoids the use of strtr,
    // which performs more slowly.
    private static $escapees = ['\\', '\\\\', '\\"', '"',
                                     "\x00",  "\x01",  "\x02",  "\x03",  "\x04",  "\x05",  "\x06",  "\x07",
                                     "\x08",  "\x09",  "\x0a",  "\x0b",  "\x0c",  "\x0d",  "\x0e",  "\x0f",
                                     "\x10",  "\x11",  "\x12",  "\x13",  "\x14",  "\x15",  "\x16",  "\x17",
                                     "\x18",  "\x19",  "\x1a",  "\x1b",  "\x1c",  "\x1d",  "\x1e",  "\x1f",
                                     "\xc2\x85", "\xc2\xa0", "\xe2\x80\xa8", "\xe2\x80\xa9",
                               ];
    private static $escaped = ['\\\\', '\\"', '\\\\', '\\"',
                                     '\\0',   '\\x01', '\\x02', '\\x03', '\\x04', '\\x05', '\\x06', '\\a',
                                     '\\b',   '\\t',   '\\n',   '\\v',   '\\f',   '\\r',   '\\x0e', '\\x0f',
                                     '\\x10', '\\x11', '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17',
                                     '\\x18', '\\x19', '\\x1a', '\\e',   '\\x1c', '\\x1d', '\\x1e', '\\x1f',
                                     '\\N', '\\_', '\\L', '\\P',
                              ];

    /**
     * Determines if a PHP value would require double quoting in YAML.
     *
     * @param string $value A PHP value
     *
     * @return bool True if the value would require double quotes
     */
    public static function requiresDoubleQuoting($value)
    {
        return 0 < preg_match('/'.self::REGEX_CHARACTER_TO_ESCAPE.'/u', $value);
    }

    /**
     * Escapes and surrounds a PHP value with double quotes.
     *
     * @param string $value A PHP value
     *
     * @return string The quoted, escaped string
     */
    public static function escapeWithDoubleQuotes($value)
    {
        return sprintf('"%s"', str_replace(self::$escapees, self::$escaped, $value));
    }

    /**
     * Determines if a PHP value would require single quoting in YAML.
     *
     * @param string $value A PHP value
     *
     * @return bool True if the value would require single quotes
     */
    public static function requiresSingleQuoting($value)
    {
        // Determines if a PHP value is entirely composed of a value that would
        // require single quoting in YAML.
        if (\in_array(strtolower($value), ['null', '~', 'true', 'false', 'y', 'n', 'yes', 'no', 'on', 'off'])) {
            return true;
        }

        // Determines if the PHP value contains any single characters that would
        // cause it to require single quoting in YAML.
        return 0 < preg_match('/[ \s \' " \: \{ \} \[ \] , & \* \# \?] | \A[ \- ? | < > = ! % @ ` ]/x', $value);
    }

    /**
     * Escapes and surrounds a PHP value with single quotes.
     *
     * @param string $value A PHP value
     *
     * @return string The quoted, escaped string
     */
    public static function escapeWithSingleQuotes($value)
    {
        return sprintf("'%s'", str_replace('\'', '\'\'', $value));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Yaml;

use Symfony\Component\Yaml\Exception\ParseException;

/**
 * Unescaper encapsulates unescaping rules for single and double-quoted
 * YAML strings.
 *
 * @author Matthew Lewinski <matthew@lewinski.org>
 *
 * @internal
 */
class Unescaper
{
    /**
     * Regex fragment that matches an escaped character in a double quoted string.
     */
    const REGEX_ESCAPED_CHARACTER = '\\\\(x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8}|.)';

    /**
     * Unescapes a single quoted string.
     *
     * @param string $value A single quoted string
     *
     * @return string The unescaped string
     */
    public function unescapeSingleQuotedString($value)
    {
        return str_replace('\'\'', '\'', $value);
    }

    /**
     * Unescapes a double quoted string.
     *
     * @param string $value A double quoted string
     *
     * @return string The unescaped string
     */
    public function unescapeDoubleQuotedString($value)
    {
        $callback = function ($match) {
            return $this->unescapeCharacter($match[0]);
        };

        // evaluate the string
        return preg_replace_callback('/'.self::REGEX_ESCAPED_CHARACTER.'/u', $callback, $value);
    }

    /**
     * Unescapes a character that was found in a double-quoted string.
     *
     * @param string $value An escaped character
     *
     * @return string The unescaped character
     */
    private function unescapeCharacter($value)
    {
        switch ($value[1]) {
            case '0':
                return "\x0";
            case 'a':
                return "\x7";
            case 'b':
                return "\x8";
            case 't':
                return "\t";
            case "\t":
                return "\t";
            case 'n':
                return "\n";
            case 'v':
                return "\xB";
            case 'f':
                return "\xC";
            case 'r':
                return "\r";
            case 'e':
                return "\x1B";
            case ' ':
                return ' ';
            case '"':
                return '"';
            case '/':
                return '/';
            case '\\':
                return '\\';
            case 'N':
                // U+0085 NEXT LINE
                return "\xC2\x85";
            case '_':
                // U+00A0 NO-BREAK SPACE
                return "\xC2\xA0";
            case 'L':
                // U+2028 LINE SEPARATOR
                return "\xE2\x80\xA8";
            case 'P':
                // U+2029 PARAGRAPH SEPARATOR
                return "\xE2\x80\xA9";
            case 'x':
                return self::utf8chr(hexdec(substr($value, 2, 2)));
            case 'u':
                return self::utf8chr(hexdec(substr($value, 2, 4)));
            case 'U':
                return self::utf8chr(hexdec(substr($value, 2, 8)));
            default:
                throw new ParseException(sprintf('Found unknown escape character "%s".', $value));
        }
    }

    /**
     * Get the UTF-8 character for the given code point.
     *
     * @param int $c The unicode code point
     *
     * @return string The corresponding UTF-8 character
     */
    private static function utf8chr($c)
    {
        if (0x80 > $c %= 0x200000) {
            return \chr($c);
        }
        if (0x800 > $c) {
            return \chr(0xC0 | $c >> 6).\chr(0x80 | $c & 0x3F);
        }
        if (0x10000 > $c) {
            return \chr(0xE0 | $c >> 12).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F);
        }

        return \chr(0xF0 | $c >> 18).\chr(0x80 | $c >> 12 & 0x3F).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Yaml;

/**
 * Dumper dumps PHP variables to YAML strings.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @final since version 3.4
 */
class Dumper
{
    /**
     * The amount of spaces to use for indentation of nested nodes.
     *
     * @var int
     */
    protected $indentation;

    /**
     * @param int $indentation
     */
    public function __construct($indentation = 4)
    {
        if ($indentation < 1) {
            throw new \InvalidArgumentException('The indentation must be greater than zero.');
        }

        $this->indentation = $indentation;
    }

    /**
     * Sets the indentation.
     *
     * @param int $num The amount of spaces to use for indentation of nested nodes
     *
     * @deprecated since version 3.1, to be removed in 4.0. Pass the indentation to the constructor instead.
     */
    public function setIndentation($num)
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 3.1 and will be removed in 4.0. Pass the indentation to the constructor instead.', E_USER_DEPRECATED);

        $this->indentation = (int) $num;
    }

    /**
     * Dumps a PHP value to YAML.
     *
     * @param mixed $input  The PHP value
     * @param int   $inline The level where you switch to inline YAML
     * @param int   $indent The level of indentation (used internally)
     * @param int   $flags  A bit field of Yaml::DUMP_* constants to customize the dumped YAML string
     *
     * @return string The YAML representation of the PHP value
     */
    public function dump($input, $inline = 0, $indent = 0, $flags = 0)
    {
        if (\is_bool($flags)) {
            @trigger_error('Passing a boolean flag to toggle exception handling is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE flag instead.', E_USER_DEPRECATED);

            if ($flags) {
                $flags = Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE;
            } else {
                $flags = 0;
            }
        }

        if (\func_num_args() >= 5) {
            @trigger_error('Passing a boolean flag to toggle object support is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::DUMP_OBJECT flag instead.', E_USER_DEPRECATED);

            if (func_get_arg(4)) {
                $flags |= Yaml::DUMP_OBJECT;
            }
        }

        $output = '';
        $prefix = $indent ? str_repeat(' ', $indent) : '';
        $dumpObjectAsInlineMap = true;

        if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($input instanceof \ArrayObject || $input instanceof \stdClass)) {
            $dumpObjectAsInlineMap = empty((array) $input);
        }

        if ($inline <= 0 || (!\is_array($input) && $dumpObjectAsInlineMap) || empty($input)) {
            $output .= $prefix.Inline::dump($input, $flags);
        } else {
            $dumpAsMap = Inline::isHash($input);

            foreach ($input as $key => $value) {
                if ($inline >= 1 && Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value) && false !== strpos($value, "\n") && false === strpos($value, "\r\n")) {
                    // If the first line starts with a space character, the spec requires a blockIndicationIndicator
                    // http://www.yaml.org/spec/1.2/spec.html#id2793979
                    $blockIndentationIndicator = (' ' === substr($value, 0, 1)) ? (string) $this->indentation : '';
                    $output .= sprintf("%s%s%s |%s\n", $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', '', $blockIndentationIndicator);

                    foreach (preg_split('/\n|\r\n/', $value) as $row) {
                        $output .= sprintf("%s%s%s\n", $prefix, str_repeat(' ', $this->indentation), $row);
                    }

                    continue;
                }

                $dumpObjectAsInlineMap = true;

                if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \ArrayObject || $value instanceof \stdClass)) {
                    $dumpObjectAsInlineMap = empty((array) $value);
                }

                $willBeInlined = $inline - 1 <= 0 || !\is_array($value) && $dumpObjectAsInlineMap || empty($value);

                $output .= sprintf('%s%s%s%s',
                    $prefix,
                    $dumpAsMap ? Inline::dump($key, $flags).':' : '-',
                    $willBeInlined ? ' ' : "\n",
                    $this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + $this->indentation, $flags)
                ).($willBeInlined ? "\n" : '');
            }
        }

        return $output;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Yaml;

use Symfony\Component\Yaml\Exception\DumpException;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Tag\TaggedValue;

/**
 * Inline implements a YAML parser/dumper for the YAML inline syntax.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @internal
 */
class Inline
{
    const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')';

    public static $parsedLineNumber = -1;
    public static $parsedFilename;

    private static $exceptionOnInvalidType = false;
    private static $objectSupport = false;
    private static $objectForMap = false;
    private static $constantSupport = false;

    /**
     * @param int         $flags
     * @param int|null    $parsedLineNumber
     * @param string|null $parsedFilename
     */
    public static function initialize($flags, $parsedLineNumber = null, $parsedFilename = null)
    {
        self::$exceptionOnInvalidType = (bool) (Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE & $flags);
        self::$objectSupport = (bool) (Yaml::PARSE_OBJECT & $flags);
        self::$objectForMap = (bool) (Yaml::PARSE_OBJECT_FOR_MAP & $flags);
        self::$constantSupport = (bool) (Yaml::PARSE_CONSTANT & $flags);
        self::$parsedFilename = $parsedFilename;

        if (null !== $parsedLineNumber) {
            self::$parsedLineNumber = $parsedLineNumber;
        }
    }

    /**
     * Converts a YAML string to a PHP value.
     *
     * @param string $value      A YAML string
     * @param int    $flags      A bit field of PARSE_* constants to customize the YAML parser behavior
     * @param array  $references Mapping of variable names to values
     *
     * @return mixed A PHP value
     *
     * @throws ParseException
     */
    public static function parse($value, $flags = 0, $references = [])
    {
        if (\is_bool($flags)) {
            @trigger_error('Passing a boolean flag to toggle exception handling is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE flag instead.', E_USER_DEPRECATED);

            if ($flags) {
                $flags = Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE;
            } else {
                $flags = 0;
            }
        }

        if (\func_num_args() >= 3 && !\is_array($references)) {
            @trigger_error('Passing a boolean flag to toggle object support is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT flag instead.', E_USER_DEPRECATED);

            if ($references) {
                $flags |= Yaml::PARSE_OBJECT;
            }

            if (\func_num_args() >= 4) {
                @trigger_error('Passing a boolean flag to toggle object for map support is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT_FOR_MAP flag instead.', E_USER_DEPRECATED);

                if (func_get_arg(3)) {
                    $flags |= Yaml::PARSE_OBJECT_FOR_MAP;
                }
            }

            if (\func_num_args() >= 5) {
                $references = func_get_arg(4);
            } else {
                $references = [];
            }
        }

        self::initialize($flags);

        $value = trim($value);

        if ('' === $value) {
            return '';
        }

        if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) {
            $mbEncoding = mb_internal_encoding();
            mb_internal_encoding('ASCII');
        }

        try {
            $i = 0;
            $tag = self::parseTag($value, $i, $flags);
            switch ($value[$i]) {
                case '[':
                    $result = self::parseSequence($value, $flags, $i, $references);
                    ++$i;
                    break;
                case '{':
                    $result = self::parseMapping($value, $flags, $i, $references);
                    ++$i;
                    break;
                default:
                    $result = self::parseScalar($value, $flags, null, $i, null === $tag, $references);
            }

            if (null !== $tag) {
                return new TaggedValue($tag, $result);
            }

            // some comments are allowed at the end
            if (preg_replace('/\s+#.*$/A', '', substr($value, $i))) {
                throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i)), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
            }

            return $result;
        } finally {
            if (isset($mbEncoding)) {
                mb_internal_encoding($mbEncoding);
            }
        }
    }

    /**
     * Dumps a given PHP variable to a YAML string.
     *
     * @param mixed $value The PHP variable to convert
     * @param int   $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string
     *
     * @return string The YAML string representing the PHP value
     *
     * @throws DumpException When trying to dump PHP resource
     */
    public static function dump($value, $flags = 0)
    {
        if (\is_bool($flags)) {
            @trigger_error('Passing a boolean flag to toggle exception handling is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE flag instead.', E_USER_DEPRECATED);

            if ($flags) {
                $flags = Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE;
            } else {
                $flags = 0;
            }
        }

        if (\func_num_args() >= 3) {
            @trigger_error('Passing a boolean flag to toggle object support is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::DUMP_OBJECT flag instead.', E_USER_DEPRECATED);

            if (func_get_arg(2)) {
                $flags |= Yaml::DUMP_OBJECT;
            }
        }

        switch (true) {
            case \is_resource($value):
                if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) {
                    throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value)));
                }

                return 'null';
            case $value instanceof \DateTimeInterface:
                return $value->format('c');
            case \is_object($value):
                if ($value instanceof TaggedValue) {
                    return '!'.$value->getTag().' '.self::dump($value->getValue(), $flags);
                }

                if (Yaml::DUMP_OBJECT & $flags) {
                    return '!php/object '.self::dump(serialize($value));
                }

                if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \stdClass || $value instanceof \ArrayObject)) {
                    return self::dumpArray($value, $flags & ~Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE);
                }

                if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) {
                    throw new DumpException('Object support when dumping a YAML file has been disabled.');
                }

                return 'null';
            case \is_array($value):
                return self::dumpArray($value, $flags);
            case null === $value:
                return 'null';
            case true === $value:
                return 'true';
            case false === $value:
                return 'false';
            case ctype_digit($value):
                return \is_string($value) ? "'$value'" : (int) $value;
            case is_numeric($value):
                $locale = setlocale(LC_NUMERIC, 0);
                if (false !== $locale) {
                    setlocale(LC_NUMERIC, 'C');
                }
                if (\is_float($value)) {
                    $repr = (string) $value;
                    if (is_infinite($value)) {
                        $repr = str_ireplace('INF', '.Inf', $repr);
                    } elseif (floor($value) == $value && $repr == $value) {
                        // Preserve float data type since storing a whole number will result in integer value.
                        $repr = '!!float '.$repr;
                    }
                } else {
                    $repr = \is_string($value) ? "'$value'" : (string) $value;
                }
                if (false !== $locale) {
                    setlocale(LC_NUMERIC, $locale);
                }

                return $repr;
            case '' == $value:
                return "''";
            case self::isBinaryString($value):
                return '!!binary '.base64_encode($value);
            case Escaper::requiresDoubleQuoting($value):
                return Escaper::escapeWithDoubleQuotes($value);
            case Escaper::requiresSingleQuoting($value):
            case Parser::preg_match('{^[0-9]+[_0-9]*$}', $value):
            case Parser::preg_match(self::getHexRegex(), $value):
            case Parser::preg_match(self::getTimestampRegex(), $value):
                return Escaper::escapeWithSingleQuotes($value);
            default:
                return $value;
        }
    }

    /**
     * Check if given array is hash or just normal indexed array.
     *
     * @internal
     *
     * @param array|\ArrayObject|\stdClass $value The PHP array or array-like object to check
     *
     * @return bool true if value is hash array, false otherwise
     */
    public static function isHash($value)
    {
        if ($value instanceof \stdClass || $value instanceof \ArrayObject) {
            return true;
        }

        $expectedKey = 0;

        foreach ($value as $key => $val) {
            if ($key !== $expectedKey++) {
                return true;
            }
        }

        return false;
    }

    /**
     * Dumps a PHP array to a YAML string.
     *
     * @param array $value The PHP array to dump
     * @param int   $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string
     *
     * @return string The YAML string representing the PHP array
     */
    private static function dumpArray($value, $flags)
    {
        // array
        if (($value || Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE & $flags) && !self::isHash($value)) {
            $output = [];
            foreach ($value as $val) {
                $output[] = self::dump($val, $flags);
            }

            return sprintf('[%s]', implode(', ', $output));
        }

        // hash
        $output = [];
        foreach ($value as $key => $val) {
            $output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags));
        }

        return sprintf('{ %s }', implode(', ', $output));
    }

    /**
     * Parses a YAML scalar.
     *
     * @param string   $scalar
     * @param int      $flags
     * @param string[] $delimiters
     * @param int      &$i
     * @param bool     $evaluate
     * @param array    $references
     *
     * @return string
     *
     * @throws ParseException When malformed inline YAML string is parsed
     *
     * @internal
     */
    public static function parseScalar($scalar, $flags = 0, $delimiters = null, &$i = 0, $evaluate = true, $references = [], $legacyOmittedKeySupport = false)
    {
        if (\in_array($scalar[$i], ['"', "'"])) {
            // quoted scalar
            $output = self::parseQuotedScalar($scalar, $i);

            if (null !== $delimiters) {
                $tmp = ltrim(substr($scalar, $i), ' ');
                if ('' === $tmp) {
                    throw new ParseException(sprintf('Unexpected end of line, expected one of "%s".', implode('', $delimiters)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
                }
                if (!\in_array($tmp[0], $delimiters)) {
                    throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
                }
            }
        } else {
            // "normal" string
            if (!$delimiters) {
                $output = substr($scalar, $i);
                $i += \strlen($output);

                // remove comments
                if (Parser::preg_match('/[ \t]+#/', $output, $match, PREG_OFFSET_CAPTURE)) {
                    $output = substr($output, 0, $match[0][1]);
                }
            } elseif (Parser::preg_match('/^(.'.($legacyOmittedKeySupport ? '+' : '*').'?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) {
                $output = $match[1];
                $i += \strlen($output);
            } else {
                throw new ParseException(sprintf('Malformed inline YAML string: %s.', $scalar), self::$parsedLineNumber + 1, null, self::$parsedFilename);
            }

            // a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >)
            if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0])) {
                throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0]), self::$parsedLineNumber + 1, $output, self::$parsedFilename);
            }

            if ($output && '%' === $output[0]) {
                @trigger_error(self::getDeprecationMessage(sprintf('Not quoting the scalar "%s" starting with the "%%" indicator character is deprecated since Symfony 3.1 and will throw a ParseException in 4.0.', $output)), E_USER_DEPRECATED);
            }

            if ($evaluate) {
                $output = self::evaluateScalar($output, $flags, $references);
            }
        }

        return $output;
    }

    /**
     * Parses a YAML quoted scalar.
     *
     * @param string $scalar
     * @param int    &$i
     *
     * @return string
     *
     * @throws ParseException When malformed inline YAML string is parsed
     */
    private static function parseQuotedScalar($scalar, &$i)
    {
        if (!Parser::preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) {
            throw new ParseException(sprintf('Malformed inline YAML string: %s.', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
        }

        $output = substr($match[0], 1, \strlen($match[0]) - 2);

        $unescaper = new Unescaper();
        if ('"' == $scalar[$i]) {
            $output = $unescaper->unescapeDoubleQuotedString($output);
        } else {
            $output = $unescaper->unescapeSingleQuotedString($output);
        }

        $i += \strlen($match[0]);

        return $output;
    }

    /**
     * Parses a YAML sequence.
     *
     * @param string $sequence
     * @param int    $flags
     * @param int    &$i
     * @param array  $references
     *
     * @return array
     *
     * @throws ParseException When malformed inline YAML string is parsed
     */
    private static function parseSequence($sequence, $flags, &$i = 0, $references = [])
    {
        $output = [];
        $len = \strlen($sequence);
        ++$i;

        // [foo, bar, ...]
        while ($i < $len) {
            if (']' === $sequence[$i]) {
                return $output;
            }
            if (',' === $sequence[$i] || ' ' === $sequence[$i]) {
                ++$i;

                continue;
            }

            $tag = self::parseTag($sequence, $i, $flags);
            switch ($sequence[$i]) {
                case '[':
                    // nested sequence
                    $value = self::parseSequence($sequence, $flags, $i, $references);
                    break;
                case '{':
                    // nested mapping
                    $value = self::parseMapping($sequence, $flags, $i, $references);
                    break;
                default:
                    $isQuoted = \in_array($sequence[$i], ['"', "'"]);
                    $value = self::parseScalar($sequence, $flags, [',', ']'], $i, null === $tag, $references);

                    // the value can be an array if a reference has been resolved to an array var
                    if (\is_string($value) && !$isQuoted && false !== strpos($value, ': ')) {
                        // embedded mapping?
                        try {
                            $pos = 0;
                            $value = self::parseMapping('{'.$value.'}', $flags, $pos, $references);
                        } catch (\InvalidArgumentException $e) {
                            // no, it's not
                        }
                    }

                    --$i;
            }

            if (null !== $tag) {
                $value = new TaggedValue($tag, $value);
            }

            $output[] = $value;

            ++$i;
        }

        throw new ParseException(sprintf('Malformed inline YAML string: %s.', $sequence), self::$parsedLineNumber + 1, null, self::$parsedFilename);
    }

    /**
     * Parses a YAML mapping.
     *
     * @param string $mapping
     * @param int    $flags
     * @param int    &$i
     * @param array  $references
     *
     * @return array|\stdClass
     *
     * @throws ParseException When malformed inline YAML string is parsed
     */
    private static function parseMapping($mapping, $flags, &$i = 0, $references = [])
    {
        $output = [];
        $len = \strlen($mapping);
        ++$i;
        $allowOverwrite = false;

        // {foo: bar, bar:foo, ...}
        while ($i < $len) {
            switch ($mapping[$i]) {
                case ' ':
                case ',':
                    ++$i;
                    continue 2;
                case '}':
                    if (self::$objectForMap) {
                        return (object) $output;
                    }

                    return $output;
            }

            // key
            $isKeyQuoted = \in_array($mapping[$i], ['"', "'"], true);
            $key = self::parseScalar($mapping, $flags, [':', ' '], $i, false, [], true);

            if (':' !== $key && false === $i = strpos($mapping, ':', $i)) {
                break;
            }

            if (':' === $key) {
                @trigger_error(self::getDeprecationMessage('Omitting the key of a mapping is deprecated and will throw a ParseException in 4.0.'), E_USER_DEPRECATED);
            }

            if (!$isKeyQuoted) {
                $evaluatedKey = self::evaluateScalar($key, $flags, $references);

                if ('' !== $key && $evaluatedKey !== $key && !\is_string($evaluatedKey) && !\is_int($evaluatedKey)) {
                    @trigger_error(self::getDeprecationMessage('Implicit casting of incompatible mapping keys to strings is deprecated since Symfony 3.3 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0. Quote your evaluable mapping keys instead.'), E_USER_DEPRECATED);
                }
            }

            if (':' !== $key && !$isKeyQuoted && (!isset($mapping[$i + 1]) || !\in_array($mapping[$i + 1], [' ', ',', '[', ']', '{', '}'], true))) {
                @trigger_error(self::getDeprecationMessage('Using a colon after an unquoted mapping key that is not followed by an indication character (i.e. " ", ",", "[", "]", "{", "}") is deprecated since Symfony 3.2 and will throw a ParseException in 4.0.'), E_USER_DEPRECATED);
            }

            if ('<<' === $key) {
                $allowOverwrite = true;
            }

            while ($i < $len) {
                if (':' === $mapping[$i] || ' ' === $mapping[$i]) {
                    ++$i;

                    continue;
                }

                $tag = self::parseTag($mapping, $i, $flags);
                switch ($mapping[$i]) {
                    case '[':
                        // nested sequence
                        $value = self::parseSequence($mapping, $flags, $i, $references);
                        // Spec: Keys MUST be unique; first one wins.
                        // Parser cannot abort this mapping earlier, since lines
                        // are processed sequentially.
                        // But overwriting is allowed when a merge node is used in current block.
                        if ('<<' === $key) {
                            foreach ($value as $parsedValue) {
                                $output += $parsedValue;
                            }
                        } elseif ($allowOverwrite || !isset($output[$key])) {
                            if (null !== $tag) {
                                $output[$key] = new TaggedValue($tag, $value);
                            } else {
                                $output[$key] = $value;
                            }
                        } elseif (isset($output[$key])) {
                            @trigger_error(self::getDeprecationMessage(sprintf('Duplicate key "%s" detected whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since Symfony 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key)), E_USER_DEPRECATED);
                        }
                        break;
                    case '{':
                        // nested mapping
                        $value = self::parseMapping($mapping, $flags, $i, $references);
                        // Spec: Keys MUST be unique; first one wins.
                        // Parser cannot abort this mapping earlier, since lines
                        // are processed sequentially.
                        // But overwriting is allowed when a merge node is used in current block.
                        if ('<<' === $key) {
                            $output += $value;
                        } elseif ($allowOverwrite || !isset($output[$key])) {
                            if (null !== $tag) {
                                $output[$key] = new TaggedValue($tag, $value);
                            } else {
                                $output[$key] = $value;
                            }
                        } elseif (isset($output[$key])) {
                            @trigger_error(self::getDeprecationMessage(sprintf('Duplicate key "%s" detected whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since Symfony 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key)), E_USER_DEPRECATED);
                        }
                        break;
                    default:
                        $value = self::parseScalar($mapping, $flags, [',', '}'], $i, null === $tag, $references);
                        // Spec: Keys MUST be unique; first one wins.
                        // Parser cannot abort this mapping earlier, since lines
                        // are processed sequentially.
                        // But overwriting is allowed when a merge node is used in current block.
                        if ('<<' === $key) {
                            $output += $value;
                        } elseif ($allowOverwrite || !isset($output[$key])) {
                            if (null !== $tag) {
                                $output[$key] = new TaggedValue($tag, $value);
                            } else {
                                $output[$key] = $value;
                            }
                        } elseif (isset($output[$key])) {
                            @trigger_error(self::getDeprecationMessage(sprintf('Duplicate key "%s" detected whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since Symfony 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key)), E_USER_DEPRECATED);
                        }
                        --$i;
                }
                ++$i;

                continue 2;
            }
        }

        throw new ParseException(sprintf('Malformed inline YAML string: %s.', $mapping), self::$parsedLineNumber + 1, null, self::$parsedFilename);
    }

    /**
     * Evaluates scalars and replaces magic values.
     *
     * @param string $scalar
     * @param int    $flags
     * @param array  $references
     *
     * @return mixed The evaluated YAML string
     *
     * @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved
     */
    private static function evaluateScalar($scalar, $flags, $references = [])
    {
        $scalar = trim($scalar);
        $scalarLower = strtolower($scalar);

        if (0 === strpos($scalar, '*')) {
            if (false !== $pos = strpos($scalar, '#')) {
                $value = substr($scalar, 1, $pos - 2);
            } else {
                $value = substr($scalar, 1);
            }

            // an unquoted *
            if (false === $value || '' === $value) {
                throw new ParseException('A reference must contain at least one character.', self::$parsedLineNumber + 1, $value, self::$parsedFilename);
            }

            if (!\array_key_exists($value, $references)) {
                throw new ParseException(sprintf('Reference "%s" does not exist.', $value), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
            }

            return $references[$value];
        }

        switch (true) {
            case 'null' === $scalarLower:
            case '' === $scalar:
            case '~' === $scalar:
                return;
            case 'true' === $scalarLower:
                return true;
            case 'false' === $scalarLower:
                return false;
            case '!' === $scalar[0]:
                switch (true) {
                    case 0 === strpos($scalar, '!str'):
                        @trigger_error(self::getDeprecationMessage('Support for the !str tag is deprecated since Symfony 3.4. Use the !!str tag instead.'), E_USER_DEPRECATED);

                        return (string) substr($scalar, 5);
                    case 0 === strpos($scalar, '!!str '):
                        return (string) substr($scalar, 6);
                    case 0 === strpos($scalar, '! '):
                        @trigger_error(self::getDeprecationMessage('Using the non-specific tag "!" is deprecated since Symfony 3.4 as its behavior will change in 4.0. It will force non-evaluating your values in 4.0. Use plain integers or !!float instead.'), E_USER_DEPRECATED);

                        return (int) self::parseScalar(substr($scalar, 2), $flags);
                    case 0 === strpos($scalar, '!php/object:'):
                        if (self::$objectSupport) {
                            @trigger_error(self::getDeprecationMessage('The !php/object: tag to indicate dumped PHP objects is deprecated since Symfony 3.4 and will be removed in 4.0. Use the !php/object (without the colon) tag instead.'), E_USER_DEPRECATED);

                            return unserialize(substr($scalar, 12));
                        }

                        if (self::$exceptionOnInvalidType) {
                            throw new ParseException('Object support when parsing a YAML file has been disabled.', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
                        }

                        return;
                    case 0 === strpos($scalar, '!!php/object:'):
                        if (self::$objectSupport) {
                            @trigger_error(self::getDeprecationMessage('The !!php/object: tag to indicate dumped PHP objects is deprecated since Symfony 3.1 and will be removed in 4.0. Use the !php/object (without the colon) tag instead.'), E_USER_DEPRECATED);

                            return unserialize(substr($scalar, 13));
                        }

                        if (self::$exceptionOnInvalidType) {
                            throw new ParseException('Object support when parsing a YAML file has been disabled.', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
                        }

                        return;
                    case 0 === strpos($scalar, '!php/object'):
                        if (self::$objectSupport) {
                            return unserialize(self::parseScalar(substr($scalar, 12)));
                        }

                        if (self::$exceptionOnInvalidType) {
                            throw new ParseException('Object support when parsing a YAML file has been disabled.', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
                        }

                        return;
                    case 0 === strpos($scalar, '!php/const:'):
                        if (self::$constantSupport) {
                            @trigger_error(self::getDeprecationMessage('The !php/const: tag to indicate dumped PHP constants is deprecated since Symfony 3.4 and will be removed in 4.0. Use the !php/const (without the colon) tag instead.'), E_USER_DEPRECATED);

                            if (\defined($const = substr($scalar, 11))) {
                                return \constant($const);
                            }

                            throw new ParseException(sprintf('The constant "%s" is not defined.', $const), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
                        }
                        if (self::$exceptionOnInvalidType) {
                            throw new ParseException(sprintf('The string "%s" could not be parsed as a constant. Have you forgotten to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
                        }

                        return;
                    case 0 === strpos($scalar, '!php/const'):
                        if (self::$constantSupport) {
                            $i = 0;
                            if (\defined($const = self::parseScalar(substr($scalar, 11), 0, null, $i, false))) {
                                return \constant($const);
                            }

                            throw new ParseException(sprintf('The constant "%s" is not defined.', $const), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
                        }
                        if (self::$exceptionOnInvalidType) {
                            throw new ParseException(sprintf('The string "%s" could not be parsed as a constant. Have you forgotten to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
                        }

                        return;
                    case 0 === strpos($scalar, '!!float '):
                        return (float) substr($scalar, 8);
                    case 0 === strpos($scalar, '!!binary '):
                        return self::evaluateBinaryScalar(substr($scalar, 9));
                    default:
                        @trigger_error(self::getDeprecationMessage(sprintf('Using the unquoted scalar value "%s" is deprecated since Symfony 3.3 and will be considered as a tagged value in 4.0. You must quote it.', $scalar)), E_USER_DEPRECATED);
                }

            // Optimize for returning strings.
            // no break
            case '+' === $scalar[0] || '-' === $scalar[0] || '.' === $scalar[0] || is_numeric($scalar[0]):
                switch (true) {
                    case Parser::preg_match('{^[+-]?[0-9][0-9_]*$}', $scalar):
                        $scalar = str_replace('_', '', (string) $scalar);
                        // omitting the break / return as integers are handled in the next case
                        // no break
                    case ctype_digit($scalar):
                        $raw = $scalar;
                        $cast = (int) $scalar;

                        return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw);
                    case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)):
                        $raw = $scalar;
                        $cast = (int) $scalar;

                        return '0' == $scalar[1] ? octdec($scalar) : (((string) $raw === (string) $cast) ? $cast : $raw);
                    case is_numeric($scalar):
                    case Parser::preg_match(self::getHexRegex(), $scalar):
                        $scalar = str_replace('_', '', $scalar);

                        return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar;
                    case '.inf' === $scalarLower:
                    case '.nan' === $scalarLower:
                        return -log(0);
                    case '-.inf' === $scalarLower:
                        return log(0);
                    case Parser::preg_match('/^(-|\+)?[0-9][0-9,]*(\.[0-9_]+)?$/', $scalar):
                    case Parser::preg_match('/^(-|\+)?[0-9][0-9_]*(\.[0-9_]+)?$/', $scalar):
                        if (false !== strpos($scalar, ',')) {
                            @trigger_error(self::getDeprecationMessage('Using the comma as a group separator for floats is deprecated since Symfony 3.2 and will be removed in 4.0.'), E_USER_DEPRECATED);
                        }

                        return (float) str_replace([',', '_'], '', $scalar);
                    case Parser::preg_match(self::getTimestampRegex(), $scalar):
                        if (Yaml::PARSE_DATETIME & $flags) {
                            // When no timezone is provided in the parsed date, YAML spec says we must assume UTC.
                            return new \DateTime($scalar, new \DateTimeZone('UTC'));
                        }

                        $timeZone = date_default_timezone_get();
                        date_default_timezone_set('UTC');
                        $time = strtotime($scalar);
                        date_default_timezone_set($timeZone);

                        return $time;
                }
        }

        return (string) $scalar;
    }

    /**
     * @param string $value
     * @param int    &$i
     * @param int    $flags
     *
     * @return string|null
     */
    private static function parseTag($value, &$i, $flags)
    {
        if ('!' !== $value[$i]) {
            return;
        }

        $tagLength = strcspn($value, " \t\n", $i + 1);
        $tag = substr($value, $i + 1, $tagLength);

        $nextOffset = $i + $tagLength + 1;
        $nextOffset += strspn($value, ' ', $nextOffset);

        // Is followed by a scalar
        if ((!isset($value[$nextOffset]) || !\in_array($value[$nextOffset], ['[', '{'], true)) && 'tagged' !== $tag) {
            // Manage non-whitelisted scalars in {@link self::evaluateScalar()}
            return;
        }

        // Built-in tags
        if ($tag && '!' === $tag[0]) {
            throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
        }

        if (Yaml::PARSE_CUSTOM_TAGS & $flags) {
            $i = $nextOffset;

            return $tag;
        }

        throw new ParseException(sprintf('Tags support is not enabled. Enable the `Yaml::PARSE_CUSTOM_TAGS` flag to use "!%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
    }

    /**
     * @param string $scalar
     *
     * @return string
     *
     * @internal
     */
    public static function evaluateBinaryScalar($scalar)
    {
        $parsedBinaryData = self::parseScalar(preg_replace('/\s/', '', $scalar));

        if (0 !== (\strlen($parsedBinaryData) % 4)) {
            throw new ParseException(sprintf('The normalized base64 encoded data (data without whitespace characters) length must be a multiple of four (%d bytes given).', \strlen($parsedBinaryData)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
        }

        if (!Parser::preg_match('#^[A-Z0-9+/]+={0,2}$#i', $parsedBinaryData)) {
            throw new ParseException(sprintf('The base64 encoded data (%s) contains invalid characters.', $parsedBinaryData), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
        }

        return base64_decode($parsedBinaryData, true);
    }

    private static function isBinaryString($value)
    {
        return !preg_match('//u', $value) || preg_match('/[^\x00\x07-\x0d\x1B\x20-\xff]/', $value);
    }

    /**
     * Gets a regex that matches a YAML date.
     *
     * @return string The regular expression
     *
     * @see http://www.yaml.org/spec/1.2/spec.html#id2761573
     */
    private static function getTimestampRegex()
    {
        return <<<EOF
        ~^
        (?P<year>[0-9][0-9][0-9][0-9])
        -(?P<month>[0-9][0-9]?)
        -(?P<day>[0-9][0-9]?)
        (?:(?:[Tt]|[ \t]+)
        (?P<hour>[0-9][0-9]?)
        :(?P<minute>[0-9][0-9])
        :(?P<second>[0-9][0-9])
        (?:\.(?P<fraction>[0-9]*))?
        (?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)
        (?::(?P<tz_minute>[0-9][0-9]))?))?)?
        $~x
EOF;
    }

    /**
     * Gets a regex that matches a YAML number in hexadecimal notation.
     *
     * @return string
     */
    private static function getHexRegex()
    {
        return '~^0x[0-9a-f_]++$~i';
    }

    private static function getDeprecationMessage($message)
    {
        $message = rtrim($message, '.');

        if (null !== self::$parsedFilename) {
            $message .= ' in '.self::$parsedFilename;
        }

        if (-1 !== self::$parsedLineNumber) {
            $message .= ' on line '.(self::$parsedLineNumber + 1);
        }

        return $message.'.';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Yaml;

use Symfony\Component\Yaml\Exception\ParseException;

/**
 * Yaml offers convenience methods to load and dump YAML.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @final since version 3.4
 */
class Yaml
{
    const DUMP_OBJECT = 1;
    const PARSE_EXCEPTION_ON_INVALID_TYPE = 2;
    const PARSE_OBJECT = 4;
    const PARSE_OBJECT_FOR_MAP = 8;
    const DUMP_EXCEPTION_ON_INVALID_TYPE = 16;
    const PARSE_DATETIME = 32;
    const DUMP_OBJECT_AS_MAP = 64;
    const DUMP_MULTI_LINE_LITERAL_BLOCK = 128;
    const PARSE_CONSTANT = 256;
    const PARSE_CUSTOM_TAGS = 512;
    const DUMP_EMPTY_ARRAY_AS_SEQUENCE = 1024;

    /**
     * @deprecated since version 3.4, to be removed in 4.0. Quote your evaluable keys instead.
     */
    const PARSE_KEYS_AS_STRINGS = 2048;

    /**
     * Parses a YAML file into a PHP value.
     *
     * Usage:
     *
     *     $array = Yaml::parseFile('config.yml');
     *     print_r($array);
     *
     * @param string $filename The path to the YAML file to be parsed
     * @param int    $flags    A bit field of PARSE_* constants to customize the YAML parser behavior
     *
     * @return mixed The YAML converted to a PHP value
     *
     * @throws ParseException If the file could not be read or the YAML is not valid
     */
    public static function parseFile($filename, $flags = 0)
    {
        $yaml = new Parser();

        return $yaml->parseFile($filename, $flags);
    }

    /**
     * Parses YAML into a PHP value.
     *
     *  Usage:
     *  <code>
     *   $array = Yaml::parse(file_get_contents('config.yml'));
     *   print_r($array);
     *  </code>
     *
     * @param string $input A string containing YAML
     * @param int    $flags A bit field of PARSE_* constants to customize the YAML parser behavior
     *
     * @return mixed The YAML converted to a PHP value
     *
     * @throws ParseException If the YAML is not valid
     */
    public static function parse($input, $flags = 0)
    {
        if (\is_bool($flags)) {
            @trigger_error('Passing a boolean flag to toggle exception handling is deprecated since Symfony 3.1 and will be removed in 4.0. Use the PARSE_EXCEPTION_ON_INVALID_TYPE flag instead.', E_USER_DEPRECATED);

            if ($flags) {
                $flags = self::PARSE_EXCEPTION_ON_INVALID_TYPE;
            } else {
                $flags = 0;
            }
        }

        if (\func_num_args() >= 3) {
            @trigger_error('Passing a boolean flag to toggle object support is deprecated since Symfony 3.1 and will be removed in 4.0. Use the PARSE_OBJECT flag instead.', E_USER_DEPRECATED);

            if (func_get_arg(2)) {
                $flags |= self::PARSE_OBJECT;
            }
        }

        if (\func_num_args() >= 4) {
            @trigger_error('Passing a boolean flag to toggle object for map support is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT_FOR_MAP flag instead.', E_USER_DEPRECATED);

            if (func_get_arg(3)) {
                $flags |= self::PARSE_OBJECT_FOR_MAP;
            }
        }

        $yaml = new Parser();

        return $yaml->parse($input, $flags);
    }

    /**
     * Dumps a PHP value to a YAML string.
     *
     * The dump method, when supplied with an array, will do its best
     * to convert the array into friendly YAML.
     *
     * @param mixed $input  The PHP value
     * @param int   $inline The level where you switch to inline YAML
     * @param int   $indent The amount of spaces to use for indentation of nested nodes
     * @param int   $flags  A bit field of DUMP_* constants to customize the dumped YAML string
     *
     * @return string A YAML string representing the original PHP value
     */
    public static function dump($input, $inline = 2, $indent = 4, $flags = 0)
    {
        if (\is_bool($flags)) {
            @trigger_error('Passing a boolean flag to toggle exception handling is deprecated since Symfony 3.1 and will be removed in 4.0. Use the DUMP_EXCEPTION_ON_INVALID_TYPE flag instead.', E_USER_DEPRECATED);

            if ($flags) {
                $flags = self::DUMP_EXCEPTION_ON_INVALID_TYPE;
            } else {
                $flags = 0;
            }
        }

        if (\func_num_args() >= 5) {
            @trigger_error('Passing a boolean flag to toggle object support is deprecated since Symfony 3.1 and will be removed in 4.0. Use the DUMP_OBJECT flag instead.', E_USER_DEPRECATED);

            if (func_get_arg(4)) {
                $flags |= self::DUMP_OBJECT;
            }
        }

        $yaml = new Dumper($indent);

        return $yaml->dump($input, $inline, 0, $flags);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Yaml\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Parser;
use Symfony\Component\Yaml\Yaml;

/**
 * Validates YAML files syntax and outputs encountered errors.
 *
 * @author GrÃ©goire Pineau <lyrixx@lyrixx.info>
 * @author Robin Chalas <robin.chalas@gmail.com>
 */
class LintCommand extends Command
{
    protected static $defaultName = 'lint:yaml';

    private $parser;
    private $format;
    private $displayCorrectFiles;
    private $directoryIteratorProvider;
    private $isReadableProvider;

    public function __construct($name = null, $directoryIteratorProvider = null, $isReadableProvider = null)
    {
        parent::__construct($name);

        $this->directoryIteratorProvider = $directoryIteratorProvider;
        $this->isReadableProvider = $isReadableProvider;
    }

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this
            ->setDescription('Lints a file and outputs encountered errors')
            ->addArgument('filename', null, 'A file or a directory or STDIN')
            ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt')
            ->addOption('parse-tags', null, InputOption::VALUE_NONE, 'Parse custom tags')
            ->setHelp(<<<EOF
The <info>%command.name%</info> command lints a YAML file and outputs to STDOUT
the first encountered syntax error.

You can validates YAML contents passed from STDIN:

  <info>cat filename | php %command.full_name%</info>

You can also validate the syntax of a file:

  <info>php %command.full_name% filename</info>

Or of a whole directory:

  <info>php %command.full_name% dirname</info>
  <info>php %command.full_name% dirname --format=json</info>

EOF
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $io = new SymfonyStyle($input, $output);
        $filename = $input->getArgument('filename');
        $this->format = $input->getOption('format');
        $this->displayCorrectFiles = $output->isVerbose();
        $flags = $input->getOption('parse-tags') ? Yaml::PARSE_CUSTOM_TAGS : 0;

        if (!$filename) {
            if (!$stdin = $this->getStdin()) {
                throw new RuntimeException('Please provide a filename or pipe file content to STDIN.');
            }

            return $this->display($io, [$this->validate($stdin, $flags)]);
        }

        if (!$this->isReadable($filename)) {
            throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename));
        }

        $filesInfo = [];
        foreach ($this->getFiles($filename) as $file) {
            $filesInfo[] = $this->validate(file_get_contents($file), $flags, $file);
        }

        return $this->display($io, $filesInfo);
    }

    private function validate($content, $flags, $file = null)
    {
        $prevErrorHandler = set_error_handler(function ($level, $message, $file, $line) use (&$prevErrorHandler) {
            if (E_USER_DEPRECATED === $level) {
                throw new ParseException($message, $this->getParser()->getRealCurrentLineNb() + 1);
            }

            return $prevErrorHandler ? $prevErrorHandler($level, $message, $file, $line) : false;
        });

        try {
            $this->getParser()->parse($content, Yaml::PARSE_CONSTANT | $flags);
        } catch (ParseException $e) {
            return ['file' => $file, 'line' => $e->getParsedLine(), 'valid' => false, 'message' => $e->getMessage()];
        } finally {
            restore_error_handler();
        }

        return ['file' => $file, 'valid' => true];
    }

    private function display(SymfonyStyle $io, array $files)
    {
        switch ($this->format) {
            case 'txt':
                return $this->displayTxt($io, $files);
            case 'json':
                return $this->displayJson($io, $files);
            default:
                throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $this->format));
        }
    }

    private function displayTxt(SymfonyStyle $io, array $filesInfo)
    {
        $countFiles = \count($filesInfo);
        $erroredFiles = 0;

        foreach ($filesInfo as $info) {
            if ($info['valid'] && $this->displayCorrectFiles) {
                $io->comment('<info>OK</info>'.($info['file'] ? sprintf(' in %s', $info['file']) : ''));
            } elseif (!$info['valid']) {
                ++$erroredFiles;
                $io->text('<error> ERROR </error>'.($info['file'] ? sprintf(' in %s', $info['file']) : ''));
                $io->text(sprintf('<error> >> %s</error>', $info['message']));
            }
        }

        if (0 === $erroredFiles) {
            $io->success(sprintf('All %d YAML files contain valid syntax.', $countFiles));
        } else {
            $io->warning(sprintf('%d YAML files have valid syntax and %d contain errors.', $countFiles - $erroredFiles, $erroredFiles));
        }

        return min($erroredFiles, 1);
    }

    private function displayJson(SymfonyStyle $io, array $filesInfo)
    {
        $errors = 0;

        array_walk($filesInfo, function (&$v) use (&$errors) {
            $v['file'] = (string) $v['file'];
            if (!$v['valid']) {
                ++$errors;
            }
        });

        $io->writeln(json_encode($filesInfo, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));

        return min($errors, 1);
    }

    private function getFiles($fileOrDirectory)
    {
        if (is_file($fileOrDirectory)) {
            yield new \SplFileInfo($fileOrDirectory);

            return;
        }

        foreach ($this->getDirectoryIterator($fileOrDirectory) as $file) {
            if (!\in_array($file->getExtension(), ['yml', 'yaml'])) {
                continue;
            }

            yield $file;
        }
    }

    private function getStdin()
    {
        if (0 !== ftell(STDIN)) {
            return;
        }

        $inputs = '';
        while (!feof(STDIN)) {
            $inputs .= fread(STDIN, 1024);
        }

        return $inputs;
    }

    private function getParser()
    {
        if (!$this->parser) {
            $this->parser = new Parser();
        }

        return $this->parser;
    }

    private function getDirectoryIterator($directory)
    {
        $default = function ($directory) {
            return new \RecursiveIteratorIterator(
                new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS),
                \RecursiveIteratorIterator::LEAVES_ONLY
            );
        };

        if (null !== $this->directoryIteratorProvider) {
            return \call_user_func($this->directoryIteratorProvider, $directory, $default);
        }

        return $default($directory);
    }

    private function isReadable($fileOrDirectory)
    {
        $default = function ($fileOrDirectory) {
            return is_readable($fileOrDirectory);
        };

        if (null !== $this->isReadableProvider) {
            return \call_user_func($this->isReadableProvider, $fileOrDirectory, $default);
        }

        return $default($fileOrDirectory);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Yaml\Exception;

/**
 * Exception class thrown when an error occurs during dumping.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class DumpException extends RuntimeException
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Yaml\Exception;

/**
 * Exception interface for all exceptions thrown by the component.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
interface ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Yaml\Exception;

/**
 * Exception class thrown when an error occurs during parsing.
 *
 * @author Romain Neutron <imprec@gmail.com>
 */
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Yaml\Exception;

/**
 * Exception class thrown when an error occurs during parsing.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ParseException extends RuntimeException
{
    private $parsedFile;
    private $parsedLine;
    private $snippet;
    private $rawMessage;

    /**
     * @param string          $message    The error message
     * @param int             $parsedLine The line where the error occurred
     * @param string|null     $snippet    The snippet of code near the problem
     * @param string|null     $parsedFile The file name where the error occurred
     * @param \Exception|null $previous   The previous exception
     */
    public function __construct($message, $parsedLine = -1, $snippet = null, $parsedFile = null, \Exception $previous = null)
    {
        $this->parsedFile = $parsedFile;
        $this->parsedLine = $parsedLine;
        $this->snippet = $snippet;
        $this->rawMessage = $message;

        $this->updateRepr();

        parent::__construct($this->message, 0, $previous);
    }

    /**
     * Gets the snippet of code near the error.
     *
     * @return string The snippet of code
     */
    public function getSnippet()
    {
        return $this->snippet;
    }

    /**
     * Sets the snippet of code near the error.
     *
     * @param string $snippet The code snippet
     */
    public function setSnippet($snippet)
    {
        $this->snippet = $snippet;

        $this->updateRepr();
    }

    /**
     * Gets the filename where the error occurred.
     *
     * This method returns null if a string is parsed.
     *
     * @return string The filename
     */
    public function getParsedFile()
    {
        return $this->parsedFile;
    }

    /**
     * Sets the filename where the error occurred.
     *
     * @param string $parsedFile The filename
     */
    public function setParsedFile($parsedFile)
    {
        $this->parsedFile = $parsedFile;

        $this->updateRepr();
    }

    /**
     * Gets the line where the error occurred.
     *
     * @return int The file line
     */
    public function getParsedLine()
    {
        return $this->parsedLine;
    }

    /**
     * Sets the line where the error occurred.
     *
     * @param int $parsedLine The file line
     */
    public function setParsedLine($parsedLine)
    {
        $this->parsedLine = $parsedLine;

        $this->updateRepr();
    }

    private function updateRepr()
    {
        $this->message = $this->rawMessage;

        $dot = false;
        if ('.' === substr($this->message, -1)) {
            $this->message = substr($this->message, 0, -1);
            $dot = true;
        }

        if (null !== $this->parsedFile) {
            $this->message .= sprintf(' in %s', json_encode($this->parsedFile, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
        }

        if ($this->parsedLine >= 0) {
            $this->message .= sprintf(' at line %d', $this->parsedLine);
        }

        if ($this->snippet) {
            $this->message .= sprintf(' (near "%s")', $this->snippet);
        }

        if ($dot) {
            $this->message .= '.';
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Yaml\Tag;

/**
 * @author Nicolas Grekas <p@tchwork.com>
 * @author Guilhem N. <egetick@gmail.com>
 */
final class TaggedValue
{
    private $tag;
    private $value;

    /**
     * @param string $tag
     * @param mixed  $value
     */
    public function __construct($tag, $value)
    {
        $this->tag = $tag;
        $this->value = $value;
    }

    /**
     * @return string
     */
    public function getTag()
    {
        return $this->tag;
    }

    /**
     * @return mixed
     */
    public function getValue()
    {
        return $this->value;
    }
}
{
    "name": "symfony/yaml",
    "type": "library",
    "description": "Symfony Yaml Component",
    "keywords": [],
    "homepage": "https://symfony.com",
    "license": "MIT",
    "authors": [
        {
            "name": "Fabien Potencier",
            "email": "fabien@symfony.com"
        },
        {
            "name": "Symfony Community",
            "homepage": "https://symfony.com/contributors"
        }
    ],
    "require": {
        "php": "^5.5.9|>=7.0.8",
        "symfony/polyfill-ctype": "~1.8"
    },
    "require-dev": {
        "symfony/console": "~3.4|~4.0"
    },
    "conflict": {
        "symfony/console": "<3.4"
    },
    "suggest": {
        "symfony/console": "For validating YAML files using the lint command"
    },
    "autoload": {
        "psr-4": { "Symfony\\Component\\Yaml\\": "" },
        "exclude-from-classmap": [
            "/Tests/"
        ]
    },
    "minimum-stability": "dev",
    "extra": {
        "branch-alias": {
            "dev-master": "3.4-dev"
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug;

/**
 * Registers all the debug tools.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Debug
{
    private static $enabled = false;

    /**
     * Enables the debug tools.
     *
     * This method registers an error handler and an exception handler.
     *
     * @param int  $errorReportingLevel The level of error reporting you want
     * @param bool $displayErrors       Whether to display errors (for development) or just log them (for production)
     */
    public static function enable($errorReportingLevel = E_ALL, $displayErrors = true)
    {
        if (static::$enabled) {
            return;
        }

        static::$enabled = true;

        if (null !== $errorReportingLevel) {
            error_reporting($errorReportingLevel);
        } else {
            error_reporting(E_ALL);
        }

        if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
            ini_set('display_errors', 0);
            ExceptionHandler::register();
        } elseif ($displayErrors && (!filter_var(ini_get('log_errors'), FILTER_VALIDATE_BOOLEAN) || ini_get('error_log'))) {
            // CLI - display errors only if they're not already logged to STDERR
            ini_set('display_errors', 1);
        }
        if ($displayErrors) {
            ErrorHandler::register(new ErrorHandler(new BufferingLogger()));
        } else {
            ErrorHandler::register()->throwAt(0, true);
        }

        DebugClassLoader::enable();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\FatalErrorHandler;

use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\Exception\UndefinedFunctionException;

/**
 * ErrorHandler for undefined functions.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class UndefinedFunctionFatalErrorHandler implements FatalErrorHandlerInterface
{
    /**
     * {@inheritdoc}
     */
    public function handleError(array $error, FatalErrorException $exception)
    {
        $messageLen = \strlen($error['message']);
        $notFoundSuffix = '()';
        $notFoundSuffixLen = \strlen($notFoundSuffix);
        if ($notFoundSuffixLen > $messageLen) {
            return;
        }

        if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {
            return;
        }

        $prefix = 'Call to undefined function ';
        $prefixLen = \strlen($prefix);
        if (0 !== strpos($error['message'], $prefix)) {
            return;
        }

        $fullyQualifiedFunctionName = substr($error['message'], $prefixLen, -$notFoundSuffixLen);
        if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedFunctionName, '\\')) {
            $functionName = substr($fullyQualifiedFunctionName, $namespaceSeparatorIndex + 1);
            $namespacePrefix = substr($fullyQualifiedFunctionName, 0, $namespaceSeparatorIndex);
            $message = sprintf('Attempted to call function "%s" from namespace "%s".', $functionName, $namespacePrefix);
        } else {
            $functionName = $fullyQualifiedFunctionName;
            $message = sprintf('Attempted to call function "%s" from the global namespace.', $functionName);
        }

        $candidates = [];
        foreach (get_defined_functions() as $type => $definedFunctionNames) {
            foreach ($definedFunctionNames as $definedFunctionName) {
                if (false !== $namespaceSeparatorIndex = strrpos($definedFunctionName, '\\')) {
                    $definedFunctionNameBasename = substr($definedFunctionName, $namespaceSeparatorIndex + 1);
                } else {
                    $definedFunctionNameBasename = $definedFunctionName;
                }

                if ($definedFunctionNameBasename === $functionName) {
                    $candidates[] = '\\'.$definedFunctionName;
                }
            }
        }

        if ($candidates) {
            sort($candidates);
            $last = array_pop($candidates).'"?';
            if ($candidates) {
                $candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last;
            } else {
                $candidates = '"'.$last;
            }
            $message .= "\nDid you mean to call ".$candidates;
        }

        return new UndefinedFunctionException($message, $exception);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\FatalErrorHandler;

use Symfony\Component\Debug\Exception\FatalErrorException;

/**
 * Attempts to convert fatal errors to exceptions.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
interface FatalErrorHandlerInterface
{
    /**
     * Attempts to convert an error into an exception.
     *
     * @param array               $error     An array as returned by error_get_last()
     * @param FatalErrorException $exception A FatalErrorException instance
     *
     * @return FatalErrorException|null A FatalErrorException instance if the class is able to convert the error, null otherwise
     */
    public function handleError(array $error, FatalErrorException $exception);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\FatalErrorHandler;

use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\Exception\UndefinedMethodException;

/**
 * ErrorHandler for undefined methods.
 *
 * @author GrÃ©goire Pineau <lyrixx@lyrixx.info>
 */
class UndefinedMethodFatalErrorHandler implements FatalErrorHandlerInterface
{
    /**
     * {@inheritdoc}
     */
    public function handleError(array $error, FatalErrorException $exception)
    {
        preg_match('/^Call to undefined method (.*)::(.*)\(\)$/', $error['message'], $matches);
        if (!$matches) {
            return;
        }

        $className = $matches[1];
        $methodName = $matches[2];

        $message = sprintf('Attempted to call an undefined method named "%s" of class "%s".', $methodName, $className);

        if (!class_exists($className) || null === $methods = get_class_methods($className)) {
            // failed to get the class or its methods on which an unknown method was called (for example on an anonymous class)
            return new UndefinedMethodException($message, $exception);
        }

        $candidates = [];
        foreach ($methods as $definedMethodName) {
            $lev = levenshtein($methodName, $definedMethodName);
            if ($lev <= \strlen($methodName) / 3 || false !== strpos($definedMethodName, $methodName)) {
                $candidates[] = $definedMethodName;
            }
        }

        if ($candidates) {
            sort($candidates);
            $last = array_pop($candidates).'"?';
            if ($candidates) {
                $candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last;
            } else {
                $candidates = '"'.$last;
            }

            $message .= "\nDid you mean to call ".$candidates;
        }

        return new UndefinedMethodException($message, $exception);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\FatalErrorHandler;

use Composer\Autoload\ClassLoader as ComposerClassLoader;
use Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader;
use Symfony\Component\Debug\DebugClassLoader;
use Symfony\Component\Debug\Exception\ClassNotFoundException;
use Symfony\Component\Debug\Exception\FatalErrorException;

/**
 * ErrorHandler for classes that do not exist.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
{
    /**
     * {@inheritdoc}
     */
    public function handleError(array $error, FatalErrorException $exception)
    {
        $messageLen = \strlen($error['message']);
        $notFoundSuffix = '\' not found';
        $notFoundSuffixLen = \strlen($notFoundSuffix);
        if ($notFoundSuffixLen > $messageLen) {
            return;
        }

        if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {
            return;
        }

        foreach (['class', 'interface', 'trait'] as $typeName) {
            $prefix = ucfirst($typeName).' \'';
            $prefixLen = \strlen($prefix);
            if (0 !== strpos($error['message'], $prefix)) {
                continue;
            }

            $fullyQualifiedClassName = substr($error['message'], $prefixLen, -$notFoundSuffixLen);
            if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedClassName, '\\')) {
                $className = substr($fullyQualifiedClassName, $namespaceSeparatorIndex + 1);
                $namespacePrefix = substr($fullyQualifiedClassName, 0, $namespaceSeparatorIndex);
                $message = sprintf('Attempted to load %s "%s" from namespace "%s".', $typeName, $className, $namespacePrefix);
                $tail = ' for another namespace?';
            } else {
                $className = $fullyQualifiedClassName;
                $message = sprintf('Attempted to load %s "%s" from the global namespace.', $typeName, $className);
                $tail = '?';
            }

            if ($candidates = $this->getClassCandidates($className)) {
                $tail = array_pop($candidates).'"?';
                if ($candidates) {
                    $tail = ' for e.g. "'.implode('", "', $candidates).'" or "'.$tail;
                } else {
                    $tail = ' for "'.$tail;
                }
            }
            $message .= "\nDid you forget a \"use\" statement".$tail;

            return new ClassNotFoundException($message, $exception);
        }
    }

    /**
     * Tries to guess the full namespace for a given class name.
     *
     * By default, it looks for PSR-0 and PSR-4 classes registered via a Symfony or a Composer
     * autoloader (that should cover all common cases).
     *
     * @param string $class A class name (without its namespace)
     *
     * @return array An array of possible fully qualified class names
     */
    private function getClassCandidates(string $class): array
    {
        if (!\is_array($functions = spl_autoload_functions())) {
            return [];
        }

        // find Symfony and Composer autoloaders
        $classes = [];

        foreach ($functions as $function) {
            if (!\is_array($function)) {
                continue;
            }
            // get class loaders wrapped by DebugClassLoader
            if ($function[0] instanceof DebugClassLoader) {
                $function = $function[0]->getClassLoader();

                if (!\is_array($function)) {
                    continue;
                }
            }

            if ($function[0] instanceof ComposerClassLoader || $function[0] instanceof SymfonyClassLoader) {
                foreach ($function[0]->getPrefixes() as $prefix => $paths) {
                    foreach ($paths as $path) {
                        $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix));
                    }
                }
            }
            if ($function[0] instanceof ComposerClassLoader) {
                foreach ($function[0]->getPrefixesPsr4() as $prefix => $paths) {
                    foreach ($paths as $path) {
                        $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix));
                    }
                }
            }
        }

        return array_unique($classes);
    }

    private function findClassInPath(string $path, string $class, string $prefix): array
    {
        if (!$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.\dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path)) {
            return [];
        }

        $classes = [];
        $filename = $class.'.php';
        foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
            if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName(), $prefix)) {
                $classes[] = $class;
            }
        }

        return $classes;
    }

    private function convertFileToClass(string $path, string $file, string $prefix): ?string
    {
        $candidates = [
            // namespaced class
            $namespacedClass = str_replace([$path.\DIRECTORY_SEPARATOR, '.php', '/'], ['', '', '\\'], $file),
            // namespaced class (with target dir)
            $prefix.$namespacedClass,
            // namespaced class (with target dir and separator)
            $prefix.'\\'.$namespacedClass,
            // PEAR class
            str_replace('\\', '_', $namespacedClass),
            // PEAR class (with target dir)
            str_replace('\\', '_', $prefix.$namespacedClass),
            // PEAR class (with target dir and separator)
            str_replace('\\', '_', $prefix.'\\'.$namespacedClass),
        ];

        if ($prefix) {
            $candidates = array_filter($candidates, function ($candidate) use ($prefix) { return 0 === strpos($candidate, $prefix); });
        }

        // We cannot use the autoloader here as most of them use require; but if the class
        // is not found, the new autoloader call will require the file again leading to a
        // "cannot redeclare class" error.
        foreach ($candidates as $candidate) {
            if ($this->classExists($candidate)) {
                return $candidate;
            }
        }

        require_once $file;

        foreach ($candidates as $candidate) {
            if ($this->classExists($candidate)) {
                return $candidate;
            }
        }

        return null;
    }

    private function classExists(string $class): bool
    {
        return class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug;

use Symfony\Component\Debug\Exception\FlattenException;
use Symfony\Component\Debug\Exception\OutOfMemoryException;
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;

/**
 * ExceptionHandler converts an exception to a Response object.
 *
 * It is mostly useful in debug mode to replace the default PHP/XDebug
 * output with something prettier and more useful.
 *
 * As this class is mainly used during Kernel boot, where nothing is yet
 * available, the Response content is always HTML.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Nicolas Grekas <p@tchwork.com>
 */
class ExceptionHandler
{
    private $debug;
    private $charset;
    private $handler;
    private $caughtBuffer;
    private $caughtLength;
    private $fileLinkFormat;

    public function __construct(bool $debug = true, string $charset = null, $fileLinkFormat = null)
    {
        $this->debug = $debug;
        $this->charset = $charset ?: ini_get('default_charset') ?: 'UTF-8';
        $this->fileLinkFormat = $fileLinkFormat;
    }

    /**
     * Registers the exception handler.
     *
     * @param bool        $debug          Enable/disable debug mode, where the stack trace is displayed
     * @param string|null $charset        The charset used by exception messages
     * @param string|null $fileLinkFormat The IDE link template
     *
     * @return static
     */
    public static function register($debug = true, $charset = null, $fileLinkFormat = null)
    {
        $handler = new static($debug, $charset, $fileLinkFormat);

        $prev = set_exception_handler([$handler, 'handle']);
        if (\is_array($prev) && $prev[0] instanceof ErrorHandler) {
            restore_exception_handler();
            $prev[0]->setExceptionHandler([$handler, 'handle']);
        }

        return $handler;
    }

    /**
     * Sets a user exception handler.
     *
     * @param callable $handler An handler that will be called on Exception
     *
     * @return callable|null The previous exception handler if any
     */
    public function setHandler(callable $handler = null)
    {
        $old = $this->handler;
        $this->handler = $handler;

        return $old;
    }

    /**
     * Sets the format for links to source files.
     *
     * @param string|FileLinkFormatter $fileLinkFormat The format for links to source files
     *
     * @return string The previous file link format
     */
    public function setFileLinkFormat($fileLinkFormat)
    {
        $old = $this->fileLinkFormat;
        $this->fileLinkFormat = $fileLinkFormat;

        return $old;
    }

    /**
     * Sends a response for the given Exception.
     *
     * To be as fail-safe as possible, the exception is first handled
     * by our simple exception handler, then by the user exception handler.
     * The latter takes precedence and any output from the former is cancelled,
     * if and only if nothing bad happens in this handling path.
     */
    public function handle(\Exception $exception)
    {
        if (null === $this->handler || $exception instanceof OutOfMemoryException) {
            $this->sendPhpResponse($exception);

            return;
        }

        $caughtLength = $this->caughtLength = 0;

        ob_start(function ($buffer) {
            $this->caughtBuffer = $buffer;

            return '';
        });

        $this->sendPhpResponse($exception);
        while (null === $this->caughtBuffer && ob_end_flush()) {
            // Empty loop, everything is in the condition
        }
        if (isset($this->caughtBuffer[0])) {
            ob_start(function ($buffer) {
                if ($this->caughtLength) {
                    // use substr_replace() instead of substr() for mbstring overloading resistance
                    $cleanBuffer = substr_replace($buffer, '', 0, $this->caughtLength);
                    if (isset($cleanBuffer[0])) {
                        $buffer = $cleanBuffer;
                    }
                }

                return $buffer;
            });

            echo $this->caughtBuffer;
            $caughtLength = ob_get_length();
        }
        $this->caughtBuffer = null;

        try {
            ($this->handler)($exception);
            $this->caughtLength = $caughtLength;
        } catch (\Exception $e) {
            if (!$caughtLength) {
                // All handlers failed. Let PHP handle that now.
                throw $exception;
            }
        }
    }

    /**
     * Sends the error associated with the given Exception as a plain PHP response.
     *
     * This method uses plain PHP functions like header() and echo to output
     * the response.
     *
     * @param \Exception|FlattenException $exception An \Exception or FlattenException instance
     */
    public function sendPhpResponse($exception)
    {
        if (!$exception instanceof FlattenException) {
            $exception = FlattenException::create($exception);
        }

        if (!headers_sent()) {
            header(sprintf('HTTP/1.0 %s', $exception->getStatusCode()));
            foreach ($exception->getHeaders() as $name => $value) {
                header($name.': '.$value, false);
            }
            header('Content-Type: text/html; charset='.$this->charset);
        }

        echo $this->decorate($this->getContent($exception), $this->getStylesheet($exception));
    }

    /**
     * Gets the full HTML content associated with the given exception.
     *
     * @param \Exception|FlattenException $exception An \Exception or FlattenException instance
     *
     * @return string The HTML content as a string
     */
    public function getHtml($exception)
    {
        if (!$exception instanceof FlattenException) {
            $exception = FlattenException::create($exception);
        }

        return $this->decorate($this->getContent($exception), $this->getStylesheet($exception));
    }

    /**
     * Gets the HTML content associated with the given exception.
     *
     * @return string The content as a string
     */
    public function getContent(FlattenException $exception)
    {
        switch ($exception->getStatusCode()) {
            case 404:
                $title = 'Sorry, the page you are looking for could not be found.';
                break;
            default:
                $title = 'Whoops, looks like something went wrong.';
        }

        if (!$this->debug) {
            return <<<EOF
                <div class="container">
                    <h1>$title</h1>
                </div>
EOF;
        }

        $content = '';
        try {
            $count = \count($exception->getAllPrevious());
            $total = $count + 1;
            foreach ($exception->toArray() as $position => $e) {
                $ind = $count - $position + 1;
                $class = $this->formatClass($e['class']);
                $message = nl2br($this->escapeHtml($e['message']));
                $content .= sprintf(<<<'EOF'
                    <div class="trace trace-as-html">
                        <table class="trace-details">
                            <thead class="trace-head"><tr><th>
                                <h3 class="trace-class">
                                    <span class="text-muted">(%d/%d)</span>
                                    <span class="exception_title">%s</span>
                                </h3>
                                <p class="break-long-words trace-message">%s</p>
                            </th></tr></thead>
                            <tbody>
EOF
                    , $ind, $total, $class, $message);
                foreach ($e['trace'] as $trace) {
                    $content .= '<tr><td>';
                    if ($trace['function']) {
                        $content .= sprintf('at <span class="trace-class">%s</span><span class="trace-type">%s</span><span class="trace-method">%s</span>(<span class="trace-arguments">%s</span>)', $this->formatClass($trace['class']), $trace['type'], $trace['function'], $this->formatArgs($trace['args']));
                    }
                    if (isset($trace['file']) && isset($trace['line'])) {
                        $content .= $this->formatPath($trace['file'], $trace['line']);
                    }
                    $content .= "</td></tr>\n";
                }

                $content .= "</tbody>\n</table>\n</div>\n";
            }
        } catch (\Exception $e) {
            // something nasty happened and we cannot throw an exception anymore
            if ($this->debug) {
                $e = FlattenException::create($e);
                $title = sprintf('Exception thrown when handling an exception (%s: %s)', $e->getClass(), $this->escapeHtml($e->getMessage()));
            } else {
                $title = 'Whoops, looks like something went wrong.';
            }
        }

        $symfonyGhostImageContents = $this->getSymfonyGhostAsSvg();

        return <<<EOF
            <div class="exception-summary">
                <div class="container">
                    <div class="exception-message-wrapper">
                        <h1 class="break-long-words exception-message">$title</h1>
                        <div class="exception-illustration hidden-xs-down">$symfonyGhostImageContents</div>
                    </div>
                </div>
            </div>

            <div class="container">
                $content
            </div>
EOF;
    }

    /**
     * Gets the stylesheet associated with the given exception.
     *
     * @return string The stylesheet as a string
     */
    public function getStylesheet(FlattenException $exception)
    {
        if (!$this->debug) {
            return <<<'EOF'
                body { background-color: #fff; color: #222; font: 16px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; margin: 0; }
                .container { margin: 30px; max-width: 600px; }
                h1 { color: #dc3545; font-size: 24px; }
EOF;
        }

        return <<<'EOF'
            body { background-color: #F9F9F9; color: #222; font: 14px/1.4 Helvetica, Arial, sans-serif; margin: 0; padding-bottom: 45px; }

            a { cursor: pointer; text-decoration: none; }
            a:hover { text-decoration: underline; }
            abbr[title] { border-bottom: none; cursor: help; text-decoration: none; }

            code, pre { font: 13px/1.5 Consolas, Monaco, Menlo, "Ubuntu Mono", "Liberation Mono", monospace; }

            table, tr, th, td { background: #FFF; border-collapse: collapse; vertical-align: top; }
            table { background: #FFF; border: 1px solid #E0E0E0; box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 1em 0; width: 100%; }
            table th, table td { border: solid #E0E0E0; border-width: 1px 0; padding: 8px 10px; }
            table th { background-color: #E0E0E0; font-weight: bold; text-align: left; }

            .hidden-xs-down { display: none; }
            .block { display: block; }
            .break-long-words { -ms-word-break: break-all; word-break: break-all; word-break: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; }
            .text-muted { color: #999; }

            .container { max-width: 1024px; margin: 0 auto; padding: 0 15px; }
            .container::after { content: ""; display: table; clear: both; }

            .exception-summary { background: #B0413E; border-bottom: 2px solid rgba(0, 0, 0, 0.1); border-top: 1px solid rgba(0, 0, 0, .3); flex: 0 0 auto; margin-bottom: 30px; }

            .exception-message-wrapper { display: flex; align-items: center; min-height: 70px; }
            .exception-message { flex-grow: 1; padding: 30px 0; }
            .exception-message, .exception-message a { color: #FFF; font-size: 21px; font-weight: 400; margin: 0; }
            .exception-message.long { font-size: 18px; }
            .exception-message a { border-bottom: 1px solid rgba(255, 255, 255, 0.5); font-size: inherit; text-decoration: none; }
            .exception-message a:hover { border-bottom-color: #ffffff; }

            .exception-illustration { flex-basis: 111px; flex-shrink: 0; height: 66px; margin-left: 15px; opacity: .7; }

            .trace + .trace { margin-top: 30px; }
            .trace-head .trace-class { color: #222; font-size: 18px; font-weight: bold; line-height: 1.3; margin: 0; position: relative; }

            .trace-message { font-size: 14px; font-weight: normal; margin: .5em 0 0; }

            .trace-file-path, .trace-file-path a { color: #222; margin-top: 3px; font-size: 13px; }
            .trace-class { color: #B0413E; }
            .trace-type { padding: 0 2px; }
            .trace-method { color: #B0413E; font-weight: bold; }
            .trace-arguments { color: #777; font-weight: normal; padding-left: 2px; }

            @media (min-width: 575px) {
                .hidden-xs-down { display: initial; }
            }
EOF;
    }

    private function decorate($content, $css)
    {
        return <<<EOF
<!DOCTYPE html>
<html>
    <head>
        <meta charset="{$this->charset}" />
        <meta name="robots" content="noindex,nofollow" />
        <style>$css</style>
    </head>
    <body>
        $content
    </body>
</html>
EOF;
    }

    private function formatClass($class)
    {
        $parts = explode('\\', $class);

        return sprintf('<abbr title="%s">%s</abbr>', $class, array_pop($parts));
    }

    private function formatPath($path, $line)
    {
        $file = $this->escapeHtml(preg_match('#[^/\\\\]*+$#', $path, $file) ? $file[0] : $path);
        $fmt = $this->fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');

        if (!$fmt) {
            return sprintf('<span class="block trace-file-path">in <span title="%s%3$s"><strong>%s</strong>%s</span></span>', $this->escapeHtml($path), $file, 0 < $line ? ' line '.$line : '');
        }

        if (\is_string($fmt)) {
            $i = strpos($f = $fmt, '&', max(strrpos($f, '%f'), strrpos($f, '%l'))) ?: \strlen($f);
            $fmt = [substr($f, 0, $i)] + preg_split('/&([^>]++)>/', substr($f, $i), -1, PREG_SPLIT_DELIM_CAPTURE);

            for ($i = 1; isset($fmt[$i]); ++$i) {
                if (0 === strpos($path, $k = $fmt[$i++])) {
                    $path = substr_replace($path, $fmt[$i], 0, \strlen($k));
                    break;
                }
            }

            $link = strtr($fmt[0], ['%f' => $path, '%l' => $line]);
        } else {
            try {
                $link = $fmt->format($path, $line);
            } catch (\Exception $e) {
                return sprintf('<span class="block trace-file-path">in <span title="%s%3$s"><strong>%s</strong>%s</span></span>', $this->escapeHtml($path), $file, 0 < $line ? ' line '.$line : '');
            }
        }

        return sprintf('<span class="block trace-file-path">in <a href="%s" title="Go to source"><strong>%s</string>%s</a></span>', $this->escapeHtml($link), $file, 0 < $line ? ' line '.$line : '');
    }

    /**
     * Formats an array as a string.
     *
     * @param array $args The argument array
     *
     * @return string
     */
    private function formatArgs(array $args)
    {
        $result = [];
        foreach ($args as $key => $item) {
            if ('object' === $item[0]) {
                $formattedValue = sprintf('<em>object</em>(%s)', $this->formatClass($item[1]));
            } elseif ('array' === $item[0]) {
                $formattedValue = sprintf('<em>array</em>(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]);
            } elseif ('null' === $item[0]) {
                $formattedValue = '<em>null</em>';
            } elseif ('boolean' === $item[0]) {
                $formattedValue = '<em>'.strtolower(var_export($item[1], true)).'</em>';
            } elseif ('resource' === $item[0]) {
                $formattedValue = '<em>resource</em>';
            } else {
                $formattedValue = str_replace("\n", '', $this->escapeHtml(var_export($item[1], true)));
            }

            $result[] = \is_int($key) ? $formattedValue : sprintf("'%s' => %s", $this->escapeHtml($key), $formattedValue);
        }

        return implode(', ', $result);
    }

    /**
     * HTML-encodes a string.
     */
    private function escapeHtml($str)
    {
        return htmlspecialchars($str, ENT_COMPAT | ENT_SUBSTITUTE, $this->charset);
    }

    private function getSymfonyGhostAsSvg()
    {
        return '<svg viewBox="0 0 136 81" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.4"><path d="M92.4 20.4a23.2 23.2 0 0 1 9 1.9 23.7 23.7 0 0 1 5.2 3 24.3 24.3 0 0 1 3.4 3.4 24.8 24.8 0 0 1 5 9.4c.5 1.7.8 3.4 1 5.2v14.5h.4l.5.2a7.4 7.4 0 0 0 2.5.2l.2-.2.6-.8.8-1.3-.2-.1a5.5 5.5 0 0 1-.8-.3 5.6 5.6 0 0 1-2.3-1.8 5.7 5.7 0 0 1-.9-1.6 6.5 6.5 0 0 1-.2-2.8 7.3 7.3 0 0 1 .5-2l.3-.3.8-.9.3-.3c.2-.2.5-.3.8-.3H120.7c.2 0 .3-.1.4 0h.4l.2.1.3.2.2-.4.3-.4.1-.1 1.2-1 .3-.2.4-.1.4-.1h.3l1.5.1.4.1.8.5.1.2 1 1.1v.2H129.4l.4-.2 1.4-.5h1.1c.3 0 .7.2 1 .4.2 0 .3.2.5.3l.2.2.5.3.4.6.1.3.4 1.4.1.4v.6a7.8 7.8 0 0 1-.1.6 9.9 9.9 0 0 1-.8 2.4 7.8 7.8 0 0 1-3 3.3 6.4 6.4 0 0 1-1 .5 6.1 6.1 0 0 1-.6.2l-.7.1h-.1a23.4 23.4 0 0 1-.2 1.7 14.3 14.3 0 0 1-.6 2.1l-.8 2a9.2 9.2 0 0 1-.4.6l-.7 1a9.1 9.1 0 0 1-2.3 2.2c-.9.5-2 .6-3 .7l-1.4.1h-.5l-.4.1a15.8 15.8 0 0 1-2.8-.1v4.2a9.7 9.7 0 0 1-.7 3.5 9.6 9.6 0 0 1-1.7 2.8 9.3 9.3 0 0 1-3 2.3 9 9 0 0 1-5.4.7 9 9 0 0 1-3-1 9.4 9.4 0 0 1-2.7-2.5 10 10 0 0 1-1 1.2 9.3 9.3 0 0 1-2 1.3 9 9 0 0 1-2.4 1 9 9 0 0 1-6.5-1.1A9.4 9.4 0 0 1 85 77V77a10.9 10.9 0 0 1-.6.6 9.3 9.3 0 0 1-2.7 2 9 9 0 0 1-6 .8 9 9 0 0 1-2.4-1 9.3 9.3 0 0 1-2.3-1.7 9.6 9.6 0 0 1-1.8-2.8 9.7 9.7 0 0 1-.8-3.7v-4a18.5 18.5 0 0 1-2.9.2l-1.2-.1c-1.9-.3-3.7-1-5.1-2.1A8.2 8.2 0 0 1 58 64a10.2 10.2 0 0 1-.9-1.2 15.3 15.3 0 0 1-.7-1.3 20.8 20.8 0 0 1-1.9-6.2v-.2a6.5 6.5 0 0 1-1-.3 6.1 6.1 0 0 1-.6-.3 6.6 6.6 0 0 1-.9-.5 8.2 8.2 0 0 1-2.7-3.8 10 10 0 0 1-.3-1 10.3 10.3 0 0 1-.3-1.9V47v-.4l.1-.4.6-1.4.1-.2a2 2 0 0 1 .8-.8l.3-.2.3-.2a3.2 3.2 0 0 1 1.8-.5h.4l.3.2 1.4.6.2.2.4.3.3.4.7-.7.2-.2.4-.2.6-.2h2.1l.4.2.4.2.3.2.8 1 .2-.1h.1v-.1H63l1.1.1h.3l.8.5.3.4.7 1 .2.3.1.5a11 11 0 0 1 .2 1.5c0 .8 0 1.6-.3 2.3a6 6 0 0 1-.5 1.2 5.5 5.5 0 0 1-3.3 2.5 12.3 12.3 0 0 0 1.4 3h.1l.2.1 1 .2h1.5l.5-.2H67.8l.5-.2h.1V44v-.4a26.7 26.7 0 0 1 .3-2.3 24.7 24.7 0 0 1 5.7-12.5 24.2 24.2 0 0 1 3.5-3.3 23.7 23.7 0 0 1 4.9-3 23.2 23.2 0 0 1 5.6-1.7 23.7 23.7 0 0 1 4-.3zm-.3 2a21.2 21.2 0 0 0-8 1.7 21.6 21.6 0 0 0-4.8 2.7 22.2 22.2 0 0 0-3.2 3 22.7 22.7 0 0 0-5 9.2 23.4 23.4 0 0 0-.7 4.9v15.7l-.5.1a34.3 34.3 0 0 1-1.5.3h-.2l-.4.1h-.4l-.9.2a10 10 0 0 1-1.9 0c-.5 0-1-.2-1.5-.4a1.8 1.8 0 0 1-.3-.2 2 2 0 0 1-.3-.3 5.2 5.2 0 0 1-.1-.2 9 9 0 0 1-.6-.9 13.8 13.8 0 0 1-1-2 14.3 14.3 0 0 1-.6-2 14 14 0 0 1-.1-.8v-.2h.3a12.8 12.8 0 0 0 1.4-.2 4.4 4.4 0 0 0 .3 0 3.6 3.6 0 0 0 1.1-.7 3.4 3.4 0 0 0 1.2-1.7l.2-1.2a5.1 5.1 0 0 0 0-.8 7.2 7.2 0 0 0-.1-.8l-.7-1-1.2-.2-1 .7-.1 1.3a5 5 0 0 1 .1.4v.6a1 1 0 0 1 0 .3c-.1.3-.4.4-.7.5l-1.2.4v-.7A9.9 9.9 0 0 1 60 49l.3-.6v-.2l.1-.1v-1.6l-1-1.2h-1.5l-1 1.1v.4a5.3 5.3 0 0 0-.2.6 5.5 5.5 0 0 0 0 .5c0 .7 0 1.4.3 2 0 .4.2.8.4 1.2L57 51a9.5 9.5 0 0 1-1.1-.5h-.2a2 2 0 0 1-.4-.3c-.4-.4-.5-1-.6-1.6a5.6 5.6 0 0 1 0-.5v-.5-.5l-.6-1.5-1.4-.6-.9.3s-.2 0-.3.2a2 2 0 0 1-.1 0l-.6 1.4v.7a8.5 8.5 0 0 0 .5 2c.4 1.1 1 2.1 2 2.8a4.7 4.7 0 0 0 2.1.9h1a22.8 22.8 0 0 0 .1 1 18.1 18.1 0 0 0 .8 3.8 18.2 18.2 0 0 0 1.6 3.7l1 1.3c1 1 2.3 1.6 3.7 2a11.7 11.7 0 0 0 4.8 0h.4l.5-.2.5-.1.6-.2v6.6a8 8 0 0 0 .1 1.3 7.5 7.5 0 0 0 2.4 4.3 7.2 7.2 0 0 0 2.3 1.3 7 7 0 0 0 7-1.1 7.5 7.5 0 0 0 2-2.6A7.7 7.7 0 0 0 85 72V71a8.2 8.2 0 0 0 .2 1.3c0 .7.3 1.4.6 2a7.5 7.5 0 0 0 1.7 2.3 7.3 7.3 0 0 0 2.2 1.4 7.1 7.1 0 0 0 4.6.2 7.2 7.2 0 0 0 2.4-1.2 7.5 7.5 0 0 0 2.1-2.7 7.8 7.8 0 0 0 .7-2.4V71a9.3 9.3 0 0 0 .1.6 7.6 7.6 0 0 0 .6 2.5 7.5 7.5 0 0 0 2.4 3 7.1 7.1 0 0 0 7 .8 7.3 7.3 0 0 0 2.3-1.5 7.5 7.5 0 0 0 1.6-2.3 7.6 7.6 0 0 0 .5-2l.1-1.1v-6.7l.4.1a12.2 12.2 0 0 0 2 .5 11.1 11.1 0 0 0 2.5 0h.8l1.2-.1a9.5 9.5 0 0 0 1.4-.2l.9-.3a3.5 3.5 0 0 0 .6-.4l1.2-1.4a12.2 12.2 0 0 0 .8-1.2c0-.3.2-.5.3-.7a15.9 15.9 0 0 0 .7-2l.3-1.6v-1.3l.2-.9V54.6a15.5 15.5 0 0 0 1.8 0 4.5 4.5 0 0 0 1.4-.5 5.7 5.7 0 0 0 2.5-3.2 7.6 7.6 0 0 0 .4-1.5v-.3l-.4-1.4a5.2 5.2 0 0 1-.2-.1l-.4-.4a3.8 3.8 0 0 0-.2 0 1.4 1.4 0 0 0-.5-.2l-1.4.4-.7 1.3v.7a5.7 5.7 0 0 1-.1.8l-.7 1.4a1.9 1.9 0 0 1-.5.3h-.3a9.6 9.6 0 0 1-.8.3 8.8 8.8 0 0 1-.6 0l.2-.4.2-.5.2-.3v-.4l.1-.2V50l.1-1 .1-.6v-.6a4.8 4.8 0 0 0 0-.8v-.2l-1-1.1-1.5-.2-1.1 1-.2 1.4v.1l.2.4.2.3v.4l.1 1.1v.3l.1.5v.8a9.6 9.6 0 0 1-.8-.3l-.2-.1h-.3l-.8-.1h-.2a1.6 1.6 0 0 1-.2-.2.9.9 0 0 1-.2-.2 1 1 0 0 1-.1-.5l.2-.9v-1.2l-.9-.8h-1.2l-.8.9v.3a4.8 4.8 0 0 0-.3 2l.3.9a3.5 3.5 0 0 0 1.2 1.6l1 .5.8.2 1.4.1h.4l.2.1a12.1 12.1 0 0 1-1 2.6 13.2 13.2 0 0 1-.8 1.5 9.5 9.5 0 0 1-1 1.2l-.2.3a1.7 1.7 0 0 1-.4.3 2.4 2.4 0 0 1-.7.2h-2.5a7.8 7.8 0 0 1-.6-.2l-.7-.2h-.2a14.8 14.8 0 0 1-.6-.2 23.4 23.4 0 0 1-.4-.1l-.4-.1-.3-.1V43.9a34.6 34.6 0 0 0 0-.6 23.6 23.6 0 0 0-.4-3 22.7 22.7 0 0 0-1.5-4.7 22.6 22.6 0 0 0-4.6-6.7 21.9 21.9 0 0 0-6.9-4.7 21.2 21.2 0 0 0-8.1-1.8H92zm9.1 33.7l.3.1a1 1 0 0 1 .6.8v.4a8.4 8.4 0 0 1 0 .5 8.8 8.8 0 0 1-1.6 4.2l-1 1.3A10 10 0 0 1 95 66c-1.3.3-2.7.4-4 .3a10.4 10.4 0 0 1-2.7-.8 10 10 0 0 1-3.6-2.5 9.3 9.3 0 0 1-.8-1 9 9 0 0 1-.7-1.2 8.6 8.6 0 0 1-.8-3.4V57a1 1 0 0 1 .3-.6 1 1 0 0 1 1.3-.2 1 1 0 0 1 .4.8v.4a6.5 6.5 0 0 0 .5 2.2 7 7 0 0 0 2.1 2.8l1 .6c2.6 1.6 6 1.6 8.5 0a8 8 0 0 0 1.1-.6 7.6 7.6 0 0 0 1.2-1.2 7 7 0 0 0 1-1.7 6.5 6.5 0 0 0 .4-2.5 1 1 0 0 1 .7-1h.4zM30.7 43.7c-15.5 1-28.5-6-30.1-16.4C-1.2 15.7 11.6 4 29 1.3 46.6-1.7 62.3 5.5 64 17.1c1.6 10.4-8.7 21-23.7 25a31.2 31.2 0 0 0 0 .9v.3a19 19 0 0 0 .1 1l.1.4.1.9a4.7 4.7 0 0 0 .5 1l.7 1a9.2 9.2 0 0 0 1.2 1l1.5.8.6.8-.7.6-1.1.3a11.2 11.2 0 0 1-2.6.4 8.6 8.6 0 0 1-3-.5 8.5 8.5 0 0 1-1-.4 11.2 11.2 0 0 1-1.8-1.2 13.3 13.3 0 0 1-1-1 18 18 0 0 1-.7-.6l-.4-.4a23.4 23.4 0 0 1-1.3-1.8l-.1-.1-.3-.5V45l-.3-.6v-.7zM83.1 36c3.6 0 6.5 3.2 6.5 7.1 0 4-3 7.2-6.5 7.2S76.7 47 76.7 43 79.6 36 83 36zm18 0c3.6 0 6.5 3.2 6.5 7.1 0 4-2.9 7.2-6.4 7.2S94.7 47 94.7 43s3-7.1 6.5-7.1zm-18 6.1c2 0 3.5 1.6 3.5 3.6S85 49.2 83 49.2s-3.4-1.6-3.4-3.6S81.2 42 83 42zm17.9 0c1.9 0 3.4 1.6 3.4 3.6s-1.5 3.6-3.4 3.6c-2 0-3.5-1.6-3.5-3.6S99.1 42 101 42zM17 28c-.3 1.6-1.8 5-5.2 5.8-2.5.6-4.1-.8-4.5-2.6-.4-1.9.7-3.5 2.1-4.5A3.5 3.5 0 0 1 8 24.6c-.4-2 .8-3.7 3.2-4.2 1.9-.5 3.1.2 3.4 1.5.3 1.1-.5 2.2-1.8 2.5-.9.3-1.6 0-1.7-.6a1.4 1.4 0 0 1 0-.7s.3.2 1 0c.7-.1 1-.7.9-1.2-.2-.6-1-.8-1.8-.6-1 .2-2 1-1.7 2.6.3 1 .9 1.6 1.5 1.8l.7-.2c1-.2 1.5 0 1.6.5 0 .4-.2 1-1.2 1.2a3.3 3.3 0 0 1-1.5 0c-.9.7-1.6 1.9-1.3 3.2.3 1.3 1.3 2.2 3 1.8 2.5-.7 3.8-3.7 4.2-5-.3-.5-.6-1-.7-1.6-.1-.5.1-1 .9-1.2.4 0 .7.2.8.8a2.8 2.8 0 0 1 0 1l.7 1c.6-2 1.4-4 1.7-4 .6-.2 1.5.6 1.5.6-.8.7-1.7 2.4-2.3 4.2.8.6 1.6 1 2.1 1 .5-.1.8-.6 1-1.2-.3-2.2 1-4.3 2.3-4.6.7-.2 1.3.2 1.4.8.1.5 0 1.3-.9 1.7-.2-1-.6-1.3-1-1.3-.4.1-.7 1.4-.4 2.8.2 1 .7 1.5 1.3 1.4.8-.2 1.3-1.2 1.7-2.1-.3-2.1.9-4.2 2.2-4.5.7-.2 1.2.1 1.4 1 .4 1.4-1 2.8-2.2 3.4.3.7.7 1 1.3.9 1-.3 1.6-1.5 2-2.5l-.5-3v-.3s1.6-.3 1.8.6v.1c.2-.6.7-1.2 1.3-1.4.8-.1 1.5.6 1.7 1.6.5 2.2-.5 4.4-1.8 4.7H33a31.9 31.9 0 0 0 1 5.2c-.4.1-1.8.4-2-.4l-.5-5.6c-.5 1-1.3 2.2-2.5 2.4-1 .3-1.6-.3-2-1.1-.5 1-1.3 2.1-2.4 2.4-.8.2-1.5-.1-2-1-.3.8-.9 1.5-1.5 1.7-.7.1-1.5-.3-2.4-1-.3.8-.4 1.6-.4 2.2 0 0-.7 0-.8-.4-.1-.5 0-1.5.3-2.7a10.3 10.3 0 0 1-.7-.8zm38.2-17.8l.2.9c.5 1.9.4 4.4.8 6.4 0 .6-.4 3-1.4 3.3-.2 0-.3 0-.4-.4-.1-.7 0-1.6-.3-2.6-.2-1.1-.8-1.6-1.5-1.5-.8.2-1.3 1-1.6 2l-.1-.5c-.2-1-1.8-.6-1.8-.6a6.2 6.2 0 0 1 .4 1.3l.2 1c-.2.5-.6 1-1.2 1l-.2.1a7 7 0 0 0-.1-.8c-.3-1.1-1-2-1.6-1.8a.7.7 0 0 0-.4.3c-1.3.3-2.4 2-2.1 3.9-.2.9-.6 1.7-1 1.9-.5 0-.8-.5-1.1-1.8l-.1-1.2a4 4 0 0 0 0-1.7c0-.4-.4-.7-.8-.6-.7.2-.9 1.7-.5 3.8-.2 1-.6 2-1.3 2-.4.2-.8-.2-1-1l-.2-3c1.2-.5 2-1 1.8-1.7-.1-.5-.8-.7-.8-.7s0 .7-1 1.2l-.2-1.4c-.1-.6-.4-1-1.7-.6l.4 1 .2 1.5h-1v.8c0 .3.4.3 1 .2 0 1.3 0 2.7.2 3.6.3 1.4 1.2 2 2 1.7 1-.2 1.6-1.3 2-2.3.3 1.2 1 2 1.9 1.7.7-.2 1.2-1.1 1.6-2.2.4.8 1.1 1.1 2 1 1.2-.4 1.7-1.6 1.8-2.8h.2c.6-.2 1-.6 1.3-1 0 .8 0 1.5.2 2.1.1.5.3.7.6.6.5-.1 1-.9 1-.9a4 4 0 0 1-.3-1c-.3-1.3.3-3.6 1-3.7.2 0 .3.2.5.7v.8l.2 1.5v.7c.2.7.7 1.3 1.5 1 1.3-.2 2-2.6 2.1-3.9.3.2.6.2 1 .1-.6-2.2 0-6.1-.3-7.9-.1-.4-1-.5-1.7-.5h-.4zm-21.5 12c.4 0 .7.3 1 1.1.2 1.3-.3 2.6-.9 2.8-.2 0-.7 0-1-1.2v-.4c0-1.3.4-2 1-2.2zm-5.2 1c.3 0 .6.2.6.5.2.6-.3 1.3-1.2 2-.3-1.4.1-2.3.6-2.5zm18-.4c-.5.2-1-.4-1.2-1.2-.2-1 0-2.1.7-2.5v.5c.2.7.6 1.5 1.3 1.9 0 .7-.2 1.2-.7 1.3zm10-1.6c0 .5.4.7 1 .6.8-.2 1-1 .8-1.6 0-.5-.4-1-1-.8-.5.1-1 .9-.8 1.8zm-14.3-5.5c0-.4-.5-.7-1-.5-.8.2-1 1-.9 1.5.2.6.5 1 1 .8.5 0 1.1-1 1-1.8z" fill="#fff" fill-opacity=".6"/></svg>';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug;

use PHPUnit\Framework\MockObject\Matcher\StatelessInvocation;

/**
 * Autoloader checking if the class is really defined in the file found.
 *
 * The ClassLoader will wrap all registered autoloaders
 * and will throw an exception if a file is found but does
 * not declare the class.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Christophe Coevoet <stof@notk.org>
 * @author Nicolas Grekas <p@tchwork.com>
 * @author Guilhem Niot <guilhem.niot@gmail.com>
 */
class DebugClassLoader
{
    private $classLoader;
    private $isFinder;
    private $loaded = [];
    private static $caseCheck;
    private static $checkedClasses = [];
    private static $final = [];
    private static $finalMethods = [];
    private static $deprecated = [];
    private static $internal = [];
    private static $internalMethods = [];
    private static $annotatedParameters = [];
    private static $darwinCache = ['/' => ['/', []]];

    public function __construct(callable $classLoader)
    {
        $this->classLoader = $classLoader;
        $this->isFinder = \is_array($classLoader) && method_exists($classLoader[0], 'findFile');

        if (!isset(self::$caseCheck)) {
            $file = file_exists(__FILE__) ? __FILE__ : rtrim(realpath('.'), \DIRECTORY_SEPARATOR);
            $i = strrpos($file, \DIRECTORY_SEPARATOR);
            $dir = substr($file, 0, 1 + $i);
            $file = substr($file, 1 + $i);
            $test = strtoupper($file) === $file ? strtolower($file) : strtoupper($file);
            $test = realpath($dir.$test);

            if (false === $test || false === $i) {
                // filesystem is case sensitive
                self::$caseCheck = 0;
            } elseif (substr($test, -\strlen($file)) === $file) {
                // filesystem is case insensitive and realpath() normalizes the case of characters
                self::$caseCheck = 1;
            } elseif (false !== stripos(PHP_OS, 'darwin')) {
                // on MacOSX, HFS+ is case insensitive but realpath() doesn't normalize the case of characters
                self::$caseCheck = 2;
            } else {
                // filesystem case checks failed, fallback to disabling them
                self::$caseCheck = 0;
            }
        }
    }

    /**
     * Gets the wrapped class loader.
     *
     * @return callable The wrapped class loader
     */
    public function getClassLoader()
    {
        return $this->classLoader;
    }

    /**
     * Wraps all autoloaders.
     */
    public static function enable()
    {
        // Ensures we don't hit https://bugs.php.net/42098
        class_exists('Symfony\Component\Debug\ErrorHandler');
        class_exists('Psr\Log\LogLevel');

        if (!\is_array($functions = spl_autoload_functions())) {
            return;
        }

        foreach ($functions as $function) {
            spl_autoload_unregister($function);
        }

        foreach ($functions as $function) {
            if (!\is_array($function) || !$function[0] instanceof self) {
                $function = [new static($function), 'loadClass'];
            }

            spl_autoload_register($function);
        }
    }

    /**
     * Disables the wrapping.
     */
    public static function disable()
    {
        if (!\is_array($functions = spl_autoload_functions())) {
            return;
        }

        foreach ($functions as $function) {
            spl_autoload_unregister($function);
        }

        foreach ($functions as $function) {
            if (\is_array($function) && $function[0] instanceof self) {
                $function = $function[0]->getClassLoader();
            }

            spl_autoload_register($function);
        }
    }

    /**
     * @return string|null
     */
    public function findFile($class)
    {
        return $this->isFinder ? $this->classLoader[0]->findFile($class) ?: null : null;
    }

    /**
     * Loads the given class or interface.
     *
     * @param string $class The name of the class
     *
     * @throws \RuntimeException
     */
    public function loadClass($class)
    {
        $e = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR);

        try {
            if ($this->isFinder && !isset($this->loaded[$class])) {
                $this->loaded[$class] = true;
                if (!$file = $this->classLoader[0]->findFile($class) ?: false) {
                    // no-op
                } elseif (\function_exists('opcache_is_script_cached') && @opcache_is_script_cached($file)) {
                    require $file;

                    return;
                } else {
                    require $file;
                }
            } else {
                ($this->classLoader)($class);
                $file = false;
            }
        } finally {
            error_reporting($e);
        }

        $this->checkClass($class, $file);
    }

    private function checkClass($class, $file = null)
    {
        $exists = null === $file || \class_exists($class, false) || \interface_exists($class, false) || \trait_exists($class, false);

        if (null !== $file && $class && '\\' === $class[0]) {
            $class = substr($class, 1);
        }

        if ($exists) {
            if (isset(self::$checkedClasses[$class])) {
                return;
            }
            self::$checkedClasses[$class] = true;

            $refl = new \ReflectionClass($class);
            if (null === $file && $refl->isInternal()) {
                return;
            }
            $name = $refl->getName();

            if ($name !== $class && 0 === \strcasecmp($name, $class)) {
                throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: "%s" vs "%s".', $class, $name));
            }

            $deprecations = $this->checkAnnotations($refl, $name);

            foreach ($deprecations as $message) {
                @trigger_error($message, E_USER_DEPRECATED);
            }
        }

        if (!$file) {
            return;
        }

        if (!$exists) {
            if (false !== strpos($class, '/')) {
                throw new \RuntimeException(sprintf('Trying to autoload a class with an invalid name "%s". Be careful that the namespace separator is "\" in PHP, not "/".', $class));
            }

            throw new \RuntimeException(sprintf('The autoloader expected class "%s" to be defined in file "%s". The file was found but the class was not in it, the class name or namespace probably has a typo.', $class, $file));
        }

        if (self::$caseCheck && $message = $this->checkCase($refl, $file, $class)) {
            throw new \RuntimeException(sprintf('Case mismatch between class and real file names: "%s" vs "%s" in "%s".', $message[0], $message[1], $message[2]));
        }
    }

    public function checkAnnotations(\ReflectionClass $refl, $class)
    {
        $deprecations = [];

        // Don't trigger deprecations for classes in the same vendor
        if (2 > $len = 1 + (\strpos($class, '\\') ?: \strpos($class, '_'))) {
            $len = 0;
            $ns = '';
        } else {
            $ns = \str_replace('_', '\\', \substr($class, 0, $len));
        }

        // Detect annotations on the class
        if (false !== $doc = $refl->getDocComment()) {
            foreach (['final', 'deprecated', 'internal'] as $annotation) {
                if (false !== \strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$|\r?\n)#s', $doc, $notice)) {
                    self::${$annotation}[$class] = isset($notice[1]) ? preg_replace('#\.?\r?\n( \*)? *(?= |\r?\n|$)#', '', $notice[1]) : '';
                }
            }
        }

        $parent = \get_parent_class($class);
        $parentAndOwnInterfaces = $this->getOwnInterfaces($class, $parent);
        if ($parent) {
            $parentAndOwnInterfaces[$parent] = $parent;

            if (!isset(self::$checkedClasses[$parent])) {
                $this->checkClass($parent);
            }

            if (isset(self::$final[$parent])) {
                $deprecations[] = sprintf('The "%s" class is considered final%s. It may change without further notice as of its next major version. You should not extend it from "%s".', $parent, self::$final[$parent], $class);
            }
        }

        // Detect if the parent is annotated
        foreach ($parentAndOwnInterfaces + \class_uses($class, false) as $use) {
            if (!isset(self::$checkedClasses[$use])) {
                $this->checkClass($use);
            }
            if (isset(self::$deprecated[$use]) && \strncmp($ns, \str_replace('_', '\\', $use), $len) && !isset(self::$deprecated[$class])) {
                $type = class_exists($class, false) ? 'class' : (interface_exists($class, false) ? 'interface' : 'trait');
                $verb = class_exists($use, false) || interface_exists($class, false) ? 'extends' : (interface_exists($use, false) ? 'implements' : 'uses');

                $deprecations[] = sprintf('The "%s" %s %s "%s" that is deprecated%s.', $class, $type, $verb, $use, self::$deprecated[$use]);
            }
            if (isset(self::$internal[$use]) && \strncmp($ns, \str_replace('_', '\\', $use), $len)) {
                $deprecations[] = sprintf('The "%s" %s is considered internal%s. It may change without further notice. You should not use it from "%s".', $use, class_exists($use, false) ? 'class' : (interface_exists($use, false) ? 'interface' : 'trait'), self::$internal[$use], $class);
            }
        }

        if (\trait_exists($class)) {
            return $deprecations;
        }

        // Inherit @final, @internal and @param annotations for methods
        self::$finalMethods[$class] = [];
        self::$internalMethods[$class] = [];
        self::$annotatedParameters[$class] = [];
        foreach ($parentAndOwnInterfaces as $use) {
            foreach (['finalMethods', 'internalMethods', 'annotatedParameters'] as $property) {
                if (isset(self::${$property}[$use])) {
                    self::${$property}[$class] = self::${$property}[$class] ? self::${$property}[$use] + self::${$property}[$class] : self::${$property}[$use];
                }
            }
        }

        foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) {
            if ($method->class !== $class) {
                continue;
            }

            if ($parent && isset(self::$finalMethods[$parent][$method->name])) {
                list($declaringClass, $message) = self::$finalMethods[$parent][$method->name];
                $deprecations[] = sprintf('The "%s::%s()" method is considered final%s. It may change without further notice as of its next major version. You should not extend it from "%s".', $declaringClass, $method->name, $message, $class);
            }

            if (isset(self::$internalMethods[$class][$method->name])) {
                list($declaringClass, $message) = self::$internalMethods[$class][$method->name];
                if (\strncmp($ns, $declaringClass, $len)) {
                    $deprecations[] = sprintf('The "%s::%s()" method is considered internal%s. It may change without further notice. You should not extend it from "%s".', $declaringClass, $method->name, $message, $class);
                }
            }

            // To read method annotations
            $doc = $method->getDocComment();

            if (isset(self::$annotatedParameters[$class][$method->name])) {
                $definedParameters = [];
                foreach ($method->getParameters() as $parameter) {
                    $definedParameters[$parameter->name] = true;
                }

                foreach (self::$annotatedParameters[$class][$method->name] as $parameterName => $deprecation) {
                    if (!isset($definedParameters[$parameterName]) && !($doc && preg_match("/\\n\\s+\\* @param (.*?)(?<= )\\\${$parameterName}\\b/", $doc))) {
                        $deprecations[] = sprintf($deprecation, $class);
                    }
                }
            }

            if (!$doc) {
                continue;
            }

            $finalOrInternal = false;

            foreach (['final', 'internal'] as $annotation) {
                if (false !== \strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$|\r?\n)#s', $doc, $notice)) {
                    $message = isset($notice[1]) ? preg_replace('#\.?\r?\n( \*)? *(?= |\r?\n|$)#', '', $notice[1]) : '';
                    self::${$annotation.'Methods'}[$class][$method->name] = [$class, $message];
                    $finalOrInternal = true;
                }
            }

            if ($finalOrInternal || $method->isConstructor() || false === \strpos($doc, '@param') || StatelessInvocation::class === $class) {
                continue;
            }
            if (!preg_match_all('#\n\s+\* @param (.*?)(?<= )\$([a-zA-Z0-9_\x7f-\xff]++)#', $doc, $matches, PREG_SET_ORDER)) {
                continue;
            }
            if (!isset(self::$annotatedParameters[$class][$method->name])) {
                $definedParameters = [];
                foreach ($method->getParameters() as $parameter) {
                    $definedParameters[$parameter->name] = true;
                }
            }
            foreach ($matches as list(, $parameterType, $parameterName)) {
                if (!isset($definedParameters[$parameterName])) {
                    $parameterType = trim($parameterType);
                    self::$annotatedParameters[$class][$method->name][$parameterName] = sprintf('The "%%s::%s()" method will require a new "%s$%s" argument in the next major version of its parent class "%s", not defining it is deprecated.', $method->name, $parameterType ? $parameterType.' ' : '', $parameterName, $method->class);
                }
            }
        }

        return $deprecations;
    }

    public function checkCase(\ReflectionClass $refl, $file, $class)
    {
        $real = explode('\\', $class.strrchr($file, '.'));
        $tail = explode(\DIRECTORY_SEPARATOR, str_replace('/', \DIRECTORY_SEPARATOR, $file));

        $i = \count($tail) - 1;
        $j = \count($real) - 1;

        while (isset($tail[$i], $real[$j]) && $tail[$i] === $real[$j]) {
            --$i;
            --$j;
        }

        array_splice($tail, 0, $i + 1);

        if (!$tail) {
            return;
        }

        $tail = \DIRECTORY_SEPARATOR.implode(\DIRECTORY_SEPARATOR, $tail);
        $tailLen = \strlen($tail);
        $real = $refl->getFileName();

        if (2 === self::$caseCheck) {
            $real = $this->darwinRealpath($real);
        }

        if (0 === substr_compare($real, $tail, -$tailLen, $tailLen, true)
            && 0 !== substr_compare($real, $tail, -$tailLen, $tailLen, false)
        ) {
            return [substr($tail, -$tailLen + 1), substr($real, -$tailLen + 1), substr($real, 0, -$tailLen + 1)];
        }
    }

    /**
     * `realpath` on MacOSX doesn't normalize the case of characters.
     */
    private function darwinRealpath($real)
    {
        $i = 1 + strrpos($real, '/');
        $file = substr($real, $i);
        $real = substr($real, 0, $i);

        if (isset(self::$darwinCache[$real])) {
            $kDir = $real;
        } else {
            $kDir = strtolower($real);

            if (isset(self::$darwinCache[$kDir])) {
                $real = self::$darwinCache[$kDir][0];
            } else {
                $dir = getcwd();
                chdir($real);
                $real = getcwd().'/';
                chdir($dir);

                $dir = $real;
                $k = $kDir;
                $i = \strlen($dir) - 1;
                while (!isset(self::$darwinCache[$k])) {
                    self::$darwinCache[$k] = [$dir, []];
                    self::$darwinCache[$dir] = &self::$darwinCache[$k];

                    while ('/' !== $dir[--$i]) {
                    }
                    $k = substr($k, 0, ++$i);
                    $dir = substr($dir, 0, $i--);
                }
            }
        }

        $dirFiles = self::$darwinCache[$kDir][1];

        if (!isset($dirFiles[$file]) && ') : eval()\'d code' === substr($file, -17)) {
            // Get the file name from "file_name.php(123) : eval()'d code"
            $file = substr($file, 0, strrpos($file, '(', -17));
        }

        if (isset($dirFiles[$file])) {
            return $real .= $dirFiles[$file];
        }

        $kFile = strtolower($file);

        if (!isset($dirFiles[$kFile])) {
            foreach (scandir($real, 2) as $f) {
                if ('.' !== $f[0]) {
                    $dirFiles[$f] = $f;
                    if ($f === $file) {
                        $kFile = $k = $file;
                    } elseif ($f !== $k = strtolower($f)) {
                        $dirFiles[$k] = $f;
                    }
                }
            }
            self::$darwinCache[$kDir][1] = $dirFiles;
        }

        return $real .= $dirFiles[$kFile];
    }

    /**
     * `class_implements` includes interfaces from the parents so we have to manually exclude them.
     *
     * @param string       $class
     * @param string|false $parent
     *
     * @return string[]
     */
    private function getOwnInterfaces($class, $parent)
    {
        $ownInterfaces = class_implements($class, false);

        if ($parent) {
            foreach (class_implements($parent, false) as $interface) {
                unset($ownInterfaces[$interface]);
            }
        }

        foreach ($ownInterfaces as $interface) {
            foreach (class_implements($interface) as $interface) {
                unset($ownInterfaces[$interface]);
            }
        }

        return $ownInterfaces;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug;

use Psr\Log\AbstractLogger;

/**
 * A buffering logger that stacks logs for later.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class BufferingLogger extends AbstractLogger
{
    private $logs = [];

    public function log($level, $message, array $context = [])
    {
        $this->logs[] = [$level, $message, $context];
    }

    public function cleanLogs()
    {
        $logs = $this->logs;
        $this->logs = [];

        return $logs;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug;

use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\Exception\FatalThrowableError;
use Symfony\Component\Debug\Exception\FlattenException;
use Symfony\Component\Debug\Exception\OutOfMemoryException;
use Symfony\Component\Debug\Exception\SilencedErrorContext;
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;

/**
 * A generic ErrorHandler for the PHP engine.
 *
 * Provides five bit fields that control how errors are handled:
 * - thrownErrors: errors thrown as \ErrorException
 * - loggedErrors: logged errors, when not @-silenced
 * - scopedErrors: errors thrown or logged with their local context
 * - tracedErrors: errors logged with their stack trace
 * - screamedErrors: never @-silenced errors
 *
 * Each error level can be logged by a dedicated PSR-3 logger object.
 * Screaming only applies to logging.
 * Throwing takes precedence over logging.
 * Uncaught exceptions are logged as E_ERROR.
 * E_DEPRECATED and E_USER_DEPRECATED levels never throw.
 * E_RECOVERABLE_ERROR and E_USER_ERROR levels always throw.
 * Non catchable errors that can be detected at shutdown time are logged when the scream bit field allows so.
 * As errors have a performance cost, repeated errors are all logged, so that the developer
 * can see them and weight them as more important to fix than others of the same level.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 * @author GrÃ©goire Pineau <lyrixx@lyrixx.info>
 */
class ErrorHandler
{
    private $levels = [
        E_DEPRECATED => 'Deprecated',
        E_USER_DEPRECATED => 'User Deprecated',
        E_NOTICE => 'Notice',
        E_USER_NOTICE => 'User Notice',
        E_STRICT => 'Runtime Notice',
        E_WARNING => 'Warning',
        E_USER_WARNING => 'User Warning',
        E_COMPILE_WARNING => 'Compile Warning',
        E_CORE_WARNING => 'Core Warning',
        E_USER_ERROR => 'User Error',
        E_RECOVERABLE_ERROR => 'Catchable Fatal Error',
        E_COMPILE_ERROR => 'Compile Error',
        E_PARSE => 'Parse Error',
        E_ERROR => 'Error',
        E_CORE_ERROR => 'Core Error',
    ];

    private $loggers = [
        E_DEPRECATED => [null, LogLevel::INFO],
        E_USER_DEPRECATED => [null, LogLevel::INFO],
        E_NOTICE => [null, LogLevel::WARNING],
        E_USER_NOTICE => [null, LogLevel::WARNING],
        E_STRICT => [null, LogLevel::WARNING],
        E_WARNING => [null, LogLevel::WARNING],
        E_USER_WARNING => [null, LogLevel::WARNING],
        E_COMPILE_WARNING => [null, LogLevel::WARNING],
        E_CORE_WARNING => [null, LogLevel::WARNING],
        E_USER_ERROR => [null, LogLevel::CRITICAL],
        E_RECOVERABLE_ERROR => [null, LogLevel::CRITICAL],
        E_COMPILE_ERROR => [null, LogLevel::CRITICAL],
        E_PARSE => [null, LogLevel::CRITICAL],
        E_ERROR => [null, LogLevel::CRITICAL],
        E_CORE_ERROR => [null, LogLevel::CRITICAL],
    ];

    private $thrownErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
    private $scopedErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
    private $tracedErrors = 0x77FB; // E_ALL - E_STRICT - E_PARSE
    private $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE
    private $loggedErrors = 0;
    private $traceReflector;

    private $isRecursive = 0;
    private $isRoot = false;
    private $exceptionHandler;
    private $bootstrappingLogger;

    private static $reservedMemory;
    private static $toStringException = null;
    private static $silencedErrorCache = [];
    private static $silencedErrorCount = 0;
    private static $exitCode = 0;

    /**
     * Registers the error handler.
     *
     * @param self|null $handler The handler to register
     * @param bool      $replace Whether to replace or not any existing handler
     *
     * @return self The registered error handler
     */
    public static function register(self $handler = null, $replace = true)
    {
        if (null === self::$reservedMemory) {
            self::$reservedMemory = str_repeat('x', 10240);
            register_shutdown_function(__CLASS__.'::handleFatalError');
        }

        if ($handlerIsNew = null === $handler) {
            $handler = new static();
        }

        if (null === $prev = set_error_handler([$handler, 'handleError'])) {
            restore_error_handler();
            // Specifying the error types earlier would expose us to https://bugs.php.net/63206
            set_error_handler([$handler, 'handleError'], $handler->thrownErrors | $handler->loggedErrors);
            $handler->isRoot = true;
        }

        if ($handlerIsNew && \is_array($prev) && $prev[0] instanceof self) {
            $handler = $prev[0];
            $replace = false;
        }
        if (!$replace && $prev) {
            restore_error_handler();
            $handlerIsRegistered = \is_array($prev) && $handler === $prev[0];
        } else {
            $handlerIsRegistered = true;
        }
        if (\is_array($prev = set_exception_handler([$handler, 'handleException'])) && $prev[0] instanceof self) {
            restore_exception_handler();
            if (!$handlerIsRegistered) {
                $handler = $prev[0];
            } elseif ($handler !== $prev[0] && $replace) {
                set_exception_handler([$handler, 'handleException']);
                $p = $prev[0]->setExceptionHandler(null);
                $handler->setExceptionHandler($p);
                $prev[0]->setExceptionHandler($p);
            }
        } else {
            $handler->setExceptionHandler($prev);
        }

        $handler->throwAt(E_ALL & $handler->thrownErrors, true);

        return $handler;
    }

    public function __construct(BufferingLogger $bootstrappingLogger = null)
    {
        if ($bootstrappingLogger) {
            $this->bootstrappingLogger = $bootstrappingLogger;
            $this->setDefaultLogger($bootstrappingLogger);
        }
        $this->traceReflector = new \ReflectionProperty('Exception', 'trace');
        $this->traceReflector->setAccessible(true);
    }

    /**
     * Sets a logger to non assigned errors levels.
     *
     * @param LoggerInterface $logger  A PSR-3 logger to put as default for the given levels
     * @param array|int       $levels  An array map of E_* to LogLevel::* or an integer bit field of E_* constants
     * @param bool            $replace Whether to replace or not any existing logger
     */
    public function setDefaultLogger(LoggerInterface $logger, $levels = E_ALL, $replace = false)
    {
        $loggers = [];

        if (\is_array($levels)) {
            foreach ($levels as $type => $logLevel) {
                if (empty($this->loggers[$type][0]) || $replace || $this->loggers[$type][0] === $this->bootstrappingLogger) {
                    $loggers[$type] = [$logger, $logLevel];
                }
            }
        } else {
            if (null === $levels) {
                $levels = E_ALL;
            }
            foreach ($this->loggers as $type => $log) {
                if (($type & $levels) && (empty($log[0]) || $replace || $log[0] === $this->bootstrappingLogger)) {
                    $log[0] = $logger;
                    $loggers[$type] = $log;
                }
            }
        }

        $this->setLoggers($loggers);
    }

    /**
     * Sets a logger for each error level.
     *
     * @param array $loggers Error levels to [LoggerInterface|null, LogLevel::*] map
     *
     * @return array The previous map
     *
     * @throws \InvalidArgumentException
     */
    public function setLoggers(array $loggers)
    {
        $prevLogged = $this->loggedErrors;
        $prev = $this->loggers;
        $flush = [];

        foreach ($loggers as $type => $log) {
            if (!isset($prev[$type])) {
                throw new \InvalidArgumentException('Unknown error type: '.$type);
            }
            if (!\is_array($log)) {
                $log = [$log];
            } elseif (!\array_key_exists(0, $log)) {
                throw new \InvalidArgumentException('No logger provided');
            }
            if (null === $log[0]) {
                $this->loggedErrors &= ~$type;
            } elseif ($log[0] instanceof LoggerInterface) {
                $this->loggedErrors |= $type;
            } else {
                throw new \InvalidArgumentException('Invalid logger provided');
            }
            $this->loggers[$type] = $log + $prev[$type];

            if ($this->bootstrappingLogger && $prev[$type][0] === $this->bootstrappingLogger) {
                $flush[$type] = $type;
            }
        }
        $this->reRegister($prevLogged | $this->thrownErrors);

        if ($flush) {
            foreach ($this->bootstrappingLogger->cleanLogs() as $log) {
                $type = $log[2]['exception'] instanceof \ErrorException ? $log[2]['exception']->getSeverity() : E_ERROR;
                if (!isset($flush[$type])) {
                    $this->bootstrappingLogger->log($log[0], $log[1], $log[2]);
                } elseif ($this->loggers[$type][0]) {
                    $this->loggers[$type][0]->log($this->loggers[$type][1], $log[1], $log[2]);
                }
            }
        }

        return $prev;
    }

    /**
     * Sets a user exception handler.
     *
     * @param callable $handler A handler that will be called on Exception
     *
     * @return callable|null The previous exception handler
     */
    public function setExceptionHandler(callable $handler = null)
    {
        $prev = $this->exceptionHandler;
        $this->exceptionHandler = $handler;

        return $prev;
    }

    /**
     * Sets the PHP error levels that throw an exception when a PHP error occurs.
     *
     * @param int  $levels  A bit field of E_* constants for thrown errors
     * @param bool $replace Replace or amend the previous value
     *
     * @return int The previous value
     */
    public function throwAt($levels, $replace = false)
    {
        $prev = $this->thrownErrors;
        $this->thrownErrors = ($levels | E_RECOVERABLE_ERROR | E_USER_ERROR) & ~E_USER_DEPRECATED & ~E_DEPRECATED;
        if (!$replace) {
            $this->thrownErrors |= $prev;
        }
        $this->reRegister($prev | $this->loggedErrors);

        return $prev;
    }

    /**
     * Sets the PHP error levels for which local variables are preserved.
     *
     * @param int  $levels  A bit field of E_* constants for scoped errors
     * @param bool $replace Replace or amend the previous value
     *
     * @return int The previous value
     */
    public function scopeAt($levels, $replace = false)
    {
        $prev = $this->scopedErrors;
        $this->scopedErrors = (int) $levels;
        if (!$replace) {
            $this->scopedErrors |= $prev;
        }

        return $prev;
    }

    /**
     * Sets the PHP error levels for which the stack trace is preserved.
     *
     * @param int  $levels  A bit field of E_* constants for traced errors
     * @param bool $replace Replace or amend the previous value
     *
     * @return int The previous value
     */
    public function traceAt($levels, $replace = false)
    {
        $prev = $this->tracedErrors;
        $this->tracedErrors = (int) $levels;
        if (!$replace) {
            $this->tracedErrors |= $prev;
        }

        return $prev;
    }

    /**
     * Sets the error levels where the @-operator is ignored.
     *
     * @param int  $levels  A bit field of E_* constants for screamed errors
     * @param bool $replace Replace or amend the previous value
     *
     * @return int The previous value
     */
    public function screamAt($levels, $replace = false)
    {
        $prev = $this->screamedErrors;
        $this->screamedErrors = (int) $levels;
        if (!$replace) {
            $this->screamedErrors |= $prev;
        }

        return $prev;
    }

    /**
     * Re-registers as a PHP error handler if levels changed.
     */
    private function reRegister($prev)
    {
        if ($prev !== $this->thrownErrors | $this->loggedErrors) {
            $handler = set_error_handler('var_dump');
            $handler = \is_array($handler) ? $handler[0] : null;
            restore_error_handler();
            if ($handler === $this) {
                restore_error_handler();
                if ($this->isRoot) {
                    set_error_handler([$this, 'handleError'], $this->thrownErrors | $this->loggedErrors);
                } else {
                    set_error_handler([$this, 'handleError']);
                }
            }
        }
    }

    /**
     * Handles errors by filtering then logging them according to the configured bit fields.
     *
     * @param int    $type    One of the E_* constants
     * @param string $message
     * @param string $file
     * @param int    $line
     *
     * @return bool Returns false when no handling happens so that the PHP engine can handle the error itself
     *
     * @throws \ErrorException When $this->thrownErrors requests so
     *
     * @internal
     */
    public function handleError($type, $message, $file, $line)
    {
        // Level is the current error reporting level to manage silent error.
        $level = error_reporting();
        $silenced = 0 === ($level & $type);
        // Strong errors are not authorized to be silenced.
        $level |= E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED;
        $log = $this->loggedErrors & $type;
        $throw = $this->thrownErrors & $type & $level;
        $type &= $level | $this->screamedErrors;

        if (!$type || (!$log && !$throw)) {
            return !$silenced && $type && $log;
        }
        $scope = $this->scopedErrors & $type;

        if (4 < $numArgs = \func_num_args()) {
            $context = $scope ? (func_get_arg(4) ?: []) : [];
        } else {
            $context = [];
        }

        if (isset($context['GLOBALS']) && $scope) {
            $e = $context;                  // Whatever the signature of the method,
            unset($e['GLOBALS'], $context); // $context is always a reference in 5.3
            $context = $e;
        }

        if (false !== strpos($message, "class@anonymous\0")) {
            $logMessage = $this->levels[$type].': '.(new FlattenException())->setMessage($message)->getMessage();
        } else {
            $logMessage = $this->levels[$type].': '.$message;
        }

        if (null !== self::$toStringException) {
            $errorAsException = self::$toStringException;
            self::$toStringException = null;
        } elseif (!$throw && !($type & $level)) {
            if (!isset(self::$silencedErrorCache[$id = $file.':'.$line])) {
                $lightTrace = $this->tracedErrors & $type ? $this->cleanTrace(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5), $type, $file, $line, false) : [];
                $errorAsException = new SilencedErrorContext($type, $file, $line, isset($lightTrace[1]) ? [$lightTrace[0]] : $lightTrace);
            } elseif (isset(self::$silencedErrorCache[$id][$message])) {
                $lightTrace = null;
                $errorAsException = self::$silencedErrorCache[$id][$message];
                ++$errorAsException->count;
            } else {
                $lightTrace = [];
                $errorAsException = null;
            }

            if (100 < ++self::$silencedErrorCount) {
                self::$silencedErrorCache = $lightTrace = [];
                self::$silencedErrorCount = 1;
            }
            if ($errorAsException) {
                self::$silencedErrorCache[$id][$message] = $errorAsException;
            }
            if (null === $lightTrace) {
                return;
            }
        } else {
            $errorAsException = new \ErrorException($logMessage, 0, $type, $file, $line);

            if ($throw || $this->tracedErrors & $type) {
                $backtrace = $errorAsException->getTrace();
                $lightTrace = $this->cleanTrace($backtrace, $type, $file, $line, $throw);
                $this->traceReflector->setValue($errorAsException, $lightTrace);
            } else {
                $this->traceReflector->setValue($errorAsException, []);
                $backtrace = [];
            }
        }

        if ($throw) {
            if (E_USER_ERROR & $type) {
                for ($i = 1; isset($backtrace[$i]); ++$i) {
                    if (isset($backtrace[$i]['function'], $backtrace[$i]['type'], $backtrace[$i - 1]['function'])
                        && '__toString' === $backtrace[$i]['function']
                        && '->' === $backtrace[$i]['type']
                        && !isset($backtrace[$i - 1]['class'])
                        && ('trigger_error' === $backtrace[$i - 1]['function'] || 'user_error' === $backtrace[$i - 1]['function'])
                    ) {
                        // Here, we know trigger_error() has been called from __toString().
                        // PHP triggers a fatal error when throwing from __toString().
                        // A small convention allows working around the limitation:
                        // given a caught $e exception in __toString(), quitting the method with
                        // `return trigger_error($e, E_USER_ERROR);` allows this error handler
                        // to make $e get through the __toString() barrier.

                        foreach ($context as $e) {
                            if ($e instanceof \Throwable && $e->__toString() === $message) {
                                self::$toStringException = $e;

                                return true;
                            }
                        }

                        // Display the original error message instead of the default one.
                        $this->handleException($errorAsException);

                        // Stop the process by giving back the error to the native handler.
                        return false;
                    }
                }
            }

            throw $errorAsException;
        }

        if ($this->isRecursive) {
            $log = 0;
        } else {
            if (!\defined('HHVM_VERSION')) {
                $currentErrorHandler = set_error_handler('var_dump');
                restore_error_handler();
            }

            try {
                $this->isRecursive = true;
                $level = ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG;
                $this->loggers[$type][0]->log($level, $logMessage, $errorAsException ? ['exception' => $errorAsException] : []);
            } finally {
                $this->isRecursive = false;

                if (!\defined('HHVM_VERSION')) {
                    set_error_handler($currentErrorHandler);
                }
            }
        }

        return !$silenced && $type && $log;
    }

    /**
     * Handles an exception by logging then forwarding it to another handler.
     *
     * @param \Exception|\Throwable $exception An exception to handle
     * @param array                 $error     An array as returned by error_get_last()
     *
     * @internal
     */
    public function handleException($exception, array $error = null)
    {
        if (null === $error) {
            self::$exitCode = 255;
        }
        if (!$exception instanceof \Exception) {
            $exception = new FatalThrowableError($exception);
        }
        $type = $exception instanceof FatalErrorException ? $exception->getSeverity() : E_ERROR;
        $handlerException = null;

        if (($this->loggedErrors & $type) || $exception instanceof FatalThrowableError) {
            if (false !== strpos($message = $exception->getMessage(), "class@anonymous\0")) {
                $message = (new FlattenException())->setMessage($message)->getMessage();
            }
            if ($exception instanceof FatalErrorException) {
                if ($exception instanceof FatalThrowableError) {
                    $error = [
                        'type' => $type,
                        'message' => $message,
                        'file' => $exception->getFile(),
                        'line' => $exception->getLine(),
                    ];
                } else {
                    $message = 'Fatal '.$message;
                }
            } elseif ($exception instanceof \ErrorException) {
                $message = 'Uncaught '.$message;
            } else {
                $message = 'Uncaught Exception: '.$message;
            }
        }
        if ($this->loggedErrors & $type) {
            try {
                $this->loggers[$type][0]->log($this->loggers[$type][1], $message, ['exception' => $exception]);
            } catch (\Throwable $handlerException) {
            }
        }
        if ($exception instanceof FatalErrorException && !$exception instanceof OutOfMemoryException && $error) {
            foreach ($this->getFatalErrorHandlers() as $handler) {
                if ($e = $handler->handleError($error, $exception)) {
                    $exception = $e;
                    break;
                }
            }
        }
        $exceptionHandler = $this->exceptionHandler;
        $this->exceptionHandler = null;
        try {
            if (null !== $exceptionHandler) {
                return $exceptionHandler($exception);
            }
            $handlerException = $handlerException ?: $exception;
        } catch (\Throwable $handlerException) {
        }
        if ($exception === $handlerException) {
            self::$reservedMemory = null; // Disable the fatal error handler
            throw $exception; // Give back $exception to the native handler
        }
        $this->handleException($handlerException);
    }

    /**
     * Shutdown registered function for handling PHP fatal errors.
     *
     * @param array $error An array as returned by error_get_last()
     *
     * @internal
     */
    public static function handleFatalError(array $error = null)
    {
        if (null === self::$reservedMemory) {
            return;
        }

        $handler = self::$reservedMemory = null;
        $handlers = [];
        $previousHandler = null;
        $sameHandlerLimit = 10;

        while (!\is_array($handler) || !$handler[0] instanceof self) {
            $handler = set_exception_handler('var_dump');
            restore_exception_handler();

            if (!$handler) {
                break;
            }
            restore_exception_handler();

            if ($handler !== $previousHandler) {
                array_unshift($handlers, $handler);
                $previousHandler = $handler;
            } elseif (0 === --$sameHandlerLimit) {
                $handler = null;
                break;
            }
        }
        foreach ($handlers as $h) {
            set_exception_handler($h);
        }
        if (!$handler) {
            return;
        }
        if ($handler !== $h) {
            $handler[0]->setExceptionHandler($h);
        }
        $handler = $handler[0];
        $handlers = [];

        if ($exit = null === $error) {
            $error = error_get_last();
        }

        if ($error && $error['type'] &= E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR) {
            // Let's not throw anymore but keep logging
            $handler->throwAt(0, true);
            $trace = isset($error['backtrace']) ? $error['backtrace'] : null;

            if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) {
                $exception = new OutOfMemoryException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, false, $trace);
            } else {
                $exception = new FatalErrorException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, true, $trace);
            }
        } else {
            $exception = null;
        }

        try {
            if (null !== $exception) {
                self::$exitCode = 255;
                $handler->handleException($exception, $error);
            }
        } catch (FatalErrorException $e) {
            // Ignore this re-throw
        }

        if ($exit && self::$exitCode) {
            $exitCode = self::$exitCode;
            register_shutdown_function('register_shutdown_function', function () use ($exitCode) { exit($exitCode); });
        }
    }

    /**
     * Gets the fatal error handlers.
     *
     * Override this method if you want to define more fatal error handlers.
     *
     * @return FatalErrorHandlerInterface[] An array of FatalErrorHandlerInterface
     */
    protected function getFatalErrorHandlers()
    {
        return [
            new UndefinedFunctionFatalErrorHandler(),
            new UndefinedMethodFatalErrorHandler(),
            new ClassNotFoundFatalErrorHandler(),
        ];
    }

    /**
     * Cleans the trace by removing function arguments and the frames added by the error handler and DebugClassLoader.
     */
    private function cleanTrace($backtrace, $type, $file, $line, $throw)
    {
        $lightTrace = $backtrace;

        for ($i = 0; isset($backtrace[$i]); ++$i) {
            if (isset($backtrace[$i]['file'], $backtrace[$i]['line']) && $backtrace[$i]['line'] === $line && $backtrace[$i]['file'] === $file) {
                $lightTrace = \array_slice($lightTrace, 1 + $i);
                break;
            }
        }
        if (class_exists(DebugClassLoader::class, false)) {
            for ($i = \count($lightTrace) - 2; 0 < $i; --$i) {
                if (DebugClassLoader::class === ($lightTrace[$i]['class'] ?? null)) {
                    array_splice($lightTrace, --$i, 2);
                }
            }
        }
        if (!($throw || $this->scopedErrors & $type)) {
            for ($i = 0; isset($lightTrace[$i]); ++$i) {
                unset($lightTrace[$i]['args'], $lightTrace[$i]['object']);
            }
        }

        return $lightTrace;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\Exception;

/**
 * Fatal Throwable Error.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class FatalThrowableError extends FatalErrorException
{
    private $originalClassName;

    public function __construct(\Throwable $e)
    {
        $this->originalClassName = \get_class($e);

        if ($e instanceof \ParseError) {
            $severity = E_PARSE;
        } elseif ($e instanceof \TypeError) {
            $severity = E_RECOVERABLE_ERROR;
        } else {
            $severity = E_ERROR;
        }

        \ErrorException::__construct(
            $e->getMessage(),
            $e->getCode(),
            $severity,
            $e->getFile(),
            $e->getLine(),
            $e->getPrevious()
        );

        $this->setTrace($e->getTrace());
    }

    public function getOriginalClassName(): string
    {
        return $this->originalClassName;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\Exception;

/**
 * Out of memory exception.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class OutOfMemoryException extends FatalErrorException
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\Exception;

use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;

/**
 * FlattenException wraps a PHP Error or Exception to be able to serialize it.
 *
 * Basically, this class removes all objects from the trace.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class FlattenException
{
    private $message;
    private $code;
    private $previous;
    private $trace;
    private $class;
    private $statusCode;
    private $headers;
    private $file;
    private $line;

    public static function create(\Exception $exception, $statusCode = null, array $headers = [])
    {
        return static::createFromThrowable($exception, $statusCode, $headers);
    }

    public static function createFromThrowable(\Throwable $exception, ?int $statusCode = null, array $headers = []): self
    {
        $e = new static();
        $e->setMessage($exception->getMessage());
        $e->setCode($exception->getCode());

        if ($exception instanceof HttpExceptionInterface) {
            $statusCode = $exception->getStatusCode();
            $headers = array_merge($headers, $exception->getHeaders());
        } elseif ($exception instanceof RequestExceptionInterface) {
            $statusCode = 400;
        }

        if (null === $statusCode) {
            $statusCode = 500;
        }

        $e->setStatusCode($statusCode);
        $e->setHeaders($headers);
        $e->setTraceFromThrowable($exception);
        $e->setClass($exception instanceof FatalThrowableError ? $exception->getOriginalClassName() : \get_class($exception));
        $e->setFile($exception->getFile());
        $e->setLine($exception->getLine());

        $previous = $exception->getPrevious();

        if ($previous instanceof \Throwable) {
            $e->setPrevious(static::createFromThrowable($previous));
        }

        return $e;
    }

    public function toArray()
    {
        $exceptions = [];
        foreach (array_merge([$this], $this->getAllPrevious()) as $exception) {
            $exceptions[] = [
                'message' => $exception->getMessage(),
                'class' => $exception->getClass(),
                'trace' => $exception->getTrace(),
            ];
        }

        return $exceptions;
    }

    public function getStatusCode()
    {
        return $this->statusCode;
    }

    /**
     * @return $this
     */
    public function setStatusCode($code)
    {
        $this->statusCode = $code;

        return $this;
    }

    public function getHeaders()
    {
        return $this->headers;
    }

    /**
     * @return $this
     */
    public function setHeaders(array $headers)
    {
        $this->headers = $headers;

        return $this;
    }

    public function getClass()
    {
        return $this->class;
    }

    /**
     * @return $this
     */
    public function setClass($class)
    {
        $this->class = 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class;

        return $this;
    }

    public function getFile()
    {
        return $this->file;
    }

    /**
     * @return $this
     */
    public function setFile($file)
    {
        $this->file = $file;

        return $this;
    }

    public function getLine()
    {
        return $this->line;
    }

    /**
     * @return $this
     */
    public function setLine($line)
    {
        $this->line = $line;

        return $this;
    }

    public function getMessage()
    {
        return $this->message;
    }

    /**
     * @return $this
     */
    public function setMessage($message)
    {
        if (false !== strpos($message, "class@anonymous\0")) {
            $message = preg_replace_callback('/class@anonymous\x00.*?\.php0x?[0-9a-fA-F]++/', function ($m) {
                return \class_exists($m[0], false) ? get_parent_class($m[0]).'@anonymous' : $m[0];
            }, $message);
        }

        $this->message = $message;

        return $this;
    }

    public function getCode()
    {
        return $this->code;
    }

    /**
     * @return $this
     */
    public function setCode($code)
    {
        $this->code = $code;

        return $this;
    }

    public function getPrevious()
    {
        return $this->previous;
    }

    /**
     * @return $this
     */
    public function setPrevious(self $previous)
    {
        $this->previous = $previous;

        return $this;
    }

    public function getAllPrevious()
    {
        $exceptions = [];
        $e = $this;
        while ($e = $e->getPrevious()) {
            $exceptions[] = $e;
        }

        return $exceptions;
    }

    public function getTrace()
    {
        return $this->trace;
    }

    /**
     * @deprecated since 4.1, use {@see setTraceFromThrowable()} instead.
     */
    public function setTraceFromException(\Exception $exception)
    {
        @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1, use "setTraceFromThrowable()" instead.', __METHOD__), E_USER_DEPRECATED);

        $this->setTraceFromThrowable($exception);
    }

    public function setTraceFromThrowable(\Throwable $throwable)
    {
        return $this->setTrace($throwable->getTrace(), $throwable->getFile(), $throwable->getLine());
    }

    /**
     * @return $this
     */
    public function setTrace($trace, $file, $line)
    {
        $this->trace = [];
        $this->trace[] = [
            'namespace' => '',
            'short_class' => '',
            'class' => '',
            'type' => '',
            'function' => '',
            'file' => $file,
            'line' => $line,
            'args' => [],
        ];
        foreach ($trace as $entry) {
            $class = '';
            $namespace = '';
            if (isset($entry['class'])) {
                $parts = explode('\\', $entry['class']);
                $class = array_pop($parts);
                $namespace = implode('\\', $parts);
            }

            $this->trace[] = [
                'namespace' => $namespace,
                'short_class' => $class,
                'class' => isset($entry['class']) ? $entry['class'] : '',
                'type' => isset($entry['type']) ? $entry['type'] : '',
                'function' => isset($entry['function']) ? $entry['function'] : null,
                'file' => isset($entry['file']) ? $entry['file'] : null,
                'line' => isset($entry['line']) ? $entry['line'] : null,
                'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : [],
            ];
        }

        return $this;
    }

    private function flattenArgs($args, $level = 0, &$count = 0)
    {
        $result = [];
        foreach ($args as $key => $value) {
            if (++$count > 1e4) {
                return ['array', '*SKIPPED over 10000 entries*'];
            }
            if ($value instanceof \__PHP_Incomplete_Class) {
                // is_object() returns false on PHP<=7.1
                $result[$key] = ['incomplete-object', $this->getClassNameFromIncomplete($value)];
            } elseif (\is_object($value)) {
                $result[$key] = ['object', \get_class($value)];
            } elseif (\is_array($value)) {
                if ($level > 10) {
                    $result[$key] = ['array', '*DEEP NESTED ARRAY*'];
                } else {
                    $result[$key] = ['array', $this->flattenArgs($value, $level + 1, $count)];
                }
            } elseif (null === $value) {
                $result[$key] = ['null', null];
            } elseif (\is_bool($value)) {
                $result[$key] = ['boolean', $value];
            } elseif (\is_int($value)) {
                $result[$key] = ['integer', $value];
            } elseif (\is_float($value)) {
                $result[$key] = ['float', $value];
            } elseif (\is_resource($value)) {
                $result[$key] = ['resource', get_resource_type($value)];
            } else {
                $result[$key] = ['string', (string) $value];
            }
        }

        return $result;
    }

    private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value)
    {
        $array = new \ArrayObject($value);

        return $array['__PHP_Incomplete_Class_Name'];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\Exception;

/**
 * Undefined Method Exception.
 *
 * @author GrÃ©goire Pineau <lyrixx@lyrixx.info>
 */
class UndefinedMethodException extends FatalErrorException
{
    public function __construct(string $message, \ErrorException $previous)
    {
        parent::__construct(
            $message,
            $previous->getCode(),
            $previous->getSeverity(),
            $previous->getFile(),
            $previous->getLine(),
            null,
            true,
            null,
            $previous->getPrevious()
        );
        $this->setTrace($previous->getTrace());
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\Exception;

/**
 * Undefined Function Exception.
 *
 * @author Konstanton Myakshin <koc-dp@yandex.ru>
 */
class UndefinedFunctionException extends FatalErrorException
{
    public function __construct(string $message, \ErrorException $previous)
    {
        parent::__construct(
            $message,
            $previous->getCode(),
            $previous->getSeverity(),
            $previous->getFile(),
            $previous->getLine(),
            null,
            true,
            null,
            $previous->getPrevious()
        );
        $this->setTrace($previous->getTrace());
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\Exception;

/**
 * Fatal Error Exception.
 *
 * @author Konstanton Myakshin <koc-dp@yandex.ru>
 */
class FatalErrorException extends \ErrorException
{
    public function __construct(string $message, int $code, int $severity, string $filename, int $lineno, int $traceOffset = null, bool $traceArgs = true, array $trace = null, \Throwable $previous = null)
    {
        parent::__construct($message, $code, $severity, $filename, $lineno, $previous);

        if (null !== $trace) {
            if (!$traceArgs) {
                foreach ($trace as &$frame) {
                    unset($frame['args'], $frame['this'], $frame);
                }
            }

            $this->setTrace($trace);
        } elseif (null !== $traceOffset) {
            if (\function_exists('xdebug_get_function_stack')) {
                $trace = xdebug_get_function_stack();
                if (0 < $traceOffset) {
                    array_splice($trace, -$traceOffset);
                }

                foreach ($trace as &$frame) {
                    if (!isset($frame['type'])) {
                        // XDebug pre 2.1.1 doesn't currently set the call type key http://bugs.xdebug.org/view.php?id=695
                        if (isset($frame['class'])) {
                            $frame['type'] = '::';
                        }
                    } elseif ('dynamic' === $frame['type']) {
                        $frame['type'] = '->';
                    } elseif ('static' === $frame['type']) {
                        $frame['type'] = '::';
                    }

                    // XDebug also has a different name for the parameters array
                    if (!$traceArgs) {
                        unset($frame['params'], $frame['args']);
                    } elseif (isset($frame['params']) && !isset($frame['args'])) {
                        $frame['args'] = $frame['params'];
                        unset($frame['params']);
                    }
                }

                unset($frame);
                $trace = array_reverse($trace);
            } else {
                $trace = [];
            }

            $this->setTrace($trace);
        }
    }

    protected function setTrace($trace)
    {
        $traceReflector = new \ReflectionProperty('Exception', 'trace');
        $traceReflector->setAccessible(true);
        $traceReflector->setValue($this, $trace);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\Exception;

/**
 * Data Object that represents a Silenced Error.
 *
 * @author GrÃ©goire Pineau <lyrixx@lyrixx.info>
 */
class SilencedErrorContext implements \JsonSerializable
{
    public $count = 1;

    private $severity;
    private $file;
    private $line;
    private $trace;

    public function __construct(int $severity, string $file, int $line, array $trace = [], int $count = 1)
    {
        $this->severity = $severity;
        $this->file = $file;
        $this->line = $line;
        $this->trace = $trace;
        $this->count = $count;
    }

    public function getSeverity()
    {
        return $this->severity;
    }

    public function getFile()
    {
        return $this->file;
    }

    public function getLine()
    {
        return $this->line;
    }

    public function getTrace()
    {
        return $this->trace;
    }

    public function JsonSerialize()
    {
        return [
            'severity' => $this->severity,
            'file' => $this->file,
            'line' => $this->line,
            'trace' => $this->trace,
            'count' => $this->count,
        ];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Debug\Exception;

/**
 * Class (or Trait or Interface) Not Found Exception.
 *
 * @author Konstanton Myakshin <koc-dp@yandex.ru>
 */
class ClassNotFoundException extends FatalErrorException
{
    public function __construct(string $message, \ErrorException $previous)
    {
        parent::__construct(
            $message,
            $previous->getCode(),
            $previous->getSeverity(),
            $previous->getFile(),
            $previous->getLine(),
            null,
            true,
            null,
            $previous->getPrevious()
        );
        $this->setTrace($previous->getTrace());
    }
}
{
    "name": "symfony/debug",
    "type": "library",
    "description": "Symfony Debug Component",
    "keywords": [],
    "homepage": "https://symfony.com",
    "license": "MIT",
    "authors": [
        {
            "name": "Fabien Potencier",
            "email": "fabien@symfony.com"
        },
        {
            "name": "Symfony Community",
            "homepage": "https://symfony.com/contributors"
        }
    ],
    "require": {
        "php": "^7.1.3",
        "psr/log": "~1.0"
    },
    "conflict": {
        "symfony/http-kernel": "<3.4"
    },
    "require-dev": {
        "symfony/http-kernel": "~3.4|~4.0"
    },
    "autoload": {
        "psr-4": { "Symfony\\Component\\Debug\\": "" },
        "exclude-from-classmap": [
            "/Tests/"
        ]
    },
    "minimum-stability": "dev",
    "extra": {
        "branch-alias": {
            "dev-master": "4.2-dev"
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console;

/**
 * Contains all events dispatched by an Application.
 *
 * @author Francesco Levorato <git@flevour.net>
 */
final class ConsoleEvents
{
    /**
     * The COMMAND event allows you to attach listeners before any command is
     * executed by the console. It also allows you to modify the command, input and output
     * before they are handled to the command.
     *
     * @Event("Symfony\Component\Console\Event\ConsoleCommandEvent")
     */
    const COMMAND = 'console.command';

    /**
     * The TERMINATE event allows you to attach listeners after a command is
     * executed by the console.
     *
     * @Event("Symfony\Component\Console\Event\ConsoleTerminateEvent")
     */
    const TERMINATE = 'console.terminate';

    /**
     * The EXCEPTION event occurs when an uncaught exception appears
     * while executing Command#run().
     *
     * This event allows you to deal with the exception or
     * to modify the thrown exception.
     *
     * @Event("Symfony\Component\Console\Event\ConsoleExceptionEvent")
     *
     * @deprecated The console.exception event is deprecated since version 3.3 and will be removed in 4.0. Use the console.error event instead.
     */
    const EXCEPTION = 'console.exception';

    /**
     * The ERROR event occurs when an uncaught exception or error appears.
     *
     * This event allows you to deal with the exception/error or
     * to modify the thrown exception.
     *
     * @Event("Symfony\Component\Console\Event\ConsoleErrorEvent")
     */
    const ERROR = 'console.error';
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\HelpCommand;
use Symfony\Component\Console\Command\ListCommand;
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleExceptionEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\DebugFormatterHelper;
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\ProcessHelper;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputAwareInterface;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\StreamableInputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\Debug\Exception\FatalThrowableError;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * An Application is the container for a collection of commands.
 *
 * It is the main entry point of a Console application.
 *
 * This class is optimized for a standard CLI environment.
 *
 * Usage:
 *
 *     $app = new Application('myapp', '1.0 (stable)');
 *     $app->add(new SimpleCommand());
 *     $app->run();
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Application
{
    private $commands = [];
    private $wantHelps = false;
    private $runningCommand;
    private $name;
    private $version;
    private $commandLoader;
    private $catchExceptions = true;
    private $autoExit = true;
    private $definition;
    private $helperSet;
    private $dispatcher;
    private $terminal;
    private $defaultCommand;
    private $singleCommand = false;
    private $initialized;

    /**
     * @param string $name    The name of the application
     * @param string $version The version of the application
     */
    public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN')
    {
        $this->name = $name;
        $this->version = $version;
        $this->terminal = new Terminal();
        $this->defaultCommand = 'list';
    }

    public function setDispatcher(EventDispatcherInterface $dispatcher)
    {
        $this->dispatcher = $dispatcher;
    }

    public function setCommandLoader(CommandLoaderInterface $commandLoader)
    {
        $this->commandLoader = $commandLoader;
    }

    /**
     * Runs the current application.
     *
     * @return int 0 if everything went fine, or an error code
     *
     * @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}.
     */
    public function run(InputInterface $input = null, OutputInterface $output = null)
    {
        putenv('LINES='.$this->terminal->getHeight());
        putenv('COLUMNS='.$this->terminal->getWidth());

        if (null === $input) {
            $input = new ArgvInput();
        }

        if (null === $output) {
            $output = new ConsoleOutput();
        }

        $renderException = function ($e) use ($output) {
            if (!$e instanceof \Exception) {
                $e = class_exists(FatalThrowableError::class) ? new FatalThrowableError($e) : new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
            }
            if ($output instanceof ConsoleOutputInterface) {
                $this->renderException($e, $output->getErrorOutput());
            } else {
                $this->renderException($e, $output);
            }
        };
        if ($phpHandler = set_exception_handler($renderException)) {
            restore_exception_handler();
            if (!\is_array($phpHandler) || !$phpHandler[0] instanceof ErrorHandler) {
                $debugHandler = true;
            } elseif ($debugHandler = $phpHandler[0]->setExceptionHandler($renderException)) {
                $phpHandler[0]->setExceptionHandler($debugHandler);
            }
        }

        if (null !== $this->dispatcher && $this->dispatcher->hasListeners(ConsoleEvents::EXCEPTION)) {
            @trigger_error(sprintf('The "ConsoleEvents::EXCEPTION" event is deprecated since Symfony 3.3 and will be removed in 4.0. Listen to the "ConsoleEvents::ERROR" event instead.'), E_USER_DEPRECATED);
        }

        $this->configureIO($input, $output);

        try {
            $exitCode = $this->doRun($input, $output);
        } catch (\Exception $e) {
            if (!$this->catchExceptions) {
                throw $e;
            }

            $renderException($e);

            $exitCode = $e->getCode();
            if (is_numeric($exitCode)) {
                $exitCode = (int) $exitCode;
                if (0 === $exitCode) {
                    $exitCode = 1;
                }
            } else {
                $exitCode = 1;
            }
        } finally {
            // if the exception handler changed, keep it
            // otherwise, unregister $renderException
            if (!$phpHandler) {
                if (set_exception_handler($renderException) === $renderException) {
                    restore_exception_handler();
                }
                restore_exception_handler();
            } elseif (!$debugHandler) {
                $finalHandler = $phpHandler[0]->setExceptionHandler(null);
                if ($finalHandler !== $renderException) {
                    $phpHandler[0]->setExceptionHandler($finalHandler);
                }
            }
        }

        if ($this->autoExit) {
            if ($exitCode > 255) {
                $exitCode = 255;
            }

            exit($exitCode);
        }

        return $exitCode;
    }

    /**
     * Runs the current application.
     *
     * @return int 0 if everything went fine, or an error code
     */
    public function doRun(InputInterface $input, OutputInterface $output)
    {
        if (true === $input->hasParameterOption(['--version', '-V'], true)) {
            $output->writeln($this->getLongVersion());

            return 0;
        }

        try {
            // Makes ArgvInput::getFirstArgument() able to distinguish an option from an argument.
            $input->bind($this->getDefinition());
        } catch (ExceptionInterface $e) {
            // Errors must be ignored, full binding/validation happens later when the command is known.
        }

        $name = $this->getCommandName($input);
        if (true === $input->hasParameterOption(['--help', '-h'], true)) {
            if (!$name) {
                $name = 'help';
                $input = new ArrayInput(['command_name' => $this->defaultCommand]);
            } else {
                $this->wantHelps = true;
            }
        }

        if (!$name) {
            $name = $this->defaultCommand;
            $definition = $this->getDefinition();
            $definition->setArguments(array_merge(
                $definition->getArguments(),
                [
                    'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name),
                ]
            ));
        }

        try {
            $e = $this->runningCommand = null;
            // the command name MUST be the first element of the input
            $command = $this->find($name);
        } catch (\Exception $e) {
        } catch (\Throwable $e) {
        }
        if (null !== $e) {
            if (null !== $this->dispatcher) {
                $event = new ConsoleErrorEvent($input, $output, $e);
                $this->dispatcher->dispatch(ConsoleEvents::ERROR, $event);
                $e = $event->getError();

                if (0 === $event->getExitCode()) {
                    return 0;
                }
            }

            throw $e;
        }

        $this->runningCommand = $command;
        $exitCode = $this->doRunCommand($command, $input, $output);
        $this->runningCommand = null;

        return $exitCode;
    }

    public function setHelperSet(HelperSet $helperSet)
    {
        $this->helperSet = $helperSet;
    }

    /**
     * Get the helper set associated with the command.
     *
     * @return HelperSet The HelperSet instance associated with this command
     */
    public function getHelperSet()
    {
        if (!$this->helperSet) {
            $this->helperSet = $this->getDefaultHelperSet();
        }

        return $this->helperSet;
    }

    public function setDefinition(InputDefinition $definition)
    {
        $this->definition = $definition;
    }

    /**
     * Gets the InputDefinition related to this Application.
     *
     * @return InputDefinition The InputDefinition instance
     */
    public function getDefinition()
    {
        if (!$this->definition) {
            $this->definition = $this->getDefaultInputDefinition();
        }

        if ($this->singleCommand) {
            $inputDefinition = $this->definition;
            $inputDefinition->setArguments();

            return $inputDefinition;
        }

        return $this->definition;
    }

    /**
     * Gets the help message.
     *
     * @return string A help message
     */
    public function getHelp()
    {
        return $this->getLongVersion();
    }

    /**
     * Gets whether to catch exceptions or not during commands execution.
     *
     * @return bool Whether to catch exceptions or not during commands execution
     */
    public function areExceptionsCaught()
    {
        return $this->catchExceptions;
    }

    /**
     * Sets whether to catch exceptions or not during commands execution.
     *
     * @param bool $boolean Whether to catch exceptions or not during commands execution
     */
    public function setCatchExceptions($boolean)
    {
        $this->catchExceptions = (bool) $boolean;
    }

    /**
     * Gets whether to automatically exit after a command execution or not.
     *
     * @return bool Whether to automatically exit after a command execution or not
     */
    public function isAutoExitEnabled()
    {
        return $this->autoExit;
    }

    /**
     * Sets whether to automatically exit after a command execution or not.
     *
     * @param bool $boolean Whether to automatically exit after a command execution or not
     */
    public function setAutoExit($boolean)
    {
        $this->autoExit = (bool) $boolean;
    }

    /**
     * Gets the name of the application.
     *
     * @return string The application name
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Sets the application name.
     *
     * @param string $name The application name
     */
    public function setName($name)
    {
        $this->name = $name;
    }

    /**
     * Gets the application version.
     *
     * @return string The application version
     */
    public function getVersion()
    {
        return $this->version;
    }

    /**
     * Sets the application version.
     *
     * @param string $version The application version
     */
    public function setVersion($version)
    {
        $this->version = $version;
    }

    /**
     * Returns the long version of the application.
     *
     * @return string The long application version
     */
    public function getLongVersion()
    {
        if ('UNKNOWN' !== $this->getName()) {
            if ('UNKNOWN' !== $this->getVersion()) {
                return sprintf('%s <info>%s</info>', $this->getName(), $this->getVersion());
            }

            return $this->getName();
        }

        return 'Console Tool';
    }

    /**
     * Registers a new command.
     *
     * @param string $name The command name
     *
     * @return Command The newly created command
     */
    public function register($name)
    {
        return $this->add(new Command($name));
    }

    /**
     * Adds an array of command objects.
     *
     * If a Command is not enabled it will not be added.
     *
     * @param Command[] $commands An array of commands
     */
    public function addCommands(array $commands)
    {
        foreach ($commands as $command) {
            $this->add($command);
        }
    }

    /**
     * Adds a command object.
     *
     * If a command with the same name already exists, it will be overridden.
     * If the command is not enabled it will not be added.
     *
     * @return Command|null The registered command if enabled or null
     */
    public function add(Command $command)
    {
        $this->init();

        $command->setApplication($this);

        if (!$command->isEnabled()) {
            $command->setApplication(null);

            return;
        }

        if (null === $command->getDefinition()) {
            throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', \get_class($command)));
        }

        if (!$command->getName()) {
            throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', \get_class($command)));
        }

        $this->commands[$command->getName()] = $command;

        foreach ($command->getAliases() as $alias) {
            $this->commands[$alias] = $command;
        }

        return $command;
    }

    /**
     * Returns a registered command by name or alias.
     *
     * @param string $name The command name or alias
     *
     * @return Command A Command object
     *
     * @throws CommandNotFoundException When given command name does not exist
     */
    public function get($name)
    {
        $this->init();

        if (!$this->has($name)) {
            throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name));
        }

        $command = $this->commands[$name];

        if ($this->wantHelps) {
            $this->wantHelps = false;

            $helpCommand = $this->get('help');
            $helpCommand->setCommand($command);

            return $helpCommand;
        }

        return $command;
    }

    /**
     * Returns true if the command exists, false otherwise.
     *
     * @param string $name The command name or alias
     *
     * @return bool true if the command exists, false otherwise
     */
    public function has($name)
    {
        $this->init();

        return isset($this->commands[$name]) || ($this->commandLoader && $this->commandLoader->has($name) && $this->add($this->commandLoader->get($name)));
    }

    /**
     * Returns an array of all unique namespaces used by currently registered commands.
     *
     * It does not return the global namespace which always exists.
     *
     * @return string[] An array of namespaces
     */
    public function getNamespaces()
    {
        $namespaces = [];
        foreach ($this->all() as $command) {
            $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName()));

            foreach ($command->getAliases() as $alias) {
                $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias));
            }
        }

        return array_values(array_unique(array_filter($namespaces)));
    }

    /**
     * Finds a registered namespace by a name or an abbreviation.
     *
     * @param string $namespace A namespace or abbreviation to search for
     *
     * @return string A registered namespace
     *
     * @throws CommandNotFoundException When namespace is incorrect or ambiguous
     */
    public function findNamespace($namespace)
    {
        $allNamespaces = $this->getNamespaces();
        $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $namespace);
        $namespaces = preg_grep('{^'.$expr.'}', $allNamespaces);

        if (empty($namespaces)) {
            $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);

            if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) {
                if (1 == \count($alternatives)) {
                    $message .= "\n\nDid you mean this?\n    ";
                } else {
                    $message .= "\n\nDid you mean one of these?\n    ";
                }

                $message .= implode("\n    ", $alternatives);
            }

            throw new CommandNotFoundException($message, $alternatives);
        }

        $exact = \in_array($namespace, $namespaces, true);
        if (\count($namespaces) > 1 && !$exact) {
            throw new CommandNotFoundException(sprintf("The namespace \"%s\" is ambiguous.\nDid you mean one of these?\n%s", $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces));
        }

        return $exact ? $namespace : reset($namespaces);
    }

    /**
     * Finds a command by name or alias.
     *
     * Contrary to get, this command tries to find the best
     * match if you give it an abbreviation of a name or alias.
     *
     * @param string $name A command name or a command alias
     *
     * @return Command A Command instance
     *
     * @throws CommandNotFoundException When command name is incorrect or ambiguous
     */
    public function find($name)
    {
        $this->init();

        $aliases = [];
        $allCommands = $this->commandLoader ? array_merge($this->commandLoader->getNames(), array_keys($this->commands)) : array_keys($this->commands);
        $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name);
        $commands = preg_grep('{^'.$expr.'}', $allCommands);

        if (empty($commands)) {
            $commands = preg_grep('{^'.$expr.'}i', $allCommands);
        }

        // if no commands matched or we just matched namespaces
        if (empty($commands) || \count(preg_grep('{^'.$expr.'$}i', $commands)) < 1) {
            if (false !== $pos = strrpos($name, ':')) {
                // check if a namespace exists and contains commands
                $this->findNamespace(substr($name, 0, $pos));
            }

            $message = sprintf('Command "%s" is not defined.', $name);

            if ($alternatives = $this->findAlternatives($name, $allCommands)) {
                if (1 == \count($alternatives)) {
                    $message .= "\n\nDid you mean this?\n    ";
                } else {
                    $message .= "\n\nDid you mean one of these?\n    ";
                }
                $message .= implode("\n    ", $alternatives);
            }

            throw new CommandNotFoundException($message, $alternatives);
        }

        // filter out aliases for commands which are already on the list
        if (\count($commands) > 1) {
            $commandList = $this->commandLoader ? array_merge(array_flip($this->commandLoader->getNames()), $this->commands) : $this->commands;
            $commands = array_unique(array_filter($commands, function ($nameOrAlias) use ($commandList, $commands, &$aliases) {
                $commandName = $commandList[$nameOrAlias] instanceof Command ? $commandList[$nameOrAlias]->getName() : $nameOrAlias;
                $aliases[$nameOrAlias] = $commandName;

                return $commandName === $nameOrAlias || !\in_array($commandName, $commands);
            }));
        }

        $exact = \in_array($name, $commands, true) || isset($aliases[$name]);
        if (\count($commands) > 1 && !$exact) {
            $usableWidth = $this->terminal->getWidth() - 10;
            $abbrevs = array_values($commands);
            $maxLen = 0;
            foreach ($abbrevs as $abbrev) {
                $maxLen = max(Helper::strlen($abbrev), $maxLen);
            }
            $abbrevs = array_map(function ($cmd) use ($commandList, $usableWidth, $maxLen) {
                if (!$commandList[$cmd] instanceof Command) {
                    return $cmd;
                }
                $abbrev = str_pad($cmd, $maxLen, ' ').' '.$commandList[$cmd]->getDescription();

                return Helper::strlen($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3).'...' : $abbrev;
            }, array_values($commands));
            $suggestions = $this->getAbbreviationSuggestions($abbrevs);

            throw new CommandNotFoundException(sprintf("Command \"%s\" is ambiguous.\nDid you mean one of these?\n%s", $name, $suggestions), array_values($commands));
        }

        return $this->get($exact ? $name : reset($commands));
    }

    /**
     * Gets the commands (registered in the given namespace if provided).
     *
     * The array keys are the full names and the values the command instances.
     *
     * @param string $namespace A namespace name
     *
     * @return Command[] An array of Command instances
     */
    public function all($namespace = null)
    {
        $this->init();

        if (null === $namespace) {
            if (!$this->commandLoader) {
                return $this->commands;
            }

            $commands = $this->commands;
            foreach ($this->commandLoader->getNames() as $name) {
                if (!isset($commands[$name]) && $this->has($name)) {
                    $commands[$name] = $this->get($name);
                }
            }

            return $commands;
        }

        $commands = [];
        foreach ($this->commands as $name => $command) {
            if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) {
                $commands[$name] = $command;
            }
        }

        if ($this->commandLoader) {
            foreach ($this->commandLoader->getNames() as $name) {
                if (!isset($commands[$name]) && $namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1) && $this->has($name)) {
                    $commands[$name] = $this->get($name);
                }
            }
        }

        return $commands;
    }

    /**
     * Returns an array of possible abbreviations given a set of names.
     *
     * @param array $names An array of names
     *
     * @return array An array of abbreviations
     */
    public static function getAbbreviations($names)
    {
        $abbrevs = [];
        foreach ($names as $name) {
            for ($len = \strlen($name); $len > 0; --$len) {
                $abbrev = substr($name, 0, $len);
                $abbrevs[$abbrev][] = $name;
            }
        }

        return $abbrevs;
    }

    /**
     * Renders a caught exception.
     */
    public function renderException(\Exception $e, OutputInterface $output)
    {
        $output->writeln('', OutputInterface::VERBOSITY_QUIET);

        $this->doRenderException($e, $output);

        if (null !== $this->runningCommand) {
            $output->writeln(sprintf('<info>%s</info>', sprintf($this->runningCommand->getSynopsis(), $this->getName())), OutputInterface::VERBOSITY_QUIET);
            $output->writeln('', OutputInterface::VERBOSITY_QUIET);
        }
    }

    protected function doRenderException(\Exception $e, OutputInterface $output)
    {
        do {
            $message = trim($e->getMessage());
            if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
                $title = sprintf('  [%s%s]  ', \get_class($e), 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : '');
                $len = Helper::strlen($title);
            } else {
                $len = 0;
            }

            $width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : PHP_INT_MAX;
            // HHVM only accepts 32 bits integer in str_split, even when PHP_INT_MAX is a 64 bit integer: https://github.com/facebook/hhvm/issues/1327
            if (\defined('HHVM_VERSION') && $width > 1 << 31) {
                $width = 1 << 31;
            }
            $lines = [];
            foreach ('' !== $message ? preg_split('/\r?\n/', $message) : [] as $line) {
                foreach ($this->splitStringByWidth($line, $width - 4) as $line) {
                    // pre-format lines to get the right string length
                    $lineLength = Helper::strlen($line) + 4;
                    $lines[] = [$line, $lineLength];

                    $len = max($lineLength, $len);
                }
            }

            $messages = [];
            if (!$e instanceof ExceptionInterface || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
                $messages[] = sprintf('<comment>%s</comment>', OutputFormatter::escape(sprintf('In %s line %s:', basename($e->getFile()) ?: 'n/a', $e->getLine() ?: 'n/a')));
            }
            $messages[] = $emptyLine = sprintf('<error>%s</error>', str_repeat(' ', $len));
            if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
                $messages[] = sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - Helper::strlen($title))));
            }
            foreach ($lines as $line) {
                $messages[] = sprintf('<error>  %s  %s</error>', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1]));
            }
            $messages[] = $emptyLine;
            $messages[] = '';

            $output->writeln($messages, OutputInterface::VERBOSITY_QUIET);

            if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
                $output->writeln('<comment>Exception trace:</comment>', OutputInterface::VERBOSITY_QUIET);

                // exception related properties
                $trace = $e->getTrace();

                array_unshift($trace, [
                    'function' => '',
                    'file' => $e->getFile() ?: 'n/a',
                    'line' => $e->getLine() ?: 'n/a',
                    'args' => [],
                ]);

                for ($i = 0, $count = \count($trace); $i < $count; ++$i) {
                    $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
                    $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';
                    $function = $trace[$i]['function'];
                    $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a';
                    $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a';

                    $output->writeln(sprintf(' %s%s%s() at <info>%s:%s</info>', $class, $type, $function, $file, $line), OutputInterface::VERBOSITY_QUIET);
                }

                $output->writeln('', OutputInterface::VERBOSITY_QUIET);
            }
        } while ($e = $e->getPrevious());
    }

    /**
     * Tries to figure out the terminal width in which this application runs.
     *
     * @return int|null
     *
     * @deprecated since version 3.2, to be removed in 4.0. Create a Terminal instance instead.
     */
    protected function getTerminalWidth()
    {
        @trigger_error(sprintf('The "%s()" method is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__), E_USER_DEPRECATED);

        return $this->terminal->getWidth();
    }

    /**
     * Tries to figure out the terminal height in which this application runs.
     *
     * @return int|null
     *
     * @deprecated since version 3.2, to be removed in 4.0. Create a Terminal instance instead.
     */
    protected function getTerminalHeight()
    {
        @trigger_error(sprintf('The "%s()" method is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__), E_USER_DEPRECATED);

        return $this->terminal->getHeight();
    }

    /**
     * Tries to figure out the terminal dimensions based on the current environment.
     *
     * @return array Array containing width and height
     *
     * @deprecated since version 3.2, to be removed in 4.0. Create a Terminal instance instead.
     */
    public function getTerminalDimensions()
    {
        @trigger_error(sprintf('The "%s()" method is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.', __METHOD__), E_USER_DEPRECATED);

        return [$this->terminal->getWidth(), $this->terminal->getHeight()];
    }

    /**
     * Sets terminal dimensions.
     *
     * Can be useful to force terminal dimensions for functional tests.
     *
     * @param int $width  The width
     * @param int $height The height
     *
     * @return $this
     *
     * @deprecated since version 3.2, to be removed in 4.0. Set the COLUMNS and LINES env vars instead.
     */
    public function setTerminalDimensions($width, $height)
    {
        @trigger_error(sprintf('The "%s()" method is deprecated as of 3.2 and will be removed in 4.0. Set the COLUMNS and LINES env vars instead.', __METHOD__), E_USER_DEPRECATED);

        putenv('COLUMNS='.$width);
        putenv('LINES='.$height);

        return $this;
    }

    /**
     * Configures the input and output instances based on the user arguments and options.
     */
    protected function configureIO(InputInterface $input, OutputInterface $output)
    {
        if (true === $input->hasParameterOption(['--ansi'], true)) {
            $output->setDecorated(true);
        } elseif (true === $input->hasParameterOption(['--no-ansi'], true)) {
            $output->setDecorated(false);
        }

        if (true === $input->hasParameterOption(['--no-interaction', '-n'], true)) {
            $input->setInteractive(false);
        } elseif (\function_exists('posix_isatty')) {
            $inputStream = null;

            if ($input instanceof StreamableInputInterface) {
                $inputStream = $input->getStream();
            }

            // This check ensures that calling QuestionHelper::setInputStream() works
            // To be removed in 4.0 (in the same time as QuestionHelper::setInputStream)
            if (!$inputStream && $this->getHelperSet()->has('question')) {
                $inputStream = $this->getHelperSet()->get('question')->getInputStream(false);
            }

            if (!@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) {
                $input->setInteractive(false);
            }
        }

        switch ($shellVerbosity = (int) getenv('SHELL_VERBOSITY')) {
            case -1: $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); break;
            case 1: $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); break;
            case 2: $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); break;
            case 3: $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); break;
            default: $shellVerbosity = 0; break;
        }

        if (true === $input->hasParameterOption(['--quiet', '-q'], true)) {
            $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
            $shellVerbosity = -1;
        } else {
            if ($input->hasParameterOption('-vvv', true) || $input->hasParameterOption('--verbose=3', true) || 3 === $input->getParameterOption('--verbose', false, true)) {
                $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
                $shellVerbosity = 3;
            } elseif ($input->hasParameterOption('-vv', true) || $input->hasParameterOption('--verbose=2', true) || 2 === $input->getParameterOption('--verbose', false, true)) {
                $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
                $shellVerbosity = 2;
            } elseif ($input->hasParameterOption('-v', true) || $input->hasParameterOption('--verbose=1', true) || $input->hasParameterOption('--verbose', true) || $input->getParameterOption('--verbose', false, true)) {
                $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
                $shellVerbosity = 1;
            }
        }

        if (-1 === $shellVerbosity) {
            $input->setInteractive(false);
        }

        putenv('SHELL_VERBOSITY='.$shellVerbosity);
        $_ENV['SHELL_VERBOSITY'] = $shellVerbosity;
        $_SERVER['SHELL_VERBOSITY'] = $shellVerbosity;
    }

    /**
     * Runs the current command.
     *
     * If an event dispatcher has been attached to the application,
     * events are also dispatched during the life-cycle of the command.
     *
     * @return int 0 if everything went fine, or an error code
     */
    protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
    {
        foreach ($command->getHelperSet() as $helper) {
            if ($helper instanceof InputAwareInterface) {
                $helper->setInput($input);
            }
        }

        if (null === $this->dispatcher) {
            return $command->run($input, $output);
        }

        // bind before the console.command event, so the listeners have access to input options/arguments
        try {
            $command->mergeApplicationDefinition();
            $input->bind($command->getDefinition());
        } catch (ExceptionInterface $e) {
            // ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition
        }

        $event = new ConsoleCommandEvent($command, $input, $output);
        $e = null;

        try {
            $this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event);

            if ($event->commandShouldRun()) {
                $exitCode = $command->run($input, $output);
            } else {
                $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED;
            }
        } catch (\Exception $e) {
        } catch (\Throwable $e) {
        }
        if (null !== $e) {
            if ($this->dispatcher->hasListeners(ConsoleEvents::EXCEPTION)) {
                $x = $e instanceof \Exception ? $e : new FatalThrowableError($e);
                $event = new ConsoleExceptionEvent($command, $input, $output, $x, $x->getCode());
                $this->dispatcher->dispatch(ConsoleEvents::EXCEPTION, $event);

                if ($x !== $event->getException()) {
                    $e = $event->getException();
                }
            }
            $event = new ConsoleErrorEvent($input, $output, $e, $command);
            $this->dispatcher->dispatch(ConsoleEvents::ERROR, $event);
            $e = $event->getError();

            if (0 === $exitCode = $event->getExitCode()) {
                $e = null;
            }
        }

        $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode);
        $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event);

        if (null !== $e) {
            throw $e;
        }

        return $event->getExitCode();
    }

    /**
     * Gets the name of the command based on input.
     *
     * @return string The command name
     */
    protected function getCommandName(InputInterface $input)
    {
        return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument();
    }

    /**
     * Gets the default input definition.
     *
     * @return InputDefinition An InputDefinition instance
     */
    protected function getDefaultInputDefinition()
    {
        return new InputDefinition([
            new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),

            new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'),
            new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'),
            new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'),
            new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'),
            new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'),
            new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'),
            new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),
        ]);
    }

    /**
     * Gets the default commands that should always be available.
     *
     * @return Command[] An array of default Command instances
     */
    protected function getDefaultCommands()
    {
        return [new HelpCommand(), new ListCommand()];
    }

    /**
     * Gets the default helper set with the helpers that should always be available.
     *
     * @return HelperSet A HelperSet instance
     */
    protected function getDefaultHelperSet()
    {
        return new HelperSet([
            new FormatterHelper(),
            new DebugFormatterHelper(),
            new ProcessHelper(),
            new QuestionHelper(),
        ]);
    }

    /**
     * Returns abbreviated suggestions in string format.
     *
     * @param array $abbrevs Abbreviated suggestions to convert
     *
     * @return string A formatted string of abbreviated suggestions
     */
    private function getAbbreviationSuggestions($abbrevs)
    {
        return '    '.implode("\n    ", $abbrevs);
    }

    /**
     * Returns the namespace part of the command name.
     *
     * This method is not part of public API and should not be used directly.
     *
     * @param string $name  The full name of the command
     * @param string $limit The maximum number of parts of the namespace
     *
     * @return string The namespace of the command
     */
    public function extractNamespace($name, $limit = null)
    {
        $parts = explode(':', $name);
        array_pop($parts);

        return implode(':', null === $limit ? $parts : \array_slice($parts, 0, $limit));
    }

    /**
     * Finds alternative of $name among $collection,
     * if nothing is found in $collection, try in $abbrevs.
     *
     * @param string   $name       The string
     * @param iterable $collection The collection
     *
     * @return string[] A sorted array of similar string
     */
    private function findAlternatives($name, $collection)
    {
        $threshold = 1e3;
        $alternatives = [];

        $collectionParts = [];
        foreach ($collection as $item) {
            $collectionParts[$item] = explode(':', $item);
        }

        foreach (explode(':', $name) as $i => $subname) {
            foreach ($collectionParts as $collectionName => $parts) {
                $exists = isset($alternatives[$collectionName]);
                if (!isset($parts[$i]) && $exists) {
                    $alternatives[$collectionName] += $threshold;
                    continue;
                } elseif (!isset($parts[$i])) {
                    continue;
                }

                $lev = levenshtein($subname, $parts[$i]);
                if ($lev <= \strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) {
                    $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev;
                } elseif ($exists) {
                    $alternatives[$collectionName] += $threshold;
                }
            }
        }

        foreach ($collection as $item) {
            $lev = levenshtein($name, $item);
            if ($lev <= \strlen($name) / 3 || false !== strpos($item, $name)) {
                $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
            }
        }

        $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; });
        ksort($alternatives, SORT_NATURAL | SORT_FLAG_CASE);

        return array_keys($alternatives);
    }

    /**
     * Sets the default Command name.
     *
     * @param string $commandName     The Command name
     * @param bool   $isSingleCommand Set to true if there is only one command in this application
     *
     * @return self
     */
    public function setDefaultCommand($commandName, $isSingleCommand = false)
    {
        $this->defaultCommand = $commandName;

        if ($isSingleCommand) {
            // Ensure the command exist
            $this->find($commandName);

            $this->singleCommand = true;
        }

        return $this;
    }

    /**
     * @internal
     */
    public function isSingleCommand()
    {
        return $this->singleCommand;
    }

    private function splitStringByWidth($string, $width)
    {
        // str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly.
        // additionally, array_slice() is not enough as some character has doubled width.
        // we need a function to split string not by character count but by string width
        if (false === $encoding = mb_detect_encoding($string, null, true)) {
            return str_split($string, $width);
        }

        $utf8String = mb_convert_encoding($string, 'utf8', $encoding);
        $lines = [];
        $line = '';
        foreach (preg_split('//u', $utf8String) as $char) {
            // test if $char could be appended to current line
            if (mb_strwidth($line.$char, 'utf8') <= $width) {
                $line .= $char;
                continue;
            }
            // if not, push current line to array and make new line
            $lines[] = str_pad($line, $width);
            $line = $char;
        }

        $lines[] = \count($lines) ? str_pad($line, $width) : $line;

        mb_convert_variables($encoding, 'utf8', $lines);

        return $lines;
    }

    /**
     * Returns all namespaces of the command name.
     *
     * @param string $name The full name of the command
     *
     * @return string[] The namespaces of the command
     */
    private function extractAllNamespaces($name)
    {
        // -1 as third argument is needed to skip the command short name when exploding
        $parts = explode(':', $name, -1);
        $namespaces = [];

        foreach ($parts as $part) {
            if (\count($namespaces)) {
                $namespaces[] = end($namespaces).':'.$part;
            } else {
                $namespaces[] = $part;
            }
        }

        return $namespaces;
    }

    private function init()
    {
        if ($this->initialized) {
            return;
        }
        $this->initialized = true;

        foreach ($this->getDefaultCommands() as $command) {
            $this->add($command);
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Logger;

use Psr\Log\AbstractLogger;
use Psr\Log\InvalidArgumentException;
use Psr\Log\LogLevel;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * PSR-3 compliant console logger.
 *
 * @author KÃ©vin Dunglas <dunglas@gmail.com>
 *
 * @see http://www.php-fig.org/psr/psr-3/
 */
class ConsoleLogger extends AbstractLogger
{
    const INFO = 'info';
    const ERROR = 'error';

    private $output;
    private $verbosityLevelMap = [
        LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL,
        LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL,
        LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL,
        LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL,
        LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL,
        LogLevel::NOTICE => OutputInterface::VERBOSITY_VERBOSE,
        LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE,
        LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG,
    ];
    private $formatLevelMap = [
        LogLevel::EMERGENCY => self::ERROR,
        LogLevel::ALERT => self::ERROR,
        LogLevel::CRITICAL => self::ERROR,
        LogLevel::ERROR => self::ERROR,
        LogLevel::WARNING => self::INFO,
        LogLevel::NOTICE => self::INFO,
        LogLevel::INFO => self::INFO,
        LogLevel::DEBUG => self::INFO,
    ];
    private $errored = false;

    public function __construct(OutputInterface $output, array $verbosityLevelMap = [], array $formatLevelMap = [])
    {
        $this->output = $output;
        $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap;
        $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap;
    }

    /**
     * {@inheritdoc}
     */
    public function log($level, $message, array $context = [])
    {
        if (!isset($this->verbosityLevelMap[$level])) {
            throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level));
        }

        $output = $this->output;

        // Write to the error output if necessary and available
        if (self::ERROR === $this->formatLevelMap[$level]) {
            if ($this->output instanceof ConsoleOutputInterface) {
                $output = $output->getErrorOutput();
            }
            $this->errored = true;
        }

        // the if condition check isn't necessary -- it's the same one that $output will do internally anyway.
        // We only do it for efficiency here as the message formatting is relatively expensive.
        if ($output->getVerbosity() >= $this->verbosityLevelMap[$level]) {
            $output->writeln(sprintf('<%1$s>[%2$s] %3$s</%1$s>', $this->formatLevelMap[$level], $level, $this->interpolate($message, $context)), $this->verbosityLevelMap[$level]);
        }
    }

    /**
     * Returns true when any messages have been logged at error levels.
     *
     * @return bool
     */
    public function hasErrored()
    {
        return $this->errored;
    }

    /**
     * Interpolates context values into the message placeholders.
     *
     * @author PHP Framework Interoperability Group
     *
     * @param string $message
     * @param array  $context
     *
     * @return string
     */
    private function interpolate($message, array $context)
    {
        if (false === strpos($message, '{')) {
            return $message;
        }

        $replacements = [];
        foreach ($context as $key => $val) {
            if (null === $val || is_scalar($val) || (\is_object($val) && method_exists($val, '__toString'))) {
                $replacements["{{$key}}"] = $val;
            } elseif ($val instanceof \DateTimeInterface) {
                $replacements["{{$key}}"] = $val->format(\DateTime::RFC3339);
            } elseif (\is_object($val)) {
                $replacements["{{$key}}"] = '[object '.\get_class($val).']';
            } else {
                $replacements["{{$key}}"] = '['.\gettype($val).']';
            }
        }

        return strtr($message, $replacements);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

/**
 * StreamableInputInterface is the interface implemented by all input classes
 * that have an input stream.
 *
 * @author Robin Chalas <robin.chalas@gmail.com>
 */
interface StreamableInputInterface extends InputInterface
{
    /**
     * Sets the input stream to read from when interacting with the user.
     *
     * This is mainly useful for testing purpose.
     *
     * @param resource $stream The input stream
     */
    public function setStream($stream);

    /**
     * Returns the input stream.
     *
     * @return resource|null
     */
    public function getStream();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\InvalidOptionException;

/**
 * ArrayInput represents an input provided as an array.
 *
 * Usage:
 *
 *     $input = new ArrayInput(['command' => 'foo:bar', 'foo' => 'bar', '--bar' => 'foobar']);
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ArrayInput extends Input
{
    private $parameters;

    public function __construct(array $parameters, InputDefinition $definition = null)
    {
        $this->parameters = $parameters;

        parent::__construct($definition);
    }

    /**
     * {@inheritdoc}
     */
    public function getFirstArgument()
    {
        foreach ($this->parameters as $key => $value) {
            if ($key && '-' === $key[0]) {
                continue;
            }

            return $value;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function hasParameterOption($values, $onlyParams = false)
    {
        $values = (array) $values;

        foreach ($this->parameters as $k => $v) {
            if (!\is_int($k)) {
                $v = $k;
            }

            if ($onlyParams && '--' === $v) {
                return false;
            }

            if (\in_array($v, $values)) {
                return true;
            }
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function getParameterOption($values, $default = false, $onlyParams = false)
    {
        $values = (array) $values;

        foreach ($this->parameters as $k => $v) {
            if ($onlyParams && ('--' === $k || (\is_int($k) && '--' === $v))) {
                return $default;
            }

            if (\is_int($k)) {
                if (\in_array($v, $values)) {
                    return true;
                }
            } elseif (\in_array($k, $values)) {
                return $v;
            }
        }

        return $default;
    }

    /**
     * Returns a stringified representation of the args passed to the command.
     *
     * @return string
     */
    public function __toString()
    {
        $params = [];
        foreach ($this->parameters as $param => $val) {
            if ($param && '-' === $param[0]) {
                if (\is_array($val)) {
                    foreach ($val as $v) {
                        $params[] = $param.('' != $v ? '='.$this->escapeToken($v) : '');
                    }
                } else {
                    $params[] = $param.('' != $val ? '='.$this->escapeToken($val) : '');
                }
            } else {
                $params[] = \is_array($val) ? implode(' ', array_map([$this, 'escapeToken'], $val)) : $this->escapeToken($val);
            }
        }

        return implode(' ', $params);
    }

    /**
     * {@inheritdoc}
     */
    protected function parse()
    {
        foreach ($this->parameters as $key => $value) {
            if ('--' === $key) {
                return;
            }
            if (0 === strpos($key, '--')) {
                $this->addLongOption(substr($key, 2), $value);
            } elseif ('-' === $key[0]) {
                $this->addShortOption(substr($key, 1), $value);
            } else {
                $this->addArgument($key, $value);
            }
        }
    }

    /**
     * Adds a short option value.
     *
     * @param string $shortcut The short option key
     * @param mixed  $value    The value for the option
     *
     * @throws InvalidOptionException When option given doesn't exist
     */
    private function addShortOption($shortcut, $value)
    {
        if (!$this->definition->hasShortcut($shortcut)) {
            throw new InvalidOptionException(sprintf('The "-%s" option does not exist.', $shortcut));
        }

        $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value);
    }

    /**
     * Adds a long option value.
     *
     * @param string $name  The long option key
     * @param mixed  $value The value for the option
     *
     * @throws InvalidOptionException When option given doesn't exist
     * @throws InvalidOptionException When a required value is missing
     */
    private function addLongOption($name, $value)
    {
        if (!$this->definition->hasOption($name)) {
            throw new InvalidOptionException(sprintf('The "--%s" option does not exist.', $name));
        }

        $option = $this->definition->getOption($name);

        if (null === $value) {
            if ($option->isValueRequired()) {
                throw new InvalidOptionException(sprintf('The "--%s" option requires a value.', $name));
            }

            if (!$option->isValueOptional()) {
                $value = true;
            }
        }

        $this->options[$name] = $value;
    }

    /**
     * Adds an argument value.
     *
     * @param string $name  The argument name
     * @param mixed  $value The value for the argument
     *
     * @throws InvalidArgumentException When argument given doesn't exist
     */
    private function addArgument($name, $value)
    {
        if (!$this->definition->hasArgument($name)) {
            throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
        }

        $this->arguments[$name] = $value;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\RuntimeException;

/**
 * ArgvInput represents an input coming from the CLI arguments.
 *
 * Usage:
 *
 *     $input = new ArgvInput();
 *
 * By default, the `$_SERVER['argv']` array is used for the input values.
 *
 * This can be overridden by explicitly passing the input values in the constructor:
 *
 *     $input = new ArgvInput($_SERVER['argv']);
 *
 * If you pass it yourself, don't forget that the first element of the array
 * is the name of the running application.
 *
 * When passing an argument to the constructor, be sure that it respects
 * the same rules as the argv one. It's almost always better to use the
 * `StringInput` when you want to provide your own input.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html
 * @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02
 */
class ArgvInput extends Input
{
    private $tokens;
    private $parsed;

    /**
     * @param array|null           $argv       An array of parameters from the CLI (in the argv format)
     * @param InputDefinition|null $definition A InputDefinition instance
     */
    public function __construct(array $argv = null, InputDefinition $definition = null)
    {
        if (null === $argv) {
            $argv = $_SERVER['argv'];
        }

        // strip the application name
        array_shift($argv);

        $this->tokens = $argv;

        parent::__construct($definition);
    }

    protected function setTokens(array $tokens)
    {
        $this->tokens = $tokens;
    }

    /**
     * {@inheritdoc}
     */
    protected function parse()
    {
        $parseOptions = true;
        $this->parsed = $this->tokens;
        while (null !== $token = array_shift($this->parsed)) {
            if ($parseOptions && '' == $token) {
                $this->parseArgument($token);
            } elseif ($parseOptions && '--' == $token) {
                $parseOptions = false;
            } elseif ($parseOptions && 0 === strpos($token, '--')) {
                $this->parseLongOption($token);
            } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) {
                $this->parseShortOption($token);
            } else {
                $this->parseArgument($token);
            }
        }
    }

    /**
     * Parses a short option.
     *
     * @param string $token The current token
     */
    private function parseShortOption($token)
    {
        $name = substr($token, 1);

        if (\strlen($name) > 1) {
            if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) {
                // an option with a value (with no space)
                $this->addShortOption($name[0], substr($name, 1));
            } else {
                $this->parseShortOptionSet($name);
            }
        } else {
            $this->addShortOption($name, null);
        }
    }

    /**
     * Parses a short option set.
     *
     * @param string $name The current token
     *
     * @throws RuntimeException When option given doesn't exist
     */
    private function parseShortOptionSet($name)
    {
        $len = \strlen($name);
        for ($i = 0; $i < $len; ++$i) {
            if (!$this->definition->hasShortcut($name[$i])) {
                $encoding = mb_detect_encoding($name, null, true);
                throw new RuntimeException(sprintf('The "-%s" option does not exist.', false === $encoding ? $name[$i] : mb_substr($name, $i, 1, $encoding)));
            }

            $option = $this->definition->getOptionForShortcut($name[$i]);
            if ($option->acceptValue()) {
                $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1));

                break;
            } else {
                $this->addLongOption($option->getName(), null);
            }
        }
    }

    /**
     * Parses a long option.
     *
     * @param string $token The current token
     */
    private function parseLongOption($token)
    {
        $name = substr($token, 2);

        if (false !== $pos = strpos($name, '=')) {
            if (0 === \strlen($value = substr($name, $pos + 1))) {
                // if no value after "=" then substr() returns "" since php7 only, false before
                // see http://php.net/manual/fr/migration70.incompatible.php#119151
                if (\PHP_VERSION_ID < 70000 && false === $value) {
                    $value = '';
                }
                array_unshift($this->parsed, $value);
            }
            $this->addLongOption(substr($name, 0, $pos), $value);
        } else {
            $this->addLongOption($name, null);
        }
    }

    /**
     * Parses an argument.
     *
     * @param string $token The current token
     *
     * @throws RuntimeException When too many arguments are given
     */
    private function parseArgument($token)
    {
        $c = \count($this->arguments);

        // if input is expecting another argument, add it
        if ($this->definition->hasArgument($c)) {
            $arg = $this->definition->getArgument($c);
            $this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token;

        // if last argument isArray(), append token to last argument
        } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) {
            $arg = $this->definition->getArgument($c - 1);
            $this->arguments[$arg->getName()][] = $token;

        // unexpected argument
        } else {
            $all = $this->definition->getArguments();
            if (\count($all)) {
                throw new RuntimeException(sprintf('Too many arguments, expected arguments "%s".', implode('" "', array_keys($all))));
            }

            throw new RuntimeException(sprintf('No arguments expected, got "%s".', $token));
        }
    }

    /**
     * Adds a short option value.
     *
     * @param string $shortcut The short option key
     * @param mixed  $value    The value for the option
     *
     * @throws RuntimeException When option given doesn't exist
     */
    private function addShortOption($shortcut, $value)
    {
        if (!$this->definition->hasShortcut($shortcut)) {
            throw new RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut));
        }

        $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value);
    }

    /**
     * Adds a long option value.
     *
     * @param string $name  The long option key
     * @param mixed  $value The value for the option
     *
     * @throws RuntimeException When option given doesn't exist
     */
    private function addLongOption($name, $value)
    {
        if (!$this->definition->hasOption($name)) {
            throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name));
        }

        $option = $this->definition->getOption($name);

        if (null !== $value && !$option->acceptValue()) {
            throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name));
        }

        if (\in_array($value, ['', null], true) && $option->acceptValue() && \count($this->parsed)) {
            // if option accepts an optional or mandatory argument
            // let's see if there is one provided
            $next = array_shift($this->parsed);
            if ((isset($next[0]) && '-' !== $next[0]) || \in_array($next, ['', null], true)) {
                $value = $next;
            } else {
                array_unshift($this->parsed, $next);
            }
        }

        if (null === $value) {
            if ($option->isValueRequired()) {
                throw new RuntimeException(sprintf('The "--%s" option requires a value.', $name));
            }

            if (!$option->isArray() && !$option->isValueOptional()) {
                $value = true;
            }
        }

        if ($option->isArray()) {
            $this->options[$name][] = $value;
        } else {
            $this->options[$name] = $value;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getFirstArgument()
    {
        $isOption = false;
        foreach ($this->tokens as $i => $token) {
            if ($token && '-' === $token[0]) {
                if (false !== strpos($token, '=') || !isset($this->tokens[$i + 1])) {
                    continue;
                }

                // If it's a long option, consider that everything after "--" is the option name.
                // Otherwise, use the last char (if it's a short option set, only the last one can take a value with space separator)
                $name = '-' === $token[1] ? substr($token, 2) : substr($token, -1);
                if (!isset($this->options[$name]) && !$this->definition->hasShortcut($name)) {
                    // noop
                } elseif ((isset($this->options[$name]) || isset($this->options[$name = $this->definition->shortcutToName($name)])) && $this->tokens[$i + 1] === $this->options[$name]) {
                    $isOption = true;
                }

                continue;
            }

            if ($isOption) {
                $isOption = false;
                continue;
            }

            return $token;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function hasParameterOption($values, $onlyParams = false)
    {
        $values = (array) $values;

        foreach ($this->tokens as $token) {
            if ($onlyParams && '--' === $token) {
                return false;
            }
            foreach ($values as $value) {
                // Options with values:
                //   For long options, test for '--option=' at beginning
                //   For short options, test for '-o' at beginning
                $leading = 0 === strpos($value, '--') ? $value.'=' : $value;
                if ($token === $value || '' !== $leading && 0 === strpos($token, $leading)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function getParameterOption($values, $default = false, $onlyParams = false)
    {
        $values = (array) $values;
        $tokens = $this->tokens;

        while (0 < \count($tokens)) {
            $token = array_shift($tokens);
            if ($onlyParams && '--' === $token) {
                return $default;
            }

            foreach ($values as $value) {
                if ($token === $value) {
                    return array_shift($tokens);
                }
                // Options with values:
                //   For long options, test for '--option=' at beginning
                //   For short options, test for '-o' at beginning
                $leading = 0 === strpos($value, '--') ? $value.'=' : $value;
                if ('' !== $leading && 0 === strpos($token, $leading)) {
                    return substr($token, \strlen($leading));
                }
            }
        }

        return $default;
    }

    /**
     * Returns a stringified representation of the args passed to the command.
     *
     * @return string
     */
    public function __toString()
    {
        $tokens = array_map(function ($token) {
            if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) {
                return $match[1].$this->escapeToken($match[2]);
            }

            if ($token && '-' !== $token[0]) {
                return $this->escapeToken($token);
            }

            return $token;
        }, $this->tokens);

        return implode(' ', $tokens);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;

/**
 * Represents a command line argument.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class InputArgument
{
    const REQUIRED = 1;
    const OPTIONAL = 2;
    const IS_ARRAY = 4;

    private $name;
    private $mode;
    private $default;
    private $description;

    /**
     * @param string               $name        The argument name
     * @param int|null             $mode        The argument mode: self::REQUIRED or self::OPTIONAL
     * @param string               $description A description text
     * @param string|string[]|null $default     The default value (for self::OPTIONAL mode only)
     *
     * @throws InvalidArgumentException When argument mode is not valid
     */
    public function __construct($name, $mode = null, $description = '', $default = null)
    {
        if (null === $mode) {
            $mode = self::OPTIONAL;
        } elseif (!\is_int($mode) || $mode > 7 || $mode < 1) {
            throw new InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode));
        }

        $this->name = $name;
        $this->mode = $mode;
        $this->description = $description;

        $this->setDefault($default);
    }

    /**
     * Returns the argument name.
     *
     * @return string The argument name
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Returns true if the argument is required.
     *
     * @return bool true if parameter mode is self::REQUIRED, false otherwise
     */
    public function isRequired()
    {
        return self::REQUIRED === (self::REQUIRED & $this->mode);
    }

    /**
     * Returns true if the argument can take multiple values.
     *
     * @return bool true if mode is self::IS_ARRAY, false otherwise
     */
    public function isArray()
    {
        return self::IS_ARRAY === (self::IS_ARRAY & $this->mode);
    }

    /**
     * Sets the default value.
     *
     * @param string|string[]|null $default The default value
     *
     * @throws LogicException When incorrect default value is given
     */
    public function setDefault($default = null)
    {
        if (self::REQUIRED === $this->mode && null !== $default) {
            throw new LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.');
        }

        if ($this->isArray()) {
            if (null === $default) {
                $default = [];
            } elseif (!\is_array($default)) {
                throw new LogicException('A default value for an array argument must be an array.');
            }
        }

        $this->default = $default;
    }

    /**
     * Returns the default value.
     *
     * @return string|string[]|null The default value
     */
    public function getDefault()
    {
        return $this->default;
    }

    /**
     * Returns the description text.
     *
     * @return string The description text
     */
    public function getDescription()
    {
        return $this->description;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

/**
 * InputAwareInterface should be implemented by classes that depends on the
 * Console Input.
 *
 * @author Wouter J <waldio.webdesign@gmail.com>
 */
interface InputAwareInterface
{
    /**
     * Sets the Console Input.
     */
    public function setInput(InputInterface $input);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;

/**
 * Represents a command line option.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class InputOption
{
    const VALUE_NONE = 1;
    const VALUE_REQUIRED = 2;
    const VALUE_OPTIONAL = 4;
    const VALUE_IS_ARRAY = 8;

    private $name;
    private $shortcut;
    private $mode;
    private $default;
    private $description;

    /**
     * @param string                        $name        The option name
     * @param string|array|null             $shortcut    The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
     * @param int|null                      $mode        The option mode: One of the VALUE_* constants
     * @param string                        $description A description text
     * @param string|string[]|int|bool|null $default     The default value (must be null for self::VALUE_NONE)
     *
     * @throws InvalidArgumentException If option mode is invalid or incompatible
     */
    public function __construct($name, $shortcut = null, $mode = null, $description = '', $default = null)
    {
        if (0 === strpos($name, '--')) {
            $name = substr($name, 2);
        }

        if (empty($name)) {
            throw new InvalidArgumentException('An option name cannot be empty.');
        }

        if (empty($shortcut)) {
            $shortcut = null;
        }

        if (null !== $shortcut) {
            if (\is_array($shortcut)) {
                $shortcut = implode('|', $shortcut);
            }
            $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-'));
            $shortcuts = array_filter($shortcuts);
            $shortcut = implode('|', $shortcuts);

            if (empty($shortcut)) {
                throw new InvalidArgumentException('An option shortcut cannot be empty.');
            }
        }

        if (null === $mode) {
            $mode = self::VALUE_NONE;
        } elseif (!\is_int($mode) || $mode > 15 || $mode < 1) {
            throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode));
        }

        $this->name = $name;
        $this->shortcut = $shortcut;
        $this->mode = $mode;
        $this->description = $description;

        if ($this->isArray() && !$this->acceptValue()) {
            throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.');
        }

        $this->setDefault($default);
    }

    /**
     * Returns the option shortcut.
     *
     * @return string|null The shortcut
     */
    public function getShortcut()
    {
        return $this->shortcut;
    }

    /**
     * Returns the option name.
     *
     * @return string The name
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Returns true if the option accepts a value.
     *
     * @return bool true if value mode is not self::VALUE_NONE, false otherwise
     */
    public function acceptValue()
    {
        return $this->isValueRequired() || $this->isValueOptional();
    }

    /**
     * Returns true if the option requires a value.
     *
     * @return bool true if value mode is self::VALUE_REQUIRED, false otherwise
     */
    public function isValueRequired()
    {
        return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode);
    }

    /**
     * Returns true if the option takes an optional value.
     *
     * @return bool true if value mode is self::VALUE_OPTIONAL, false otherwise
     */
    public function isValueOptional()
    {
        return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode);
    }

    /**
     * Returns true if the option can take multiple values.
     *
     * @return bool true if mode is self::VALUE_IS_ARRAY, false otherwise
     */
    public function isArray()
    {
        return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode);
    }

    /**
     * Sets the default value.
     *
     * @param string|string[]|int|bool|null $default The default value
     *
     * @throws LogicException When incorrect default value is given
     */
    public function setDefault($default = null)
    {
        if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) {
            throw new LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.');
        }

        if ($this->isArray()) {
            if (null === $default) {
                $default = [];
            } elseif (!\is_array($default)) {
                throw new LogicException('A default value for an array option must be an array.');
            }
        }

        $this->default = $this->acceptValue() ? $default : false;
    }

    /**
     * Returns the default value.
     *
     * @return string|string[]|int|bool|null The default value
     */
    public function getDefault()
    {
        return $this->default;
    }

    /**
     * Returns the description text.
     *
     * @return string The description text
     */
    public function getDescription()
    {
        return $this->description;
    }

    /**
     * Checks whether the given option equals this one.
     *
     * @return bool
     */
    public function equals(self $option)
    {
        return $option->getName() === $this->getName()
            && $option->getShortcut() === $this->getShortcut()
            && $option->getDefault() === $this->getDefault()
            && $option->isArray() === $this->isArray()
            && $option->isValueRequired() === $this->isValueRequired()
            && $option->isValueOptional() === $this->isValueOptional()
        ;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;

/**
 * A InputDefinition represents a set of valid command line arguments and options.
 *
 * Usage:
 *
 *     $definition = new InputDefinition([
 *         new InputArgument('name', InputArgument::REQUIRED),
 *         new InputOption('foo', 'f', InputOption::VALUE_REQUIRED),
 *     ]);
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class InputDefinition
{
    private $arguments;
    private $requiredCount;
    private $hasAnArrayArgument = false;
    private $hasOptional;
    private $options;
    private $shortcuts;

    /**
     * @param array $definition An array of InputArgument and InputOption instance
     */
    public function __construct(array $definition = [])
    {
        $this->setDefinition($definition);
    }

    /**
     * Sets the definition of the input.
     */
    public function setDefinition(array $definition)
    {
        $arguments = [];
        $options = [];
        foreach ($definition as $item) {
            if ($item instanceof InputOption) {
                $options[] = $item;
            } else {
                $arguments[] = $item;
            }
        }

        $this->setArguments($arguments);
        $this->setOptions($options);
    }

    /**
     * Sets the InputArgument objects.
     *
     * @param InputArgument[] $arguments An array of InputArgument objects
     */
    public function setArguments($arguments = [])
    {
        $this->arguments = [];
        $this->requiredCount = 0;
        $this->hasOptional = false;
        $this->hasAnArrayArgument = false;
        $this->addArguments($arguments);
    }

    /**
     * Adds an array of InputArgument objects.
     *
     * @param InputArgument[] $arguments An array of InputArgument objects
     */
    public function addArguments($arguments = [])
    {
        if (null !== $arguments) {
            foreach ($arguments as $argument) {
                $this->addArgument($argument);
            }
        }
    }

    /**
     * @throws LogicException When incorrect argument is given
     */
    public function addArgument(InputArgument $argument)
    {
        if (isset($this->arguments[$argument->getName()])) {
            throw new LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName()));
        }

        if ($this->hasAnArrayArgument) {
            throw new LogicException('Cannot add an argument after an array argument.');
        }

        if ($argument->isRequired() && $this->hasOptional) {
            throw new LogicException('Cannot add a required argument after an optional one.');
        }

        if ($argument->isArray()) {
            $this->hasAnArrayArgument = true;
        }

        if ($argument->isRequired()) {
            ++$this->requiredCount;
        } else {
            $this->hasOptional = true;
        }

        $this->arguments[$argument->getName()] = $argument;
    }

    /**
     * Returns an InputArgument by name or by position.
     *
     * @param string|int $name The InputArgument name or position
     *
     * @return InputArgument An InputArgument object
     *
     * @throws InvalidArgumentException When argument given doesn't exist
     */
    public function getArgument($name)
    {
        if (!$this->hasArgument($name)) {
            throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
        }

        $arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments;

        return $arguments[$name];
    }

    /**
     * Returns true if an InputArgument object exists by name or position.
     *
     * @param string|int $name The InputArgument name or position
     *
     * @return bool true if the InputArgument object exists, false otherwise
     */
    public function hasArgument($name)
    {
        $arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments;

        return isset($arguments[$name]);
    }

    /**
     * Gets the array of InputArgument objects.
     *
     * @return InputArgument[] An array of InputArgument objects
     */
    public function getArguments()
    {
        return $this->arguments;
    }

    /**
     * Returns the number of InputArguments.
     *
     * @return int The number of InputArguments
     */
    public function getArgumentCount()
    {
        return $this->hasAnArrayArgument ? PHP_INT_MAX : \count($this->arguments);
    }

    /**
     * Returns the number of required InputArguments.
     *
     * @return int The number of required InputArguments
     */
    public function getArgumentRequiredCount()
    {
        return $this->requiredCount;
    }

    /**
     * Gets the default values.
     *
     * @return array An array of default values
     */
    public function getArgumentDefaults()
    {
        $values = [];
        foreach ($this->arguments as $argument) {
            $values[$argument->getName()] = $argument->getDefault();
        }

        return $values;
    }

    /**
     * Sets the InputOption objects.
     *
     * @param InputOption[] $options An array of InputOption objects
     */
    public function setOptions($options = [])
    {
        $this->options = [];
        $this->shortcuts = [];
        $this->addOptions($options);
    }

    /**
     * Adds an array of InputOption objects.
     *
     * @param InputOption[] $options An array of InputOption objects
     */
    public function addOptions($options = [])
    {
        foreach ($options as $option) {
            $this->addOption($option);
        }
    }

    /**
     * @throws LogicException When option given already exist
     */
    public function addOption(InputOption $option)
    {
        if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) {
            throw new LogicException(sprintf('An option named "%s" already exists.', $option->getName()));
        }

        if ($option->getShortcut()) {
            foreach (explode('|', $option->getShortcut()) as $shortcut) {
                if (isset($this->shortcuts[$shortcut]) && !$option->equals($this->options[$this->shortcuts[$shortcut]])) {
                    throw new LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut));
                }
            }
        }

        $this->options[$option->getName()] = $option;
        if ($option->getShortcut()) {
            foreach (explode('|', $option->getShortcut()) as $shortcut) {
                $this->shortcuts[$shortcut] = $option->getName();
            }
        }
    }

    /**
     * Returns an InputOption by name.
     *
     * @param string $name The InputOption name
     *
     * @return InputOption A InputOption object
     *
     * @throws InvalidArgumentException When option given doesn't exist
     */
    public function getOption($name)
    {
        if (!$this->hasOption($name)) {
            throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name));
        }

        return $this->options[$name];
    }

    /**
     * Returns true if an InputOption object exists by name.
     *
     * This method can't be used to check if the user included the option when
     * executing the command (use getOption() instead).
     *
     * @param string $name The InputOption name
     *
     * @return bool true if the InputOption object exists, false otherwise
     */
    public function hasOption($name)
    {
        return isset($this->options[$name]);
    }

    /**
     * Gets the array of InputOption objects.
     *
     * @return InputOption[] An array of InputOption objects
     */
    public function getOptions()
    {
        return $this->options;
    }

    /**
     * Returns true if an InputOption object exists by shortcut.
     *
     * @param string $name The InputOption shortcut
     *
     * @return bool true if the InputOption object exists, false otherwise
     */
    public function hasShortcut($name)
    {
        return isset($this->shortcuts[$name]);
    }

    /**
     * Gets an InputOption by shortcut.
     *
     * @param string $shortcut The Shortcut name
     *
     * @return InputOption An InputOption object
     */
    public function getOptionForShortcut($shortcut)
    {
        return $this->getOption($this->shortcutToName($shortcut));
    }

    /**
     * Gets an array of default values.
     *
     * @return array An array of all default values
     */
    public function getOptionDefaults()
    {
        $values = [];
        foreach ($this->options as $option) {
            $values[$option->getName()] = $option->getDefault();
        }

        return $values;
    }

    /**
     * Returns the InputOption name given a shortcut.
     *
     * @param string $shortcut The shortcut
     *
     * @return string The InputOption name
     *
     * @throws InvalidArgumentException When option given does not exist
     *
     * @internal
     */
    public function shortcutToName($shortcut)
    {
        if (!isset($this->shortcuts[$shortcut])) {
            throw new InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut));
        }

        return $this->shortcuts[$shortcut];
    }

    /**
     * Gets the synopsis.
     *
     * @param bool $short Whether to return the short version (with options folded) or not
     *
     * @return string The synopsis
     */
    public function getSynopsis($short = false)
    {
        $elements = [];

        if ($short && $this->getOptions()) {
            $elements[] = '[options]';
        } elseif (!$short) {
            foreach ($this->getOptions() as $option) {
                $value = '';
                if ($option->acceptValue()) {
                    $value = sprintf(
                        ' %s%s%s',
                        $option->isValueOptional() ? '[' : '',
                        strtoupper($option->getName()),
                        $option->isValueOptional() ? ']' : ''
                    );
                }

                $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : '';
                $elements[] = sprintf('[%s--%s%s]', $shortcut, $option->getName(), $value);
            }
        }

        if (\count($elements) && $this->getArguments()) {
            $elements[] = '[--]';
        }

        foreach ($this->getArguments() as $argument) {
            $element = '<'.$argument->getName().'>';
            if (!$argument->isRequired()) {
                $element = '['.$element.']';
            } elseif ($argument->isArray()) {
                $element .= ' ('.$element.')';
            }

            if ($argument->isArray()) {
                $element .= '...';
            }

            $elements[] = $element;
        }

        return implode(' ', $elements);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * StringInput represents an input provided as a string.
 *
 * Usage:
 *
 *     $input = new StringInput('foo --bar="foobar"');
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class StringInput extends ArgvInput
{
    const REGEX_STRING = '([^\s]+?)(?:\s|(?<!\\\\)"|(?<!\\\\)\'|$)';
    const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')';

    /**
     * @param string $input A string representing the parameters from the CLI
     */
    public function __construct($input)
    {
        parent::__construct([]);

        $this->setTokens($this->tokenize($input));
    }

    /**
     * Tokenizes a string.
     *
     * @param string $input The input to tokenize
     *
     * @return array An array of tokens
     *
     * @throws InvalidArgumentException When unable to parse input (should never happen)
     */
    private function tokenize($input)
    {
        $tokens = [];
        $length = \strlen($input);
        $cursor = 0;
        while ($cursor < $length) {
            if (preg_match('/\s+/A', $input, $match, null, $cursor)) {
            } elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, null, $cursor)) {
                $tokens[] = $match[1].$match[2].stripcslashes(str_replace(['"\'', '\'"', '\'\'', '""'], '', substr($match[3], 1, \strlen($match[3]) - 2)));
            } elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, null, $cursor)) {
                $tokens[] = stripcslashes(substr($match[0], 1, \strlen($match[0]) - 2));
            } elseif (preg_match('/'.self::REGEX_STRING.'/A', $input, $match, null, $cursor)) {
                $tokens[] = stripcslashes($match[1]);
            } else {
                // should never happen
                throw new InvalidArgumentException(sprintf('Unable to parse input near "... %s ..."', substr($input, $cursor, 10)));
            }

            $cursor += \strlen($match[0]);
        }

        return $tokens;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;

/**
 * Input is the base class for all concrete Input classes.
 *
 * Three concrete classes are provided by default:
 *
 *  * `ArgvInput`: The input comes from the CLI arguments (argv)
 *  * `StringInput`: The input is provided as a string
 *  * `ArrayInput`: The input is provided as an array
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
abstract class Input implements InputInterface, StreamableInputInterface
{
    protected $definition;
    protected $stream;
    protected $options = [];
    protected $arguments = [];
    protected $interactive = true;

    public function __construct(InputDefinition $definition = null)
    {
        if (null === $definition) {
            $this->definition = new InputDefinition();
        } else {
            $this->bind($definition);
            $this->validate();
        }
    }

    /**
     * {@inheritdoc}
     */
    public function bind(InputDefinition $definition)
    {
        $this->arguments = [];
        $this->options = [];
        $this->definition = $definition;

        $this->parse();
    }

    /**
     * Processes command line arguments.
     */
    abstract protected function parse();

    /**
     * {@inheritdoc}
     */
    public function validate()
    {
        $definition = $this->definition;
        $givenArguments = $this->arguments;

        $missingArguments = array_filter(array_keys($definition->getArguments()), function ($argument) use ($definition, $givenArguments) {
            return !\array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired();
        });

        if (\count($missingArguments) > 0) {
            throw new RuntimeException(sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArguments)));
        }
    }

    /**
     * {@inheritdoc}
     */
    public function isInteractive()
    {
        return $this->interactive;
    }

    /**
     * {@inheritdoc}
     */
    public function setInteractive($interactive)
    {
        $this->interactive = (bool) $interactive;
    }

    /**
     * {@inheritdoc}
     */
    public function getArguments()
    {
        return array_merge($this->definition->getArgumentDefaults(), $this->arguments);
    }

    /**
     * {@inheritdoc}
     */
    public function getArgument($name)
    {
        if (!$this->definition->hasArgument($name)) {
            throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
        }

        return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name)->getDefault();
    }

    /**
     * {@inheritdoc}
     */
    public function setArgument($name, $value)
    {
        if (!$this->definition->hasArgument($name)) {
            throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
        }

        $this->arguments[$name] = $value;
    }

    /**
     * {@inheritdoc}
     */
    public function hasArgument($name)
    {
        return $this->definition->hasArgument($name);
    }

    /**
     * {@inheritdoc}
     */
    public function getOptions()
    {
        return array_merge($this->definition->getOptionDefaults(), $this->options);
    }

    /**
     * {@inheritdoc}
     */
    public function getOption($name)
    {
        if (!$this->definition->hasOption($name)) {
            throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
        }

        return \array_key_exists($name, $this->options) ? $this->options[$name] : $this->definition->getOption($name)->getDefault();
    }

    /**
     * {@inheritdoc}
     */
    public function setOption($name, $value)
    {
        if (!$this->definition->hasOption($name)) {
            throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
        }

        $this->options[$name] = $value;
    }

    /**
     * {@inheritdoc}
     */
    public function hasOption($name)
    {
        return $this->definition->hasOption($name);
    }

    /**
     * Escapes a token through escapeshellarg if it contains unsafe chars.
     *
     * @param string $token
     *
     * @return string
     */
    public function escapeToken($token)
    {
        return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token);
    }

    /**
     * {@inheritdoc}
     */
    public function setStream($stream)
    {
        $this->stream = $stream;
    }

    /**
     * {@inheritdoc}
     */
    public function getStream()
    {
        return $this->stream;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;

/**
 * InputInterface is the interface implemented by all input classes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
interface InputInterface
{
    /**
     * Returns the first argument from the raw parameters (not parsed).
     *
     * @return string|null The value of the first argument or null otherwise
     */
    public function getFirstArgument();

    /**
     * Returns true if the raw parameters (not parsed) contain a value.
     *
     * This method is to be used to introspect the input parameters
     * before they have been validated. It must be used carefully.
     * Does not necessarily return the correct result for short options
     * when multiple flags are combined in the same option.
     *
     * @param string|array $values     The values to look for in the raw parameters (can be an array)
     * @param bool         $onlyParams Only check real parameters, skip those following an end of options (--) signal
     *
     * @return bool true if the value is contained in the raw parameters
     */
    public function hasParameterOption($values, $onlyParams = false);

    /**
     * Returns the value of a raw option (not parsed).
     *
     * This method is to be used to introspect the input parameters
     * before they have been validated. It must be used carefully.
     * Does not necessarily return the correct result for short options
     * when multiple flags are combined in the same option.
     *
     * @param string|array $values     The value(s) to look for in the raw parameters (can be an array)
     * @param mixed        $default    The default value to return if no result is found
     * @param bool         $onlyParams Only check real parameters, skip those following an end of options (--) signal
     *
     * @return mixed The option value
     */
    public function getParameterOption($values, $default = false, $onlyParams = false);

    /**
     * Binds the current Input instance with the given arguments and options.
     *
     * @throws RuntimeException
     */
    public function bind(InputDefinition $definition);

    /**
     * Validates the input.
     *
     * @throws RuntimeException When not enough arguments are given
     */
    public function validate();

    /**
     * Returns all the given arguments merged with the default values.
     *
     * @return array
     */
    public function getArguments();

    /**
     * Returns the argument value for a given argument name.
     *
     * @param string $name The argument name
     *
     * @return string|string[]|null The argument value
     *
     * @throws InvalidArgumentException When argument given doesn't exist
     */
    public function getArgument($name);

    /**
     * Sets an argument value by name.
     *
     * @param string               $name  The argument name
     * @param string|string[]|null $value The argument value
     *
     * @throws InvalidArgumentException When argument given doesn't exist
     */
    public function setArgument($name, $value);

    /**
     * Returns true if an InputArgument object exists by name or position.
     *
     * @param string|int $name The InputArgument name or position
     *
     * @return bool true if the InputArgument object exists, false otherwise
     */
    public function hasArgument($name);

    /**
     * Returns all the given options merged with the default values.
     *
     * @return array
     */
    public function getOptions();

    /**
     * Returns the option value for a given option name.
     *
     * @param string $name The option name
     *
     * @return string|string[]|bool|null The option value
     *
     * @throws InvalidArgumentException When option given doesn't exist
     */
    public function getOption($name);

    /**
     * Sets an option value by name.
     *
     * @param string                    $name  The option name
     * @param string|string[]|bool|null $value The option value
     *
     * @throws InvalidArgumentException When option given doesn't exist
     */
    public function setOption($name, $value);

    /**
     * Returns true if an InputOption object exists by name.
     *
     * @param string $name The InputOption name
     *
     * @return bool true if the InputOption object exists, false otherwise
     */
    public function hasOption($name);

    /**
     * Is this input means interactive?
     *
     * @return bool
     */
    public function isInteractive();

    /**
     * Sets the input interactivity.
     *
     * @param bool $interactive If the input should be interactive
     */
    public function setInteractive($interactive);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console;

class Terminal
{
    private static $width;
    private static $height;

    /**
     * Gets the terminal width.
     *
     * @return int
     */
    public function getWidth()
    {
        $width = getenv('COLUMNS');
        if (false !== $width) {
            return (int) trim($width);
        }

        if (null === self::$width) {
            self::initDimensions();
        }

        return self::$width ?: 80;
    }

    /**
     * Gets the terminal height.
     *
     * @return int
     */
    public function getHeight()
    {
        $height = getenv('LINES');
        if (false !== $height) {
            return (int) trim($height);
        }

        if (null === self::$height) {
            self::initDimensions();
        }

        return self::$height ?: 50;
    }

    private static function initDimensions()
    {
        if ('\\' === \DIRECTORY_SEPARATOR) {
            if (preg_match('/^(\d+)x(\d+)(?: \((\d+)x(\d+)\))?$/', trim(getenv('ANSICON')), $matches)) {
                // extract [w, H] from "wxh (WxH)"
                // or [w, h] from "wxh"
                self::$width = (int) $matches[1];
                self::$height = isset($matches[4]) ? (int) $matches[4] : (int) $matches[2];
            } elseif (null !== $dimensions = self::getConsoleMode()) {
                // extract [w, h] from "wxh"
                self::$width = (int) $dimensions[0];
                self::$height = (int) $dimensions[1];
            }
        } elseif ($sttyString = self::getSttyColumns()) {
            if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) {
                // extract [w, h] from "rows h; columns w;"
                self::$width = (int) $matches[2];
                self::$height = (int) $matches[1];
            } elseif (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) {
                // extract [w, h] from "; h rows; w columns"
                self::$width = (int) $matches[2];
                self::$height = (int) $matches[1];
            }
        }
    }

    /**
     * Runs and parses mode CON if it's available, suppressing any error output.
     *
     * @return int[]|null An array composed of the width and the height or null if it could not be parsed
     */
    private static function getConsoleMode()
    {
        if (!\function_exists('proc_open')) {
            return;
        }

        $descriptorspec = [
            1 => ['pipe', 'w'],
            2 => ['pipe', 'w'],
        ];
        $process = proc_open('mode CON', $descriptorspec, $pipes, null, null, ['suppress_errors' => true]);
        if (\is_resource($process)) {
            $info = stream_get_contents($pipes[1]);
            fclose($pipes[1]);
            fclose($pipes[2]);
            proc_close($process);

            if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
                return [(int) $matches[2], (int) $matches[1]];
            }
        }
    }

    /**
     * Runs and parses stty -a if it's available, suppressing any error output.
     *
     * @return string|null
     */
    private static function getSttyColumns()
    {
        if (!\function_exists('proc_open')) {
            return;
        }

        $descriptorspec = [
            1 => ['pipe', 'w'],
            2 => ['pipe', 'w'],
        ];

        $process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, ['suppress_errors' => true]);
        if (\is_resource($process)) {
            $info = stream_get_contents($pipes[1]);
            fclose($pipes[1]);
            fclose($pipes[2]);
            proc_close($process);

            return $info;
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

/**
 * ConsoleOutputInterface is the interface implemented by ConsoleOutput class.
 * This adds information about stderr output stream.
 *
 * @author Dariusz GÃ³recki <darek.krk@gmail.com>
 */
interface ConsoleOutputInterface extends OutputInterface
{
    /**
     * Gets the OutputInterface for errors.
     *
     * @return OutputInterface
     */
    public function getErrorOutput();

    public function setErrorOutput(OutputInterface $error);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

/**
 * @author Jean-FranÃ§ois Simon <contact@jfsimon.fr>
 */
class BufferedOutput extends Output
{
    private $buffer = '';

    /**
     * Empties buffer and returns its content.
     *
     * @return string
     */
    public function fetch()
    {
        $content = $this->buffer;
        $this->buffer = '';

        return $content;
    }

    /**
     * {@inheritdoc}
     */
    protected function doWrite($message, $newline)
    {
        $this->buffer .= $message;

        if ($newline) {
            $this->buffer .= PHP_EOL;
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * OutputInterface is the interface implemented by all Output classes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
interface OutputInterface
{
    const VERBOSITY_QUIET = 16;
    const VERBOSITY_NORMAL = 32;
    const VERBOSITY_VERBOSE = 64;
    const VERBOSITY_VERY_VERBOSE = 128;
    const VERBOSITY_DEBUG = 256;

    const OUTPUT_NORMAL = 1;
    const OUTPUT_RAW = 2;
    const OUTPUT_PLAIN = 4;

    /**
     * Writes a message to the output.
     *
     * @param string|array $messages The message as an array of strings or a single string
     * @param bool         $newline  Whether to add a newline
     * @param int          $options  A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
     */
    public function write($messages, $newline = false, $options = 0);

    /**
     * Writes a message to the output and adds a newline at the end.
     *
     * @param string|array $messages The message as an array of strings or a single string
     * @param int          $options  A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
     */
    public function writeln($messages, $options = 0);

    /**
     * Sets the verbosity of the output.
     *
     * @param int $level The level of verbosity (one of the VERBOSITY constants)
     */
    public function setVerbosity($level);

    /**
     * Gets the current verbosity of the output.
     *
     * @return int The current level of verbosity (one of the VERBOSITY constants)
     */
    public function getVerbosity();

    /**
     * Returns whether verbosity is quiet (-q).
     *
     * @return bool true if verbosity is set to VERBOSITY_QUIET, false otherwise
     */
    public function isQuiet();

    /**
     * Returns whether verbosity is verbose (-v).
     *
     * @return bool true if verbosity is set to VERBOSITY_VERBOSE, false otherwise
     */
    public function isVerbose();

    /**
     * Returns whether verbosity is very verbose (-vv).
     *
     * @return bool true if verbosity is set to VERBOSITY_VERY_VERBOSE, false otherwise
     */
    public function isVeryVerbose();

    /**
     * Returns whether verbosity is debug (-vvv).
     *
     * @return bool true if verbosity is set to VERBOSITY_DEBUG, false otherwise
     */
    public function isDebug();

    /**
     * Sets the decorated flag.
     *
     * @param bool $decorated Whether to decorate the messages
     */
    public function setDecorated($decorated);

    /**
     * Gets the decorated flag.
     *
     * @return bool true if the output will decorate messages, false otherwise
     */
    public function isDecorated();

    public function setFormatter(OutputFormatterInterface $formatter);

    /**
     * Returns current output formatter instance.
     *
     * @return OutputFormatterInterface
     */
    public function getFormatter();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * Base class for output classes.
 *
 * There are five levels of verbosity:
 *
 *  * normal: no option passed (normal output)
 *  * verbose: -v (more output)
 *  * very verbose: -vv (highly extended output)
 *  * debug: -vvv (all debug output)
 *  * quiet: -q (no output)
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
abstract class Output implements OutputInterface
{
    private $verbosity;
    private $formatter;

    /**
     * @param int                           $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
     * @param bool                          $decorated Whether to decorate messages
     * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
     */
    public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = false, OutputFormatterInterface $formatter = null)
    {
        $this->verbosity = null === $verbosity ? self::VERBOSITY_NORMAL : $verbosity;
        $this->formatter = $formatter ?: new OutputFormatter();
        $this->formatter->setDecorated($decorated);
    }

    /**
     * {@inheritdoc}
     */
    public function setFormatter(OutputFormatterInterface $formatter)
    {
        $this->formatter = $formatter;
    }

    /**
     * {@inheritdoc}
     */
    public function getFormatter()
    {
        return $this->formatter;
    }

    /**
     * {@inheritdoc}
     */
    public function setDecorated($decorated)
    {
        $this->formatter->setDecorated($decorated);
    }

    /**
     * {@inheritdoc}
     */
    public function isDecorated()
    {
        return $this->formatter->isDecorated();
    }

    /**
     * {@inheritdoc}
     */
    public function setVerbosity($level)
    {
        $this->verbosity = (int) $level;
    }

    /**
     * {@inheritdoc}
     */
    public function getVerbosity()
    {
        return $this->verbosity;
    }

    /**
     * {@inheritdoc}
     */
    public function isQuiet()
    {
        return self::VERBOSITY_QUIET === $this->verbosity;
    }

    /**
     * {@inheritdoc}
     */
    public function isVerbose()
    {
        return self::VERBOSITY_VERBOSE <= $this->verbosity;
    }

    /**
     * {@inheritdoc}
     */
    public function isVeryVerbose()
    {
        return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity;
    }

    /**
     * {@inheritdoc}
     */
    public function isDebug()
    {
        return self::VERBOSITY_DEBUG <= $this->verbosity;
    }

    /**
     * {@inheritdoc}
     */
    public function writeln($messages, $options = self::OUTPUT_NORMAL)
    {
        $this->write($messages, true, $options);
    }

    /**
     * {@inheritdoc}
     */
    public function write($messages, $newline = false, $options = self::OUTPUT_NORMAL)
    {
        $messages = (array) $messages;

        $types = self::OUTPUT_NORMAL | self::OUTPUT_RAW | self::OUTPUT_PLAIN;
        $type = $types & $options ?: self::OUTPUT_NORMAL;

        $verbosities = self::VERBOSITY_QUIET | self::VERBOSITY_NORMAL | self::VERBOSITY_VERBOSE | self::VERBOSITY_VERY_VERBOSE | self::VERBOSITY_DEBUG;
        $verbosity = $verbosities & $options ?: self::VERBOSITY_NORMAL;

        if ($verbosity > $this->getVerbosity()) {
            return;
        }

        foreach ($messages as $message) {
            switch ($type) {
                case OutputInterface::OUTPUT_NORMAL:
                    $message = $this->formatter->format($message);
                    break;
                case OutputInterface::OUTPUT_RAW:
                    break;
                case OutputInterface::OUTPUT_PLAIN:
                    $message = strip_tags($this->formatter->format($message));
                    break;
            }

            $this->doWrite($message, $newline);
        }
    }

    /**
     * Writes a message to the output.
     *
     * @param string $message A message to write to the output
     * @param bool   $newline Whether to add a newline or not
     */
    abstract protected function doWrite($message, $newline);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * ConsoleOutput is the default class for all CLI output. It uses STDOUT and STDERR.
 *
 * This class is a convenient wrapper around `StreamOutput` for both STDOUT and STDERR.
 *
 *     $output = new ConsoleOutput();
 *
 * This is equivalent to:
 *
 *     $output = new StreamOutput(fopen('php://stdout', 'w'));
 *     $stdErr = new StreamOutput(fopen('php://stderr', 'w'));
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
{
    private $stderr;

    /**
     * @param int                           $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
     * @param bool|null                     $decorated Whether to decorate messages (null for auto-guessing)
     * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
     */
    public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null)
    {
        parent::__construct($this->openOutputStream(), $verbosity, $decorated, $formatter);

        $actualDecorated = $this->isDecorated();
        $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter());

        if (null === $decorated) {
            $this->setDecorated($actualDecorated && $this->stderr->isDecorated());
        }
    }

    /**
     * {@inheritdoc}
     */
    public function setDecorated($decorated)
    {
        parent::setDecorated($decorated);
        $this->stderr->setDecorated($decorated);
    }

    /**
     * {@inheritdoc}
     */
    public function setFormatter(OutputFormatterInterface $formatter)
    {
        parent::setFormatter($formatter);
        $this->stderr->setFormatter($formatter);
    }

    /**
     * {@inheritdoc}
     */
    public function setVerbosity($level)
    {
        parent::setVerbosity($level);
        $this->stderr->setVerbosity($level);
    }

    /**
     * {@inheritdoc}
     */
    public function getErrorOutput()
    {
        return $this->stderr;
    }

    /**
     * {@inheritdoc}
     */
    public function setErrorOutput(OutputInterface $error)
    {
        $this->stderr = $error;
    }

    /**
     * Returns true if current environment supports writing console output to
     * STDOUT.
     *
     * @return bool
     */
    protected function hasStdoutSupport()
    {
        return false === $this->isRunningOS400();
    }

    /**
     * Returns true if current environment supports writing console output to
     * STDERR.
     *
     * @return bool
     */
    protected function hasStderrSupport()
    {
        return false === $this->isRunningOS400();
    }

    /**
     * Checks if current executing environment is IBM iSeries (OS400), which
     * doesn't properly convert character-encodings between ASCII to EBCDIC.
     *
     * @return bool
     */
    private function isRunningOS400()
    {
        $checks = [
            \function_exists('php_uname') ? php_uname('s') : '',
            getenv('OSTYPE'),
            PHP_OS,
        ];

        return false !== stripos(implode(';', $checks), 'OS400');
    }

    /**
     * @return resource
     */
    private function openOutputStream()
    {
        if (!$this->hasStdoutSupport()) {
            return fopen('php://output', 'w');
        }

        return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w');
    }

    /**
     * @return resource
     */
    private function openErrorStream()
    {
        return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w');
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * NullOutput suppresses all output.
 *
 *     $output = new NullOutput();
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Tobias Schultze <http://tobion.de>
 */
class NullOutput implements OutputInterface
{
    /**
     * {@inheritdoc}
     */
    public function setFormatter(OutputFormatterInterface $formatter)
    {
        // do nothing
    }

    /**
     * {@inheritdoc}
     */
    public function getFormatter()
    {
        // to comply with the interface we must return a OutputFormatterInterface
        return new OutputFormatter();
    }

    /**
     * {@inheritdoc}
     */
    public function setDecorated($decorated)
    {
        // do nothing
    }

    /**
     * {@inheritdoc}
     */
    public function isDecorated()
    {
        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function setVerbosity($level)
    {
        // do nothing
    }

    /**
     * {@inheritdoc}
     */
    public function getVerbosity()
    {
        return self::VERBOSITY_QUIET;
    }

    /**
     * {@inheritdoc}
     */
    public function isQuiet()
    {
        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function isVerbose()
    {
        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function isVeryVerbose()
    {
        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function isDebug()
    {
        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function writeln($messages, $options = self::OUTPUT_NORMAL)
    {
        // do nothing
    }

    /**
     * {@inheritdoc}
     */
    public function write($messages, $newline = false, $options = self::OUTPUT_NORMAL)
    {
        // do nothing
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * StreamOutput writes the output to a given stream.
 *
 * Usage:
 *
 *     $output = new StreamOutput(fopen('php://stdout', 'w'));
 *
 * As `StreamOutput` can use any stream, you can also use a file:
 *
 *     $output = new StreamOutput(fopen('/path/to/output.log', 'a', false));
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class StreamOutput extends Output
{
    private $stream;

    /**
     * @param resource                      $stream    A stream resource
     * @param int                           $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
     * @param bool|null                     $decorated Whether to decorate messages (null for auto-guessing)
     * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
     *
     * @throws InvalidArgumentException When first argument is not a real stream
     */
    public function __construct($stream, $verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null)
    {
        if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) {
            throw new InvalidArgumentException('The StreamOutput class needs a stream as its first argument.');
        }

        $this->stream = $stream;

        if (null === $decorated) {
            $decorated = $this->hasColorSupport();
        }

        parent::__construct($verbosity, $decorated, $formatter);
    }

    /**
     * Gets the stream attached to this StreamOutput instance.
     *
     * @return resource A stream resource
     */
    public function getStream()
    {
        return $this->stream;
    }

    /**
     * {@inheritdoc}
     */
    protected function doWrite($message, $newline)
    {
        if ($newline) {
            $message .= PHP_EOL;
        }

        if (false === @fwrite($this->stream, $message)) {
            // should never happen
            throw new RuntimeException('Unable to write output.');
        }

        fflush($this->stream);
    }

    /**
     * Returns true if the stream supports colorization.
     *
     * Colorization is disabled if not supported by the stream:
     *
     * This is tricky on Windows, because Cygwin, Msys2 etc emulate pseudo
     * terminals via named pipes, so we can only check the environment.
     *
     * Reference: Composer\XdebugHandler\Process::supportsColor
     * https://github.com/composer/xdebug-handler
     *
     * @return bool true if the stream supports colorization, false otherwise
     */
    protected function hasColorSupport()
    {
        if ('Hyper' === getenv('TERM_PROGRAM')) {
            return true;
        }

        if (\DIRECTORY_SEPARATOR === '\\') {
            return (\function_exists('sapi_windows_vt100_support')
                && @sapi_windows_vt100_support($this->stream))
                || false !== getenv('ANSICON')
                || 'ON' === getenv('ConEmuANSI')
                || 'xterm' === getenv('TERM');
        }

        if (\function_exists('stream_isatty')) {
            return @stream_isatty($this->stream);
        }

        if (\function_exists('posix_isatty')) {
            return @posix_isatty($this->stream);
        }

        $stat = @fstat($this->stream);
        // Check if formatted mode is S_IFCHR
        return $stat ? 0020000 === ($stat['mode'] & 0170000) : false;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Style;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Decorates output to add console style guide helpers.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
abstract class OutputStyle implements OutputInterface, StyleInterface
{
    private $output;

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

    /**
     * {@inheritdoc}
     */
    public function newLine($count = 1)
    {
        $this->output->write(str_repeat(PHP_EOL, $count));
    }

    /**
     * @param int $max
     *
     * @return ProgressBar
     */
    public function createProgressBar($max = 0)
    {
        return new ProgressBar($this->output, $max);
    }

    /**
     * {@inheritdoc}
     */
    public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
    {
        $this->output->write($messages, $newline, $type);
    }

    /**
     * {@inheritdoc}
     */
    public function writeln($messages, $type = self::OUTPUT_NORMAL)
    {
        $this->output->writeln($messages, $type);
    }

    /**
     * {@inheritdoc}
     */
    public function setVerbosity($level)
    {
        $this->output->setVerbosity($level);
    }

    /**
     * {@inheritdoc}
     */
    public function getVerbosity()
    {
        return $this->output->getVerbosity();
    }

    /**
     * {@inheritdoc}
     */
    public function setDecorated($decorated)
    {
        $this->output->setDecorated($decorated);
    }

    /**
     * {@inheritdoc}
     */
    public function isDecorated()
    {
        return $this->output->isDecorated();
    }

    /**
     * {@inheritdoc}
     */
    public function setFormatter(OutputFormatterInterface $formatter)
    {
        $this->output->setFormatter($formatter);
    }

    /**
     * {@inheritdoc}
     */
    public function getFormatter()
    {
        return $this->output->getFormatter();
    }

    /**
     * {@inheritdoc}
     */
    public function isQuiet()
    {
        return $this->output->isQuiet();
    }

    /**
     * {@inheritdoc}
     */
    public function isVerbose()
    {
        return $this->output->isVerbose();
    }

    /**
     * {@inheritdoc}
     */
    public function isVeryVerbose()
    {
        return $this->output->isVeryVerbose();
    }

    /**
     * {@inheritdoc}
     */
    public function isDebug()
    {
        return $this->output->isDebug();
    }

    protected function getErrorOutput()
    {
        if (!$this->output instanceof ConsoleOutputInterface) {
            return $this->output;
        }

        return $this->output->getErrorOutput();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Style;

use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Helper\SymfonyQuestionHelper;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Terminal;

/**
 * Output decorator helpers for the Symfony Style Guide.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
class SymfonyStyle extends OutputStyle
{
    const MAX_LINE_LENGTH = 120;

    private $input;
    private $questionHelper;
    private $progressBar;
    private $lineLength;
    private $bufferedOutput;

    public function __construct(InputInterface $input, OutputInterface $output)
    {
        $this->input = $input;
        $this->bufferedOutput = new BufferedOutput($output->getVerbosity(), false, clone $output->getFormatter());
        // Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not.
        $width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH;
        $this->lineLength = min($width - (int) (\DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH);

        parent::__construct($output);
    }

    /**
     * Formats a message as a block of text.
     *
     * @param string|array $messages The message to write in the block
     * @param string|null  $type     The block type (added in [] on first line)
     * @param string|null  $style    The style to apply to the whole block
     * @param string       $prefix   The prefix for the block
     * @param bool         $padding  Whether to add vertical padding
     * @param bool         $escape   Whether to escape the message
     */
    public function block($messages, $type = null, $style = null, $prefix = ' ', $padding = false, $escape = true)
    {
        $messages = \is_array($messages) ? array_values($messages) : [$messages];

        $this->autoPrependBlock();
        $this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, $escape));
        $this->newLine();
    }

    /**
     * {@inheritdoc}
     */
    public function title($message)
    {
        $this->autoPrependBlock();
        $this->writeln([
            sprintf('<comment>%s</>', OutputFormatter::escapeTrailingBackslash($message)),
            sprintf('<comment>%s</>', str_repeat('=', Helper::strlenWithoutDecoration($this->getFormatter(), $message))),
        ]);
        $this->newLine();
    }

    /**
     * {@inheritdoc}
     */
    public function section($message)
    {
        $this->autoPrependBlock();
        $this->writeln([
            sprintf('<comment>%s</>', OutputFormatter::escapeTrailingBackslash($message)),
            sprintf('<comment>%s</>', str_repeat('-', Helper::strlenWithoutDecoration($this->getFormatter(), $message))),
        ]);
        $this->newLine();
    }

    /**
     * {@inheritdoc}
     */
    public function listing(array $elements)
    {
        $this->autoPrependText();
        $elements = array_map(function ($element) {
            return sprintf(' * %s', $element);
        }, $elements);

        $this->writeln($elements);
        $this->newLine();
    }

    /**
     * {@inheritdoc}
     */
    public function text($message)
    {
        $this->autoPrependText();

        $messages = \is_array($message) ? array_values($message) : [$message];
        foreach ($messages as $message) {
            $this->writeln(sprintf(' %s', $message));
        }
    }

    /**
     * Formats a command comment.
     *
     * @param string|array $message
     */
    public function comment($message)
    {
        $this->block($message, null, null, '<fg=default;bg=default> // </>', false, false);
    }

    /**
     * {@inheritdoc}
     */
    public function success($message)
    {
        $this->block($message, 'OK', 'fg=black;bg=green', ' ', true);
    }

    /**
     * {@inheritdoc}
     */
    public function error($message)
    {
        $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true);
    }

    /**
     * {@inheritdoc}
     */
    public function warning($message)
    {
        $this->block($message, 'WARNING', 'fg=white;bg=red', ' ', true);
    }

    /**
     * {@inheritdoc}
     */
    public function note($message)
    {
        $this->block($message, 'NOTE', 'fg=yellow', ' ! ');
    }

    /**
     * {@inheritdoc}
     */
    public function caution($message)
    {
        $this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true);
    }

    /**
     * {@inheritdoc}
     */
    public function table(array $headers, array $rows)
    {
        $style = clone Table::getStyleDefinition('symfony-style-guide');
        $style->setCellHeaderFormat('<info>%s</info>');

        $table = new Table($this);
        $table->setHeaders($headers);
        $table->setRows($rows);
        $table->setStyle($style);

        $table->render();
        $this->newLine();
    }

    /**
     * {@inheritdoc}
     */
    public function ask($question, $default = null, $validator = null)
    {
        $question = new Question($question, $default);
        $question->setValidator($validator);

        return $this->askQuestion($question);
    }

    /**
     * {@inheritdoc}
     */
    public function askHidden($question, $validator = null)
    {
        $question = new Question($question);

        $question->setHidden(true);
        $question->setValidator($validator);

        return $this->askQuestion($question);
    }

    /**
     * {@inheritdoc}
     */
    public function confirm($question, $default = true)
    {
        return $this->askQuestion(new ConfirmationQuestion($question, $default));
    }

    /**
     * {@inheritdoc}
     */
    public function choice($question, array $choices, $default = null)
    {
        if (null !== $default) {
            $values = array_flip($choices);
            $default = $values[$default];
        }

        return $this->askQuestion(new ChoiceQuestion($question, $choices, $default));
    }

    /**
     * {@inheritdoc}
     */
    public function progressStart($max = 0)
    {
        $this->progressBar = $this->createProgressBar($max);
        $this->progressBar->start();
    }

    /**
     * {@inheritdoc}
     */
    public function progressAdvance($step = 1)
    {
        $this->getProgressBar()->advance($step);
    }

    /**
     * {@inheritdoc}
     */
    public function progressFinish()
    {
        $this->getProgressBar()->finish();
        $this->newLine(2);
        $this->progressBar = null;
    }

    /**
     * {@inheritdoc}
     */
    public function createProgressBar($max = 0)
    {
        $progressBar = parent::createProgressBar($max);

        if ('\\' !== \DIRECTORY_SEPARATOR || 'Hyper' === getenv('TERM_PROGRAM')) {
            $progressBar->setEmptyBarCharacter('â–‘'); // light shade character \u2591
            $progressBar->setProgressCharacter('');
            $progressBar->setBarCharacter('â–“'); // dark shade character \u2593
        }

        return $progressBar;
    }

    /**
     * @return mixed
     */
    public function askQuestion(Question $question)
    {
        if ($this->input->isInteractive()) {
            $this->autoPrependBlock();
        }

        if (!$this->questionHelper) {
            $this->questionHelper = new SymfonyQuestionHelper();
        }

        $answer = $this->questionHelper->ask($this->input, $this, $question);

        if ($this->input->isInteractive()) {
            $this->newLine();
            $this->bufferedOutput->write("\n");
        }

        return $answer;
    }

    /**
     * {@inheritdoc}
     */
    public function writeln($messages, $type = self::OUTPUT_NORMAL)
    {
        parent::writeln($messages, $type);
        $this->bufferedOutput->writeln($this->reduceBuffer($messages), $type);
    }

    /**
     * {@inheritdoc}
     */
    public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
    {
        parent::write($messages, $newline, $type);
        $this->bufferedOutput->write($this->reduceBuffer($messages), $newline, $type);
    }

    /**
     * {@inheritdoc}
     */
    public function newLine($count = 1)
    {
        parent::newLine($count);
        $this->bufferedOutput->write(str_repeat("\n", $count));
    }

    /**
     * Returns a new instance which makes use of stderr if available.
     *
     * @return self
     */
    public function getErrorStyle()
    {
        return new self($this->input, $this->getErrorOutput());
    }

    /**
     * @return ProgressBar
     */
    private function getProgressBar()
    {
        if (!$this->progressBar) {
            throw new RuntimeException('The ProgressBar is not started.');
        }

        return $this->progressBar;
    }

    private function autoPrependBlock()
    {
        $chars = substr(str_replace(PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2);

        if (!isset($chars[0])) {
            return $this->newLine(); //empty history, so we should start with a new line.
        }
        //Prepend new line for each non LF chars (This means no blank line was output before)
        $this->newLine(2 - substr_count($chars, "\n"));
    }

    private function autoPrependText()
    {
        $fetched = $this->bufferedOutput->fetch();
        //Prepend new line if last char isn't EOL:
        if ("\n" !== substr($fetched, -1)) {
            $this->newLine();
        }
    }

    private function reduceBuffer($messages)
    {
        // We need to know if the two last chars are PHP_EOL
        // Preserve the last 4 chars inserted (PHP_EOL on windows is two chars) in the history buffer
        return array_map(function ($value) {
            return substr($value, -4);
        }, array_merge([$this->bufferedOutput->fetch()], (array) $messages));
    }

    private function createBlock($messages, $type = null, $style = null, $prefix = ' ', $padding = false, $escape = false)
    {
        $indentLength = 0;
        $prefixLength = Helper::strlenWithoutDecoration($this->getFormatter(), $prefix);
        $lines = [];

        if (null !== $type) {
            $type = sprintf('[%s] ', $type);
            $indentLength = \strlen($type);
            $lineIndentation = str_repeat(' ', $indentLength);
        }

        // wrap and add newlines for each element
        foreach ($messages as $key => $message) {
            if ($escape) {
                $message = OutputFormatter::escape($message);
            }

            $lines = array_merge($lines, explode(PHP_EOL, wordwrap($message, $this->lineLength - $prefixLength - $indentLength, PHP_EOL, true)));

            if (\count($messages) > 1 && $key < \count($messages) - 1) {
                $lines[] = '';
            }
        }

        $firstLineIndex = 0;
        if ($padding && $this->isDecorated()) {
            $firstLineIndex = 1;
            array_unshift($lines, '');
            $lines[] = '';
        }

        foreach ($lines as $i => &$line) {
            if (null !== $type) {
                $line = $firstLineIndex === $i ? $type.$line : $lineIndentation.$line;
            }

            $line = $prefix.$line;
            $line .= str_repeat(' ', $this->lineLength - Helper::strlenWithoutDecoration($this->getFormatter(), $line));

            if ($style) {
                $line = sprintf('<%s>%s</>', $style, $line);
            }
        }

        return $lines;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Style;

/**
 * Output style helpers.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
interface StyleInterface
{
    /**
     * Formats a command title.
     *
     * @param string $message
     */
    public function title($message);

    /**
     * Formats a section title.
     *
     * @param string $message
     */
    public function section($message);

    /**
     * Formats a list.
     */
    public function listing(array $elements);

    /**
     * Formats informational text.
     *
     * @param string|array $message
     */
    public function text($message);

    /**
     * Formats a success result bar.
     *
     * @param string|array $message
     */
    public function success($message);

    /**
     * Formats an error result bar.
     *
     * @param string|array $message
     */
    public function error($message);

    /**
     * Formats an warning result bar.
     *
     * @param string|array $message
     */
    public function warning($message);

    /**
     * Formats a note admonition.
     *
     * @param string|array $message
     */
    public function note($message);

    /**
     * Formats a caution admonition.
     *
     * @param string|array $message
     */
    public function caution($message);

    /**
     * Formats a table.
     */
    public function table(array $headers, array $rows);

    /**
     * Asks a question.
     *
     * @param string        $question
     * @param string|null   $default
     * @param callable|null $validator
     *
     * @return mixed
     */
    public function ask($question, $default = null, $validator = null);

    /**
     * Asks a question with the user input hidden.
     *
     * @param string        $question
     * @param callable|null $validator
     *
     * @return mixed
     */
    public function askHidden($question, $validator = null);

    /**
     * Asks for confirmation.
     *
     * @param string $question
     * @param bool   $default
     *
     * @return bool
     */
    public function confirm($question, $default = true);

    /**
     * Asks a choice question.
     *
     * @param string          $question
     * @param array           $choices
     * @param string|int|null $default
     *
     * @return mixed
     */
    public function choice($question, array $choices, $default = null);

    /**
     * Add newline(s).
     *
     * @param int $count The number of newlines
     */
    public function newLine($count = 1);

    /**
     * Starts the progress output.
     *
     * @param int $max Maximum steps (0 if unknown)
     */
    public function progressStart($max = 0);

    /**
     * Advances the progress output X steps.
     *
     * @param int $step Number of steps to advance
     */
    public function progressAdvance($step = 1);

    /**
     * Finishes the progress output.
     */
    public function progressFinish();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Question;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * Represents a choice question.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ChoiceQuestion extends Question
{
    private $choices;
    private $multiselect = false;
    private $prompt = ' > ';
    private $errorMessage = 'Value "%s" is invalid';

    /**
     * @param string $question The question to ask to the user
     * @param array  $choices  The list of available choices
     * @param mixed  $default  The default answer to return
     */
    public function __construct($question, array $choices, $default = null)
    {
        if (!$choices) {
            throw new \LogicException('Choice question must have at least 1 choice available.');
        }

        parent::__construct($question, $default);

        $this->choices = $choices;
        $this->setValidator($this->getDefaultValidator());
        $this->setAutocompleterValues($choices);
    }

    /**
     * Returns available choices.
     *
     * @return array
     */
    public function getChoices()
    {
        return $this->choices;
    }

    /**
     * Sets multiselect option.
     *
     * When multiselect is set to true, multiple choices can be answered.
     *
     * @param bool $multiselect
     *
     * @return $this
     */
    public function setMultiselect($multiselect)
    {
        $this->multiselect = $multiselect;
        $this->setValidator($this->getDefaultValidator());

        return $this;
    }

    /**
     * Returns whether the choices are multiselect.
     *
     * @return bool
     */
    public function isMultiselect()
    {
        return $this->multiselect;
    }

    /**
     * Gets the prompt for choices.
     *
     * @return string
     */
    public function getPrompt()
    {
        return $this->prompt;
    }

    /**
     * Sets the prompt for choices.
     *
     * @param string $prompt
     *
     * @return $this
     */
    public function setPrompt($prompt)
    {
        $this->prompt = $prompt;

        return $this;
    }

    /**
     * Sets the error message for invalid values.
     *
     * The error message has a string placeholder (%s) for the invalid value.
     *
     * @param string $errorMessage
     *
     * @return $this
     */
    public function setErrorMessage($errorMessage)
    {
        $this->errorMessage = $errorMessage;
        $this->setValidator($this->getDefaultValidator());

        return $this;
    }

    /**
     * Returns the default answer validator.
     *
     * @return callable
     */
    private function getDefaultValidator()
    {
        $choices = $this->choices;
        $errorMessage = $this->errorMessage;
        $multiselect = $this->multiselect;
        $isAssoc = $this->isAssoc($choices);

        return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) {
            // Collapse all spaces.
            $selectedChoices = str_replace(' ', '', $selected);

            if ($multiselect) {
                // Check for a separated comma values
                if (!preg_match('/^[^,]+(?:,[^,]+)*$/', $selectedChoices, $matches)) {
                    throw new InvalidArgumentException(sprintf($errorMessage, $selected));
                }
                $selectedChoices = explode(',', $selectedChoices);
            } else {
                $selectedChoices = [$selected];
            }

            $multiselectChoices = [];
            foreach ($selectedChoices as $value) {
                $results = [];
                foreach ($choices as $key => $choice) {
                    if ($choice === $value) {
                        $results[] = $key;
                    }
                }

                if (\count($results) > 1) {
                    throw new InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of %s.', implode(' or ', $results)));
                }

                $result = array_search($value, $choices);

                if (!$isAssoc) {
                    if (false !== $result) {
                        $result = $choices[$result];
                    } elseif (isset($choices[$value])) {
                        $result = $choices[$value];
                    }
                } elseif (false === $result && isset($choices[$value])) {
                    $result = $value;
                }

                if (false === $result) {
                    throw new InvalidArgumentException(sprintf($errorMessage, $value));
                }

                $multiselectChoices[] = (string) $result;
            }

            if ($multiselect) {
                return $multiselectChoices;
            }

            return current($multiselectChoices);
        };
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Question;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;

/**
 * Represents a Question.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Question
{
    private $question;
    private $attempts;
    private $hidden = false;
    private $hiddenFallback = true;
    private $autocompleterValues;
    private $validator;
    private $default;
    private $normalizer;

    /**
     * @param string $question The question to ask to the user
     * @param mixed  $default  The default answer to return if the user enters nothing
     */
    public function __construct($question, $default = null)
    {
        $this->question = $question;
        $this->default = $default;
    }

    /**
     * Returns the question.
     *
     * @return string
     */
    public function getQuestion()
    {
        return $this->question;
    }

    /**
     * Returns the default answer.
     *
     * @return mixed
     */
    public function getDefault()
    {
        return $this->default;
    }

    /**
     * Returns whether the user response must be hidden.
     *
     * @return bool
     */
    public function isHidden()
    {
        return $this->hidden;
    }

    /**
     * Sets whether the user response must be hidden or not.
     *
     * @param bool $hidden
     *
     * @return $this
     *
     * @throws LogicException In case the autocompleter is also used
     */
    public function setHidden($hidden)
    {
        if ($this->autocompleterValues) {
            throw new LogicException('A hidden question cannot use the autocompleter.');
        }

        $this->hidden = (bool) $hidden;

        return $this;
    }

    /**
     * In case the response can not be hidden, whether to fallback on non-hidden question or not.
     *
     * @return bool
     */
    public function isHiddenFallback()
    {
        return $this->hiddenFallback;
    }

    /**
     * Sets whether to fallback on non-hidden question if the response can not be hidden.
     *
     * @param bool $fallback
     *
     * @return $this
     */
    public function setHiddenFallback($fallback)
    {
        $this->hiddenFallback = (bool) $fallback;

        return $this;
    }

    /**
     * Gets values for the autocompleter.
     *
     * @return iterable|null
     */
    public function getAutocompleterValues()
    {
        return $this->autocompleterValues;
    }

    /**
     * Sets values for the autocompleter.
     *
     * @param iterable|null $values
     *
     * @return $this
     *
     * @throws InvalidArgumentException
     * @throws LogicException
     */
    public function setAutocompleterValues($values)
    {
        if (\is_array($values)) {
            $values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values);
        }

        if (null !== $values && !\is_array($values) && !$values instanceof \Traversable) {
            throw new InvalidArgumentException('Autocompleter values can be either an array, `null` or a `Traversable` object.');
        }

        if ($this->hidden) {
            throw new LogicException('A hidden question cannot use the autocompleter.');
        }

        $this->autocompleterValues = $values;

        return $this;
    }

    /**
     * Sets a validator for the question.
     *
     * @param callable|null $validator
     *
     * @return $this
     */
    public function setValidator(callable $validator = null)
    {
        $this->validator = $validator;

        return $this;
    }

    /**
     * Gets the validator for the question.
     *
     * @return callable|null
     */
    public function getValidator()
    {
        return $this->validator;
    }

    /**
     * Sets the maximum number of attempts.
     *
     * Null means an unlimited number of attempts.
     *
     * @param int|null $attempts
     *
     * @return $this
     *
     * @throws InvalidArgumentException in case the number of attempts is invalid
     */
    public function setMaxAttempts($attempts)
    {
        if (null !== $attempts && $attempts < 1) {
            throw new InvalidArgumentException('Maximum number of attempts must be a positive value.');
        }

        $this->attempts = $attempts;

        return $this;
    }

    /**
     * Gets the maximum number of attempts.
     *
     * Null means an unlimited number of attempts.
     *
     * @return int|null
     */
    public function getMaxAttempts()
    {
        return $this->attempts;
    }

    /**
     * Sets a normalizer for the response.
     *
     * The normalizer can be a callable (a string), a closure or a class implementing __invoke.
     *
     * @param callable $normalizer
     *
     * @return $this
     */
    public function setNormalizer(callable $normalizer)
    {
        $this->normalizer = $normalizer;

        return $this;
    }

    /**
     * Gets the normalizer for the response.
     *
     * The normalizer can ba a callable (a string), a closure or a class implementing __invoke.
     *
     * @return callable
     */
    public function getNormalizer()
    {
        return $this->normalizer;
    }

    protected function isAssoc($array)
    {
        return (bool) \count(array_filter(array_keys($array), 'is_string'));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Question;

/**
 * Represents a yes/no question.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ConfirmationQuestion extends Question
{
    private $trueAnswerRegex;

    /**
     * @param string $question        The question to ask to the user
     * @param bool   $default         The default answer to return, true or false
     * @param string $trueAnswerRegex A regex to match the "yes" answer
     */
    public function __construct($question, $default = true, $trueAnswerRegex = '/^y/i')
    {
        parent::__construct($question, (bool) $default);

        $this->trueAnswerRegex = $trueAnswerRegex;
        $this->setNormalizer($this->getDefaultNormalizer());
    }

    /**
     * Returns the default answer normalizer.
     *
     * @return callable
     */
    private function getDefaultNormalizer()
    {
        $default = $this->getDefault();
        $regex = $this->trueAnswerRegex;

        return function ($answer) use ($default, $regex) {
            if (\is_bool($answer)) {
                return $answer;
            }

            $answerIsTrue = (bool) preg_match($regex, $answer);
            if (false === $default) {
                return $answer && $answerIsTrue;
            }

            return '' === $answer || $answerIsTrue;
        };
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * Formatter style class for defining styles.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class OutputFormatterStyle implements OutputFormatterStyleInterface
{
    private static $availableForegroundColors = [
        'black' => ['set' => 30, 'unset' => 39],
        'red' => ['set' => 31, 'unset' => 39],
        'green' => ['set' => 32, 'unset' => 39],
        'yellow' => ['set' => 33, 'unset' => 39],
        'blue' => ['set' => 34, 'unset' => 39],
        'magenta' => ['set' => 35, 'unset' => 39],
        'cyan' => ['set' => 36, 'unset' => 39],
        'white' => ['set' => 37, 'unset' => 39],
        'default' => ['set' => 39, 'unset' => 39],
    ];
    private static $availableBackgroundColors = [
        'black' => ['set' => 40, 'unset' => 49],
        'red' => ['set' => 41, 'unset' => 49],
        'green' => ['set' => 42, 'unset' => 49],
        'yellow' => ['set' => 43, 'unset' => 49],
        'blue' => ['set' => 44, 'unset' => 49],
        'magenta' => ['set' => 45, 'unset' => 49],
        'cyan' => ['set' => 46, 'unset' => 49],
        'white' => ['set' => 47, 'unset' => 49],
        'default' => ['set' => 49, 'unset' => 49],
    ];
    private static $availableOptions = [
        'bold' => ['set' => 1, 'unset' => 22],
        'underscore' => ['set' => 4, 'unset' => 24],
        'blink' => ['set' => 5, 'unset' => 25],
        'reverse' => ['set' => 7, 'unset' => 27],
        'conceal' => ['set' => 8, 'unset' => 28],
    ];

    private $foreground;
    private $background;
    private $options = [];

    /**
     * Initializes output formatter style.
     *
     * @param string|null $foreground The style foreground color name
     * @param string|null $background The style background color name
     * @param array       $options    The style options
     */
    public function __construct($foreground = null, $background = null, array $options = [])
    {
        if (null !== $foreground) {
            $this->setForeground($foreground);
        }
        if (null !== $background) {
            $this->setBackground($background);
        }
        if (\count($options)) {
            $this->setOptions($options);
        }
    }

    /**
     * Sets style foreground color.
     *
     * @param string|null $color The color name
     *
     * @throws InvalidArgumentException When the color name isn't defined
     */
    public function setForeground($color = null)
    {
        if (null === $color) {
            $this->foreground = null;

            return;
        }

        if (!isset(static::$availableForegroundColors[$color])) {
            throw new InvalidArgumentException(sprintf('Invalid foreground color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableForegroundColors))));
        }

        $this->foreground = static::$availableForegroundColors[$color];
    }

    /**
     * Sets style background color.
     *
     * @param string|null $color The color name
     *
     * @throws InvalidArgumentException When the color name isn't defined
     */
    public function setBackground($color = null)
    {
        if (null === $color) {
            $this->background = null;

            return;
        }

        if (!isset(static::$availableBackgroundColors[$color])) {
            throw new InvalidArgumentException(sprintf('Invalid background color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableBackgroundColors))));
        }

        $this->background = static::$availableBackgroundColors[$color];
    }

    /**
     * Sets some specific style option.
     *
     * @param string $option The option name
     *
     * @throws InvalidArgumentException When the option name isn't defined
     */
    public function setOption($option)
    {
        if (!isset(static::$availableOptions[$option])) {
            throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions))));
        }

        if (!\in_array(static::$availableOptions[$option], $this->options)) {
            $this->options[] = static::$availableOptions[$option];
        }
    }

    /**
     * Unsets some specific style option.
     *
     * @param string $option The option name
     *
     * @throws InvalidArgumentException When the option name isn't defined
     */
    public function unsetOption($option)
    {
        if (!isset(static::$availableOptions[$option])) {
            throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions))));
        }

        $pos = array_search(static::$availableOptions[$option], $this->options);
        if (false !== $pos) {
            unset($this->options[$pos]);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function setOptions(array $options)
    {
        $this->options = [];

        foreach ($options as $option) {
            $this->setOption($option);
        }
    }

    /**
     * Applies the style to a given text.
     *
     * @param string $text The text to style
     *
     * @return string
     */
    public function apply($text)
    {
        $setCodes = [];
        $unsetCodes = [];

        if (null !== $this->foreground) {
            $setCodes[] = $this->foreground['set'];
            $unsetCodes[] = $this->foreground['unset'];
        }
        if (null !== $this->background) {
            $setCodes[] = $this->background['set'];
            $unsetCodes[] = $this->background['unset'];
        }
        if (\count($this->options)) {
            foreach ($this->options as $option) {
                $setCodes[] = $option['set'];
                $unsetCodes[] = $option['unset'];
            }
        }

        if (0 === \count($setCodes)) {
            return $text;
        }

        return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

/**
 * Formatter style interface for defining styles.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface OutputFormatterStyleInterface
{
    /**
     * Sets style foreground color.
     *
     * @param string $color The color name
     */
    public function setForeground($color = null);

    /**
     * Sets style background color.
     *
     * @param string $color The color name
     */
    public function setBackground($color = null);

    /**
     * Sets some specific style option.
     *
     * @param string $option The option name
     */
    public function setOption($option);

    /**
     * Unsets some specific style option.
     *
     * @param string $option The option name
     */
    public function unsetOption($option);

    /**
     * Sets multiple style options at once.
     */
    public function setOptions(array $options);

    /**
     * Applies the style to a given text.
     *
     * @param string $text The text to style
     *
     * @return string
     */
    public function apply($text);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

/**
 * Formatter interface for console output.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface OutputFormatterInterface
{
    /**
     * Sets the decorated flag.
     *
     * @param bool $decorated Whether to decorate the messages or not
     */
    public function setDecorated($decorated);

    /**
     * Gets the decorated flag.
     *
     * @return bool true if the output will decorate messages, false otherwise
     */
    public function isDecorated();

    /**
     * Sets a new style.
     *
     * @param string                        $name  The style name
     * @param OutputFormatterStyleInterface $style The style instance
     */
    public function setStyle($name, OutputFormatterStyleInterface $style);

    /**
     * Checks if output formatter has style with specified name.
     *
     * @param string $name
     *
     * @return bool
     */
    public function hasStyle($name);

    /**
     * Gets style options from style with specified name.
     *
     * @param string $name
     *
     * @return OutputFormatterStyleInterface
     *
     * @throws \InvalidArgumentException When style isn't defined
     */
    public function getStyle($name);

    /**
     * Formats a message according to the given styles.
     *
     * @param string $message The message to style
     *
     * @return string The styled message
     */
    public function format($message);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * Formatter class for console output.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class OutputFormatter implements OutputFormatterInterface
{
    private $decorated;
    private $styles = [];
    private $styleStack;

    /**
     * Escapes "<" special char in given text.
     *
     * @param string $text Text to escape
     *
     * @return string Escaped text
     */
    public static function escape($text)
    {
        $text = preg_replace('/([^\\\\]?)</', '$1\\<', $text);

        return self::escapeTrailingBackslash($text);
    }

    /**
     * Escapes trailing "\" in given text.
     *
     * @param string $text Text to escape
     *
     * @return string Escaped text
     *
     * @internal
     */
    public static function escapeTrailingBackslash($text)
    {
        if ('\\' === substr($text, -1)) {
            $len = \strlen($text);
            $text = rtrim($text, '\\');
            $text = str_replace("\0", '', $text);
            $text .= str_repeat("\0", $len - \strlen($text));
        }

        return $text;
    }

    /**
     * Initializes console output formatter.
     *
     * @param bool                            $decorated Whether this formatter should actually decorate strings
     * @param OutputFormatterStyleInterface[] $styles    Array of "name => FormatterStyle" instances
     */
    public function __construct($decorated = false, array $styles = [])
    {
        $this->decorated = (bool) $decorated;

        $this->setStyle('error', new OutputFormatterStyle('white', 'red'));
        $this->setStyle('info', new OutputFormatterStyle('green'));
        $this->setStyle('comment', new OutputFormatterStyle('yellow'));
        $this->setStyle('question', new OutputFormatterStyle('black', 'cyan'));

        foreach ($styles as $name => $style) {
            $this->setStyle($name, $style);
        }

        $this->styleStack = new OutputFormatterStyleStack();
    }

    /**
     * {@inheritdoc}
     */
    public function setDecorated($decorated)
    {
        $this->decorated = (bool) $decorated;
    }

    /**
     * {@inheritdoc}
     */
    public function isDecorated()
    {
        return $this->decorated;
    }

    /**
     * {@inheritdoc}
     */
    public function setStyle($name, OutputFormatterStyleInterface $style)
    {
        $this->styles[strtolower($name)] = $style;
    }

    /**
     * {@inheritdoc}
     */
    public function hasStyle($name)
    {
        return isset($this->styles[strtolower($name)]);
    }

    /**
     * {@inheritdoc}
     */
    public function getStyle($name)
    {
        if (!$this->hasStyle($name)) {
            throw new InvalidArgumentException(sprintf('Undefined style: %s', $name));
        }

        return $this->styles[strtolower($name)];
    }

    /**
     * {@inheritdoc}
     */
    public function format($message)
    {
        $message = (string) $message;
        $offset = 0;
        $output = '';
        $tagRegex = '[a-z][a-z0-9,_=;-]*+';
        preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#ix", $message, $matches, PREG_OFFSET_CAPTURE);
        foreach ($matches[0] as $i => $match) {
            $pos = $match[1];
            $text = $match[0];

            if (0 != $pos && '\\' == $message[$pos - 1]) {
                continue;
            }

            // add the text up to the next tag
            $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset));
            $offset = $pos + \strlen($text);

            // opening tag?
            if ($open = '/' != $text[1]) {
                $tag = $matches[1][$i][0];
            } else {
                $tag = isset($matches[3][$i][0]) ? $matches[3][$i][0] : '';
            }

            if (!$open && !$tag) {
                // </>
                $this->styleStack->pop();
            } elseif (false === $style = $this->createStyleFromString($tag)) {
                $output .= $this->applyCurrentStyle($text);
            } elseif ($open) {
                $this->styleStack->push($style);
            } else {
                $this->styleStack->pop($style);
            }
        }

        $output .= $this->applyCurrentStyle(substr($message, $offset));

        if (false !== strpos($output, "\0")) {
            return strtr($output, ["\0" => '\\', '\\<' => '<']);
        }

        return str_replace('\\<', '<', $output);
    }

    /**
     * @return OutputFormatterStyleStack
     */
    public function getStyleStack()
    {
        return $this->styleStack;
    }

    /**
     * Tries to create new style instance from string.
     *
     * @param string $string
     *
     * @return OutputFormatterStyle|false false if string is not format string
     */
    private function createStyleFromString($string)
    {
        if (isset($this->styles[$string])) {
            return $this->styles[$string];
        }

        if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', $string, $matches, PREG_SET_ORDER)) {
            return false;
        }

        $style = new OutputFormatterStyle();
        foreach ($matches as $match) {
            array_shift($match);
            $match[0] = strtolower($match[0]);

            if ('fg' == $match[0]) {
                $style->setForeground(strtolower($match[1]));
            } elseif ('bg' == $match[0]) {
                $style->setBackground(strtolower($match[1]));
            } elseif ('options' === $match[0]) {
                preg_match_all('([^,;]+)', strtolower($match[1]), $options);
                $options = array_shift($options);
                foreach ($options as $option) {
                    try {
                        $style->setOption($option);
                    } catch (\InvalidArgumentException $e) {
                        @trigger_error(sprintf('Unknown style options are deprecated since Symfony 3.2 and will be removed in 4.0. Exception "%s".', $e->getMessage()), E_USER_DEPRECATED);

                        return false;
                    }
                }
            } else {
                return false;
            }
        }

        return $style;
    }

    /**
     * Applies current style from stack to text, if must be applied.
     *
     * @param string $text Input text
     *
     * @return string Styled text
     */
    private function applyCurrentStyle($text)
    {
        return $this->isDecorated() && \strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * @author Jean-FranÃ§ois Simon <contact@jfsimon.fr>
 */
class OutputFormatterStyleStack
{
    /**
     * @var OutputFormatterStyleInterface[]
     */
    private $styles;

    private $emptyStyle;

    public function __construct(OutputFormatterStyleInterface $emptyStyle = null)
    {
        $this->emptyStyle = $emptyStyle ?: new OutputFormatterStyle();
        $this->reset();
    }

    /**
     * Resets stack (ie. empty internal arrays).
     */
    public function reset()
    {
        $this->styles = [];
    }

    /**
     * Pushes a style in the stack.
     */
    public function push(OutputFormatterStyleInterface $style)
    {
        $this->styles[] = $style;
    }

    /**
     * Pops a style from the stack.
     *
     * @return OutputFormatterStyleInterface
     *
     * @throws InvalidArgumentException When style tags incorrectly nested
     */
    public function pop(OutputFormatterStyleInterface $style = null)
    {
        if (empty($this->styles)) {
            return $this->emptyStyle;
        }

        if (null === $style) {
            return array_pop($this->styles);
        }

        foreach (array_reverse($this->styles, true) as $index => $stackedStyle) {
            if ($style->apply('') === $stackedStyle->apply('')) {
                $this->styles = \array_slice($this->styles, 0, $index);

                return $stackedStyle;
            }
        }

        throw new InvalidArgumentException('Incorrectly nested style tag found.');
    }

    /**
     * Computes current style with stacks top codes.
     *
     * @return OutputFormatterStyle
     */
    public function getCurrent()
    {
        if (empty($this->styles)) {
            return $this->emptyStyle;
        }

        return $this->styles[\count($this->styles) - 1];
    }

    /**
     * @return $this
     */
    public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle)
    {
        $this->emptyStyle = $emptyStyle;

        return $this;
    }

    /**
     * @return OutputFormatterStyleInterface
     */
    public function getEmptyStyle()
    {
        return $this->emptyStyle;
    }
}
<?php

namespace Symfony\Component\Console\CommandLoader;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\CommandNotFoundException;

/**
 * @author Robin Chalas <robin.chalas@gmail.com>
 */
interface CommandLoaderInterface
{
    /**
     * Loads a command.
     *
     * @param string $name
     *
     * @return Command
     *
     * @throws CommandNotFoundException
     */
    public function get($name);

    /**
     * Checks if a command exists.
     *
     * @param string $name
     *
     * @return bool
     */
    public function has($name);

    /**
     * @return string[] All registered command names
     */
    public function getNames();
}
<?php

namespace Symfony\Component\Console\CommandLoader;

use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Exception\CommandNotFoundException;

/**
 * Loads commands from a PSR-11 container.
 *
 * @author Robin Chalas <robin.chalas@gmail.com>
 */
class ContainerCommandLoader implements CommandLoaderInterface
{
    private $container;
    private $commandMap;

    /**
     * @param ContainerInterface $container  A container from which to load command services
     * @param array              $commandMap An array with command names as keys and service ids as values
     */
    public function __construct(ContainerInterface $container, array $commandMap)
    {
        $this->container = $container;
        $this->commandMap = $commandMap;
    }

    /**
     * {@inheritdoc}
     */
    public function get($name)
    {
        if (!$this->has($name)) {
            throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
        }

        return $this->container->get($this->commandMap[$name]);
    }

    /**
     * {@inheritdoc}
     */
    public function has($name)
    {
        return isset($this->commandMap[$name]) && $this->container->has($this->commandMap[$name]);
    }

    /**
     * {@inheritdoc}
     */
    public function getNames()
    {
        return array_keys($this->commandMap);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\CommandLoader;

use Symfony\Component\Console\Exception\CommandNotFoundException;

/**
 * A simple command loader using factories to instantiate commands lazily.
 *
 * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
 */
class FactoryCommandLoader implements CommandLoaderInterface
{
    private $factories;

    /**
     * @param callable[] $factories Indexed by command names
     */
    public function __construct(array $factories)
    {
        $this->factories = $factories;
    }

    /**
     * {@inheritdoc}
     */
    public function has($name)
    {
        return isset($this->factories[$name]);
    }

    /**
     * {@inheritdoc}
     */
    public function get($name)
    {
        if (!isset($this->factories[$name])) {
            throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
        }

        $factory = $this->factories[$name];

        return $factory();
    }

    /**
     * {@inheritdoc}
     */
    public function getNames()
    {
        return array_keys($this->factories);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;

/**
 * Text descriptor.
 *
 * @author Jean-FranÃ§ois Simon <contact@jfsimon.fr>
 *
 * @internal
 */
class TextDescriptor extends Descriptor
{
    /**
     * {@inheritdoc}
     */
    protected function describeInputArgument(InputArgument $argument, array $options = [])
    {
        if (null !== $argument->getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) {
            $default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($argument->getDefault()));
        } else {
            $default = '';
        }

        $totalWidth = isset($options['total_width']) ? $options['total_width'] : Helper::strlen($argument->getName());
        $spacingWidth = $totalWidth - \strlen($argument->getName());

        $this->writeText(sprintf('  <info>%s</info>  %s%s%s',
            $argument->getName(),
            str_repeat(' ', $spacingWidth),
            // + 4 = 2 spaces before <info>, 2 spaces after </info>
            preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $argument->getDescription()),
            $default
        ), $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputOption(InputOption $option, array $options = [])
    {
        if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) {
            $default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($option->getDefault()));
        } else {
            $default = '';
        }

        $value = '';
        if ($option->acceptValue()) {
            $value = '='.strtoupper($option->getName());

            if ($option->isValueOptional()) {
                $value = '['.$value.']';
            }
        }

        $totalWidth = isset($options['total_width']) ? $options['total_width'] : $this->calculateTotalWidthForOptions([$option]);
        $synopsis = sprintf('%s%s',
            $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : '    ',
            sprintf('--%s%s', $option->getName(), $value)
        );

        $spacingWidth = $totalWidth - Helper::strlen($synopsis);

        $this->writeText(sprintf('  <info>%s</info>  %s%s%s%s',
            $synopsis,
            str_repeat(' ', $spacingWidth),
            // + 4 = 2 spaces before <info>, 2 spaces after </info>
            preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $option->getDescription()),
            $default,
            $option->isArray() ? '<comment> (multiple values allowed)</comment>' : ''
        ), $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputDefinition(InputDefinition $definition, array $options = [])
    {
        $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions());
        foreach ($definition->getArguments() as $argument) {
            $totalWidth = max($totalWidth, Helper::strlen($argument->getName()));
        }

        if ($definition->getArguments()) {
            $this->writeText('<comment>Arguments:</comment>', $options);
            $this->writeText("\n");
            foreach ($definition->getArguments() as $argument) {
                $this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth]));
                $this->writeText("\n");
            }
        }

        if ($definition->getArguments() && $definition->getOptions()) {
            $this->writeText("\n");
        }

        if ($definition->getOptions()) {
            $laterOptions = [];

            $this->writeText('<comment>Options:</comment>', $options);
            foreach ($definition->getOptions() as $option) {
                if (\strlen($option->getShortcut()) > 1) {
                    $laterOptions[] = $option;
                    continue;
                }
                $this->writeText("\n");
                $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
            }
            foreach ($laterOptions as $option) {
                $this->writeText("\n");
                $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function describeCommand(Command $command, array $options = [])
    {
        $command->getSynopsis(true);
        $command->getSynopsis(false);
        $command->mergeApplicationDefinition(false);

        $this->writeText('<comment>Usage:</comment>', $options);
        foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) {
            $this->writeText("\n");
            $this->writeText('  '.OutputFormatter::escape($usage), $options);
        }
        $this->writeText("\n");

        $definition = $command->getNativeDefinition();
        if ($definition->getOptions() || $definition->getArguments()) {
            $this->writeText("\n");
            $this->describeInputDefinition($definition, $options);
            $this->writeText("\n");
        }

        if ($help = $command->getProcessedHelp()) {
            $this->writeText("\n");
            $this->writeText('<comment>Help:</comment>', $options);
            $this->writeText("\n");
            $this->writeText('  '.str_replace("\n", "\n  ", $help), $options);
            $this->writeText("\n");
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function describeApplication(Application $application, array $options = [])
    {
        $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
        $description = new ApplicationDescription($application, $describedNamespace);

        if (isset($options['raw_text']) && $options['raw_text']) {
            $width = $this->getColumnWidth($description->getCommands());

            foreach ($description->getCommands() as $command) {
                $this->writeText(sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options);
                $this->writeText("\n");
            }
        } else {
            if ('' != $help = $application->getHelp()) {
                $this->writeText("$help\n\n", $options);
            }

            $this->writeText("<comment>Usage:</comment>\n", $options);
            $this->writeText("  command [options] [arguments]\n\n", $options);

            $this->describeInputDefinition(new InputDefinition($application->getDefinition()->getOptions()), $options);

            $this->writeText("\n");
            $this->writeText("\n");

            $commands = $description->getCommands();
            $namespaces = $description->getNamespaces();
            if ($describedNamespace && $namespaces) {
                // make sure all alias commands are included when describing a specific namespace
                $describedNamespaceInfo = reset($namespaces);
                foreach ($describedNamespaceInfo['commands'] as $name) {
                    $commands[$name] = $description->getCommand($name);
                }
            }

            // calculate max. width based on available commands per namespace
            $width = $this->getColumnWidth(\call_user_func_array('array_merge', array_map(function ($namespace) use ($commands) {
                return array_intersect($namespace['commands'], array_keys($commands));
            }, $namespaces)));

            if ($describedNamespace) {
                $this->writeText(sprintf('<comment>Available commands for the "%s" namespace:</comment>', $describedNamespace), $options);
            } else {
                $this->writeText('<comment>Available commands:</comment>', $options);
            }

            foreach ($namespaces as $namespace) {
                $namespace['commands'] = array_filter($namespace['commands'], function ($name) use ($commands) {
                    return isset($commands[$name]);
                });

                if (!$namespace['commands']) {
                    continue;
                }

                if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
                    $this->writeText("\n");
                    $this->writeText(' <comment>'.$namespace['id'].'</comment>', $options);
                }

                foreach ($namespace['commands'] as $name) {
                    $this->writeText("\n");
                    $spacingWidth = $width - Helper::strlen($name);
                    $command = $commands[$name];
                    $commandAliases = $name === $command->getName() ? $this->getCommandAliasesText($command) : '';
                    $this->writeText(sprintf('  <info>%s</info>%s%s', $name, str_repeat(' ', $spacingWidth), $commandAliases.$command->getDescription()), $options);
                }
            }

            $this->writeText("\n");
        }
    }

    /**
     * {@inheritdoc}
     */
    private function writeText($content, array $options = [])
    {
        $this->write(
            isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content,
            isset($options['raw_output']) ? !$options['raw_output'] : true
        );
    }

    /**
     * Formats command aliases to show them in the command description.
     *
     * @return string
     */
    private function getCommandAliasesText(Command $command)
    {
        $text = '';
        $aliases = $command->getAliases();

        if ($aliases) {
            $text = '['.implode('|', $aliases).'] ';
        }

        return $text;
    }

    /**
     * Formats input option/argument default value.
     *
     * @param mixed $default
     *
     * @return string
     */
    private function formatDefaultValue($default)
    {
        if (INF === $default) {
            return 'INF';
        }

        if (\is_string($default)) {
            $default = OutputFormatter::escape($default);
        } elseif (\is_array($default)) {
            foreach ($default as $key => $value) {
                if (\is_string($value)) {
                    $default[$key] = OutputFormatter::escape($value);
                }
            }
        }

        return str_replace('\\\\', '\\', json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
    }

    /**
     * @param (Command|string)[] $commands
     *
     * @return int
     */
    private function getColumnWidth(array $commands)
    {
        $widths = [];

        foreach ($commands as $command) {
            if ($command instanceof Command) {
                $widths[] = Helper::strlen($command->getName());
                foreach ($command->getAliases() as $alias) {
                    $widths[] = Helper::strlen($alias);
                }
            } else {
                $widths[] = Helper::strlen($command);
            }
        }

        return $widths ? max($widths) + 2 : 0;
    }

    /**
     * @param InputOption[] $options
     *
     * @return int
     */
    private function calculateTotalWidthForOptions(array $options)
    {
        $totalWidth = 0;
        foreach ($options as $option) {
            // "-" + shortcut + ", --" + name
            $nameLength = 1 + max(Helper::strlen($option->getShortcut()), 1) + 4 + Helper::strlen($option->getName());

            if ($option->acceptValue()) {
                $valueLength = 1 + Helper::strlen($option->getName()); // = + value
                $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ]

                $nameLength += $valueLength;
            }
            $totalWidth = max($totalWidth, $nameLength);
        }

        return $totalWidth;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\CommandNotFoundException;

/**
 * @author Jean-FranÃ§ois Simon <jeanfrancois.simon@sensiolabs.com>
 *
 * @internal
 */
class ApplicationDescription
{
    const GLOBAL_NAMESPACE = '_global';

    private $application;
    private $namespace;
    private $showHidden;

    /**
     * @var array
     */
    private $namespaces;

    /**
     * @var Command[]
     */
    private $commands;

    /**
     * @var Command[]
     */
    private $aliases;

    /**
     * @param Application $application
     * @param string|null $namespace
     * @param bool        $showHidden
     */
    public function __construct(Application $application, $namespace = null, $showHidden = false)
    {
        $this->application = $application;
        $this->namespace = $namespace;
        $this->showHidden = $showHidden;
    }

    /**
     * @return array
     */
    public function getNamespaces()
    {
        if (null === $this->namespaces) {
            $this->inspectApplication();
        }

        return $this->namespaces;
    }

    /**
     * @return Command[]
     */
    public function getCommands()
    {
        if (null === $this->commands) {
            $this->inspectApplication();
        }

        return $this->commands;
    }

    /**
     * @param string $name
     *
     * @return Command
     *
     * @throws CommandNotFoundException
     */
    public function getCommand($name)
    {
        if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) {
            throw new CommandNotFoundException(sprintf('Command %s does not exist.', $name));
        }

        return isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name];
    }

    private function inspectApplication()
    {
        $this->commands = [];
        $this->namespaces = [];

        $all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null);
        foreach ($this->sortCommands($all) as $namespace => $commands) {
            $names = [];

            /** @var Command $command */
            foreach ($commands as $name => $command) {
                if (!$command->getName() || (!$this->showHidden && $command->isHidden())) {
                    continue;
                }

                if ($command->getName() === $name) {
                    $this->commands[$name] = $command;
                } else {
                    $this->aliases[$name] = $command;
                }

                $names[] = $name;
            }

            $this->namespaces[$namespace] = ['id' => $namespace, 'commands' => $names];
        }
    }

    /**
     * @return array
     */
    private function sortCommands(array $commands)
    {
        $namespacedCommands = [];
        $globalCommands = [];
        foreach ($commands as $name => $command) {
            $key = $this->application->extractNamespace($name, 1);
            if (!$key) {
                $globalCommands['_global'][$name] = $command;
            } else {
                $namespacedCommands[$key][$name] = $command;
            }
        }
        ksort($namespacedCommands);
        $namespacedCommands = array_merge($globalCommands, $namespacedCommands);

        foreach ($namespacedCommands as &$commandsSet) {
            ksort($commandsSet);
        }
        // unset reference to keep scope clear
        unset($commandsSet);

        return $namespacedCommands;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;

/**
 * XML descriptor.
 *
 * @author Jean-FranÃ§ois Simon <contact@jfsimon.fr>
 *
 * @internal
 */
class XmlDescriptor extends Descriptor
{
    /**
     * @return \DOMDocument
     */
    public function getInputDefinitionDocument(InputDefinition $definition)
    {
        $dom = new \DOMDocument('1.0', 'UTF-8');
        $dom->appendChild($definitionXML = $dom->createElement('definition'));

        $definitionXML->appendChild($argumentsXML = $dom->createElement('arguments'));
        foreach ($definition->getArguments() as $argument) {
            $this->appendDocument($argumentsXML, $this->getInputArgumentDocument($argument));
        }

        $definitionXML->appendChild($optionsXML = $dom->createElement('options'));
        foreach ($definition->getOptions() as $option) {
            $this->appendDocument($optionsXML, $this->getInputOptionDocument($option));
        }

        return $dom;
    }

    /**
     * @return \DOMDocument
     */
    public function getCommandDocument(Command $command)
    {
        $dom = new \DOMDocument('1.0', 'UTF-8');
        $dom->appendChild($commandXML = $dom->createElement('command'));

        $command->getSynopsis();
        $command->mergeApplicationDefinition(false);

        $commandXML->setAttribute('id', $command->getName());
        $commandXML->setAttribute('name', $command->getName());
        $commandXML->setAttribute('hidden', $command->isHidden() ? 1 : 0);

        $commandXML->appendChild($usagesXML = $dom->createElement('usages'));

        foreach (array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()) as $usage) {
            $usagesXML->appendChild($dom->createElement('usage', $usage));
        }

        $commandXML->appendChild($descriptionXML = $dom->createElement('description'));
        $descriptionXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getDescription())));

        $commandXML->appendChild($helpXML = $dom->createElement('help'));
        $helpXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getProcessedHelp())));

        $definitionXML = $this->getInputDefinitionDocument($command->getNativeDefinition());
        $this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0));

        return $dom;
    }

    /**
     * @param Application $application
     * @param string|null $namespace
     *
     * @return \DOMDocument
     */
    public function getApplicationDocument(Application $application, $namespace = null)
    {
        $dom = new \DOMDocument('1.0', 'UTF-8');
        $dom->appendChild($rootXml = $dom->createElement('symfony'));

        if ('UNKNOWN' !== $application->getName()) {
            $rootXml->setAttribute('name', $application->getName());
            if ('UNKNOWN' !== $application->getVersion()) {
                $rootXml->setAttribute('version', $application->getVersion());
            }
        }

        $rootXml->appendChild($commandsXML = $dom->createElement('commands'));

        $description = new ApplicationDescription($application, $namespace, true);

        if ($namespace) {
            $commandsXML->setAttribute('namespace', $namespace);
        }

        foreach ($description->getCommands() as $command) {
            $this->appendDocument($commandsXML, $this->getCommandDocument($command));
        }

        if (!$namespace) {
            $rootXml->appendChild($namespacesXML = $dom->createElement('namespaces'));

            foreach ($description->getNamespaces() as $namespaceDescription) {
                $namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace'));
                $namespaceArrayXML->setAttribute('id', $namespaceDescription['id']);

                foreach ($namespaceDescription['commands'] as $name) {
                    $namespaceArrayXML->appendChild($commandXML = $dom->createElement('command'));
                    $commandXML->appendChild($dom->createTextNode($name));
                }
            }
        }

        return $dom;
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputArgument(InputArgument $argument, array $options = [])
    {
        $this->writeDocument($this->getInputArgumentDocument($argument));
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputOption(InputOption $option, array $options = [])
    {
        $this->writeDocument($this->getInputOptionDocument($option));
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputDefinition(InputDefinition $definition, array $options = [])
    {
        $this->writeDocument($this->getInputDefinitionDocument($definition));
    }

    /**
     * {@inheritdoc}
     */
    protected function describeCommand(Command $command, array $options = [])
    {
        $this->writeDocument($this->getCommandDocument($command));
    }

    /**
     * {@inheritdoc}
     */
    protected function describeApplication(Application $application, array $options = [])
    {
        $this->writeDocument($this->getApplicationDocument($application, isset($options['namespace']) ? $options['namespace'] : null));
    }

    /**
     * Appends document children to parent node.
     */
    private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent)
    {
        foreach ($importedParent->childNodes as $childNode) {
            $parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true));
        }
    }

    /**
     * Writes DOM document.
     *
     * @return \DOMDocument|string
     */
    private function writeDocument(\DOMDocument $dom)
    {
        $dom->formatOutput = true;
        $this->write($dom->saveXML());
    }

    /**
     * @return \DOMDocument
     */
    private function getInputArgumentDocument(InputArgument $argument)
    {
        $dom = new \DOMDocument('1.0', 'UTF-8');

        $dom->appendChild($objectXML = $dom->createElement('argument'));
        $objectXML->setAttribute('name', $argument->getName());
        $objectXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0);
        $objectXML->setAttribute('is_array', $argument->isArray() ? 1 : 0);
        $objectXML->appendChild($descriptionXML = $dom->createElement('description'));
        $descriptionXML->appendChild($dom->createTextNode($argument->getDescription()));

        $objectXML->appendChild($defaultsXML = $dom->createElement('defaults'));
        $defaults = \is_array($argument->getDefault()) ? $argument->getDefault() : (\is_bool($argument->getDefault()) ? [var_export($argument->getDefault(), true)] : ($argument->getDefault() ? [$argument->getDefault()] : []));
        foreach ($defaults as $default) {
            $defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
            $defaultXML->appendChild($dom->createTextNode($default));
        }

        return $dom;
    }

    /**
     * @return \DOMDocument
     */
    private function getInputOptionDocument(InputOption $option)
    {
        $dom = new \DOMDocument('1.0', 'UTF-8');

        $dom->appendChild($objectXML = $dom->createElement('option'));
        $objectXML->setAttribute('name', '--'.$option->getName());
        $pos = strpos($option->getShortcut(), '|');
        if (false !== $pos) {
            $objectXML->setAttribute('shortcut', '-'.substr($option->getShortcut(), 0, $pos));
            $objectXML->setAttribute('shortcuts', '-'.str_replace('|', '|-', $option->getShortcut()));
        } else {
            $objectXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : '');
        }
        $objectXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0);
        $objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0);
        $objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0);
        $objectXML->appendChild($descriptionXML = $dom->createElement('description'));
        $descriptionXML->appendChild($dom->createTextNode($option->getDescription()));

        if ($option->acceptValue()) {
            $defaults = \is_array($option->getDefault()) ? $option->getDefault() : (\is_bool($option->getDefault()) ? [var_export($option->getDefault(), true)] : ($option->getDefault() ? [$option->getDefault()] : []));
            $objectXML->appendChild($defaultsXML = $dom->createElement('defaults'));

            if (!empty($defaults)) {
                foreach ($defaults as $default) {
                    $defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
                    $defaultXML->appendChild($dom->createTextNode($default));
                }
            }
        }

        return $dom;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Output\OutputInterface;

/**
 * Descriptor interface.
 *
 * @author Jean-FranÃ§ois Simon <contact@jfsimon.fr>
 */
interface DescriptorInterface
{
    /**
     * Describes an object if supported.
     *
     * @param OutputInterface $output
     * @param object          $object
     * @param array           $options
     */
    public function describe(OutputInterface $output, $object, array $options = []);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jean-FranÃ§ois Simon <jeanfrancois.simon@sensiolabs.com>
 *
 * @internal
 */
abstract class Descriptor implements DescriptorInterface
{
    /**
     * @var OutputInterface
     */
    protected $output;

    /**
     * {@inheritdoc}
     */
    public function describe(OutputInterface $output, $object, array $options = [])
    {
        $this->output = $output;

        switch (true) {
            case $object instanceof InputArgument:
                $this->describeInputArgument($object, $options);
                break;
            case $object instanceof InputOption:
                $this->describeInputOption($object, $options);
                break;
            case $object instanceof InputDefinition:
                $this->describeInputDefinition($object, $options);
                break;
            case $object instanceof Command:
                $this->describeCommand($object, $options);
                break;
            case $object instanceof Application:
                $this->describeApplication($object, $options);
                break;
            default:
                throw new InvalidArgumentException(sprintf('Object of type "%s" is not describable.', \get_class($object)));
        }
    }

    /**
     * Writes content to output.
     *
     * @param string $content
     * @param bool   $decorated
     */
    protected function write($content, $decorated = false)
    {
        $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW);
    }

    /**
     * Describes an InputArgument instance.
     *
     * @return string|mixed
     */
    abstract protected function describeInputArgument(InputArgument $argument, array $options = []);

    /**
     * Describes an InputOption instance.
     *
     * @return string|mixed
     */
    abstract protected function describeInputOption(InputOption $option, array $options = []);

    /**
     * Describes an InputDefinition instance.
     *
     * @return string|mixed
     */
    abstract protected function describeInputDefinition(InputDefinition $definition, array $options = []);

    /**
     * Describes a Command instance.
     *
     * @return string|mixed
     */
    abstract protected function describeCommand(Command $command, array $options = []);

    /**
     * Describes an Application instance.
     *
     * @return string|mixed
     */
    abstract protected function describeApplication(Application $application, array $options = []);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;

/**
 * JSON descriptor.
 *
 * @author Jean-FranÃ§ois Simon <contact@jfsimon.fr>
 *
 * @internal
 */
class JsonDescriptor extends Descriptor
{
    /**
     * {@inheritdoc}
     */
    protected function describeInputArgument(InputArgument $argument, array $options = [])
    {
        $this->writeData($this->getInputArgumentData($argument), $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputOption(InputOption $option, array $options = [])
    {
        $this->writeData($this->getInputOptionData($option), $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputDefinition(InputDefinition $definition, array $options = [])
    {
        $this->writeData($this->getInputDefinitionData($definition), $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeCommand(Command $command, array $options = [])
    {
        $this->writeData($this->getCommandData($command), $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeApplication(Application $application, array $options = [])
    {
        $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
        $description = new ApplicationDescription($application, $describedNamespace, true);
        $commands = [];

        foreach ($description->getCommands() as $command) {
            $commands[] = $this->getCommandData($command);
        }

        $data = [];
        if ('UNKNOWN' !== $application->getName()) {
            $data['application']['name'] = $application->getName();
            if ('UNKNOWN' !== $application->getVersion()) {
                $data['application']['version'] = $application->getVersion();
            }
        }

        $data['commands'] = $commands;

        if ($describedNamespace) {
            $data['namespace'] = $describedNamespace;
        } else {
            $data['namespaces'] = array_values($description->getNamespaces());
        }

        $this->writeData($data, $options);
    }

    /**
     * Writes data as json.
     *
     * @return array|string
     */
    private function writeData(array $data, array $options)
    {
        $this->write(json_encode($data, isset($options['json_encoding']) ? $options['json_encoding'] : 0));
    }

    /**
     * @return array
     */
    private function getInputArgumentData(InputArgument $argument)
    {
        return [
            'name' => $argument->getName(),
            'is_required' => $argument->isRequired(),
            'is_array' => $argument->isArray(),
            'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $argument->getDescription()),
            'default' => INF === $argument->getDefault() ? 'INF' : $argument->getDefault(),
        ];
    }

    /**
     * @return array
     */
    private function getInputOptionData(InputOption $option)
    {
        return [
            'name' => '--'.$option->getName(),
            'shortcut' => $option->getShortcut() ? '-'.str_replace('|', '|-', $option->getShortcut()) : '',
            'accept_value' => $option->acceptValue(),
            'is_value_required' => $option->isValueRequired(),
            'is_multiple' => $option->isArray(),
            'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()),
            'default' => INF === $option->getDefault() ? 'INF' : $option->getDefault(),
        ];
    }

    /**
     * @return array
     */
    private function getInputDefinitionData(InputDefinition $definition)
    {
        $inputArguments = [];
        foreach ($definition->getArguments() as $name => $argument) {
            $inputArguments[$name] = $this->getInputArgumentData($argument);
        }

        $inputOptions = [];
        foreach ($definition->getOptions() as $name => $option) {
            $inputOptions[$name] = $this->getInputOptionData($option);
        }

        return ['arguments' => $inputArguments, 'options' => $inputOptions];
    }

    /**
     * @return array
     */
    private function getCommandData(Command $command)
    {
        $command->getSynopsis();
        $command->mergeApplicationDefinition(false);

        return [
            'name' => $command->getName(),
            'usage' => array_merge([$command->getSynopsis()], $command->getUsages(), $command->getAliases()),
            'description' => $command->getDescription(),
            'help' => $command->getProcessedHelp(),
            'definition' => $this->getInputDefinitionData($command->getNativeDefinition()),
            'hidden' => $command->isHidden(),
        ];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Markdown descriptor.
 *
 * @author Jean-FranÃ§ois Simon <contact@jfsimon.fr>
 *
 * @internal
 */
class MarkdownDescriptor extends Descriptor
{
    /**
     * {@inheritdoc}
     */
    public function describe(OutputInterface $output, $object, array $options = [])
    {
        $decorated = $output->isDecorated();
        $output->setDecorated(false);

        parent::describe($output, $object, $options);

        $output->setDecorated($decorated);
    }

    /**
     * {@inheritdoc}
     */
    protected function write($content, $decorated = true)
    {
        parent::write($content, $decorated);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputArgument(InputArgument $argument, array $options = [])
    {
        $this->write(
            '#### `'.($argument->getName() ?: '<none>')."`\n\n"
            .($argument->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $argument->getDescription())."\n\n" : '')
            .'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n"
            .'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n"
            .'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`'
        );
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputOption(InputOption $option, array $options = [])
    {
        $name = '--'.$option->getName();
        if ($option->getShortcut()) {
            $name .= '|-'.str_replace('|', '|-', $option->getShortcut()).'';
        }

        $this->write(
            '#### `'.$name.'`'."\n\n"
            .($option->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $option->getDescription())."\n\n" : '')
            .'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n"
            .'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n"
            .'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n"
            .'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`'
        );
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputDefinition(InputDefinition $definition, array $options = [])
    {
        if ($showArguments = \count($definition->getArguments()) > 0) {
            $this->write('### Arguments');
            foreach ($definition->getArguments() as $argument) {
                $this->write("\n\n");
                $this->write($this->describeInputArgument($argument));
            }
        }

        if (\count($definition->getOptions()) > 0) {
            if ($showArguments) {
                $this->write("\n\n");
            }

            $this->write('### Options');
            foreach ($definition->getOptions() as $option) {
                $this->write("\n\n");
                $this->write($this->describeInputOption($option));
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function describeCommand(Command $command, array $options = [])
    {
        $command->getSynopsis();
        $command->mergeApplicationDefinition(false);

        $this->write(
            '`'.$command->getName()."`\n"
            .str_repeat('-', Helper::strlen($command->getName()) + 2)."\n\n"
            .($command->getDescription() ? $command->getDescription()."\n\n" : '')
            .'### Usage'."\n\n"
            .array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), function ($carry, $usage) {
                return $carry.'* `'.$usage.'`'."\n";
            })
        );

        if ($help = $command->getProcessedHelp()) {
            $this->write("\n");
            $this->write($help);
        }

        if ($command->getNativeDefinition()) {
            $this->write("\n\n");
            $this->describeInputDefinition($command->getNativeDefinition());
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function describeApplication(Application $application, array $options = [])
    {
        $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
        $description = new ApplicationDescription($application, $describedNamespace);
        $title = $this->getApplicationTitle($application);

        $this->write($title."\n".str_repeat('=', Helper::strlen($title)));

        foreach ($description->getNamespaces() as $namespace) {
            if (ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
                $this->write("\n\n");
                $this->write('**'.$namespace['id'].':**');
            }

            $this->write("\n\n");
            $this->write(implode("\n", array_map(function ($commandName) use ($description) {
                return sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName()));
            }, $namespace['commands'])));
        }

        foreach ($description->getCommands() as $command) {
            $this->write("\n\n");
            $this->write($this->describeCommand($command));
        }
    }

    private function getApplicationTitle(Application $application)
    {
        if ('UNKNOWN' !== $application->getName()) {
            if ('UNKNOWN' !== $application->getVersion()) {
                return sprintf('%s %s', $application->getName(), $application->getVersion());
            }

            return $application->getName();
        }

        return 'Console Tool';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Tester;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\StreamOutput;

/**
 * Eases the testing of console commands.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Robin Chalas <robin.chalas@gmail.com>
 */
class CommandTester
{
    private $command;
    private $input;
    private $output;
    private $inputs = [];
    private $statusCode;

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

    /**
     * Executes the command.
     *
     * Available execution options:
     *
     *  * interactive: Sets the input interactive flag
     *  * decorated:   Sets the output decorated flag
     *  * verbosity:   Sets the output verbosity flag
     *
     * @param array $input   An array of command arguments and options
     * @param array $options An array of execution options
     *
     * @return int The command exit code
     */
    public function execute(array $input, array $options = [])
    {
        // set the command name automatically if the application requires
        // this argument and no command name was passed
        if (!isset($input['command'])
            && (null !== $application = $this->command->getApplication())
            && $application->getDefinition()->hasArgument('command')
        ) {
            $input = array_merge(['command' => $this->command->getName()], $input);
        }

        $this->input = new ArrayInput($input);
        // Use an in-memory input stream even if no inputs are set so that QuestionHelper::ask() does not rely on the blocking STDIN.
        $this->input->setStream(self::createStream($this->inputs));

        if (isset($options['interactive'])) {
            $this->input->setInteractive($options['interactive']);
        }

        $this->output = new StreamOutput(fopen('php://memory', 'w', false));
        $this->output->setDecorated(isset($options['decorated']) ? $options['decorated'] : false);
        if (isset($options['verbosity'])) {
            $this->output->setVerbosity($options['verbosity']);
        }

        return $this->statusCode = $this->command->run($this->input, $this->output);
    }

    /**
     * Gets the display returned by the last execution of the command.
     *
     * @param bool $normalize Whether to normalize end of lines to \n or not
     *
     * @return string The display
     */
    public function getDisplay($normalize = false)
    {
        if (null === $this->output) {
            throw new \RuntimeException('Output not initialized, did you execute the command before requesting the display?');
        }

        rewind($this->output->getStream());

        $display = stream_get_contents($this->output->getStream());

        if ($normalize) {
            $display = str_replace(PHP_EOL, "\n", $display);
        }

        return $display;
    }

    /**
     * Gets the input instance used by the last execution of the command.
     *
     * @return InputInterface The current input instance
     */
    public function getInput()
    {
        return $this->input;
    }

    /**
     * Gets the output instance used by the last execution of the command.
     *
     * @return OutputInterface The current output instance
     */
    public function getOutput()
    {
        return $this->output;
    }

    /**
     * Gets the status code returned by the last execution of the application.
     *
     * @return int The status code
     */
    public function getStatusCode()
    {
        return $this->statusCode;
    }

    /**
     * Sets the user inputs.
     *
     * @param array $inputs An array of strings representing each input
     *                      passed to the command input stream
     *
     * @return CommandTester
     */
    public function setInputs(array $inputs)
    {
        $this->inputs = $inputs;

        return $this;
    }

    private static function createStream(array $inputs)
    {
        $stream = fopen('php://memory', 'r+', false);

        foreach ($inputs as $input) {
            fwrite($stream, $input.PHP_EOL);
        }

        rewind($stream);

        return $stream;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Tester;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\StreamOutput;

/**
 * Eases the testing of console applications.
 *
 * When testing an application, don't forget to disable the auto exit flag:
 *
 *     $application = new Application();
 *     $application->setAutoExit(false);
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ApplicationTester
{
    private $application;
    private $input;
    private $statusCode;
    /**
     * @var OutputInterface
     */
    private $output;
    private $captureStreamsIndependently = false;

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

    /**
     * Executes the application.
     *
     * Available options:
     *
     *  * interactive:               Sets the input interactive flag
     *  * decorated:                 Sets the output decorated flag
     *  * verbosity:                 Sets the output verbosity flag
     *  * capture_stderr_separately: Make output of stdOut and stdErr separately available
     *
     * @param array $input   An array of arguments and options
     * @param array $options An array of options
     *
     * @return int The command exit code
     */
    public function run(array $input, $options = [])
    {
        $this->input = new ArrayInput($input);
        if (isset($options['interactive'])) {
            $this->input->setInteractive($options['interactive']);
        }

        $this->captureStreamsIndependently = \array_key_exists('capture_stderr_separately', $options) && $options['capture_stderr_separately'];
        if (!$this->captureStreamsIndependently) {
            $this->output = new StreamOutput(fopen('php://memory', 'w', false));
            if (isset($options['decorated'])) {
                $this->output->setDecorated($options['decorated']);
            }
            if (isset($options['verbosity'])) {
                $this->output->setVerbosity($options['verbosity']);
            }
        } else {
            $this->output = new ConsoleOutput(
                isset($options['verbosity']) ? $options['verbosity'] : ConsoleOutput::VERBOSITY_NORMAL,
                isset($options['decorated']) ? $options['decorated'] : null
            );

            $errorOutput = new StreamOutput(fopen('php://memory', 'w', false));
            $errorOutput->setFormatter($this->output->getFormatter());
            $errorOutput->setVerbosity($this->output->getVerbosity());
            $errorOutput->setDecorated($this->output->isDecorated());

            $reflectedOutput = new \ReflectionObject($this->output);
            $strErrProperty = $reflectedOutput->getProperty('stderr');
            $strErrProperty->setAccessible(true);
            $strErrProperty->setValue($this->output, $errorOutput);

            $reflectedParent = $reflectedOutput->getParentClass();
            $streamProperty = $reflectedParent->getProperty('stream');
            $streamProperty->setAccessible(true);
            $streamProperty->setValue($this->output, fopen('php://memory', 'w', false));
        }

        return $this->statusCode = $this->application->run($this->input, $this->output);
    }

    /**
     * Gets the display returned by the last execution of the application.
     *
     * @param bool $normalize Whether to normalize end of lines to \n or not
     *
     * @return string The display
     */
    public function getDisplay($normalize = false)
    {
        rewind($this->output->getStream());

        $display = stream_get_contents($this->output->getStream());

        if ($normalize) {
            $display = str_replace(PHP_EOL, "\n", $display);
        }

        return $display;
    }

    /**
     * Gets the output written to STDERR by the application.
     *
     * @param bool $normalize Whether to normalize end of lines to \n or not
     *
     * @return string
     */
    public function getErrorOutput($normalize = false)
    {
        if (!$this->captureStreamsIndependently) {
            throw new \LogicException('The error output is not available when the tester is run without "capture_stderr_separately" option set.');
        }

        rewind($this->output->getErrorOutput()->getStream());

        $display = stream_get_contents($this->output->getErrorOutput()->getStream());

        if ($normalize) {
            $display = str_replace(PHP_EOL, "\n", $display);
        }

        return $display;
    }

    /**
     * Gets the input instance used by the last execution of the application.
     *
     * @return InputInterface The current input instance
     */
    public function getInput()
    {
        return $this->input;
    }

    /**
     * Gets the output instance used by the last execution of the application.
     *
     * @return OutputInterface The current output instance
     */
    public function getOutput()
    {
        return $this->output;
    }

    /**
     * Gets the status code returned by the last execution of the application.
     *
     * @return int The status code
     */
    public function getStatusCode()
    {
        return $this->statusCode;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Lock\Factory;
use Symfony\Component\Lock\Lock;
use Symfony\Component\Lock\Store\FlockStore;
use Symfony\Component\Lock\Store\SemaphoreStore;

/**
 * Basic lock feature for commands.
 *
 * @author Geoffrey Brier <geoffrey.brier@gmail.com>
 */
trait LockableTrait
{
    /** @var Lock */
    private $lock;

    /**
     * Locks a command.
     *
     * @return bool
     */
    private function lock($name = null, $blocking = false)
    {
        if (!class_exists(SemaphoreStore::class)) {
            throw new RuntimeException('To enable the locking feature you must install the symfony/lock component.');
        }

        if (null !== $this->lock) {
            throw new LogicException('A lock is already in place.');
        }

        if (SemaphoreStore::isSupported($blocking)) {
            $store = new SemaphoreStore();
        } else {
            $store = new FlockStore();
        }

        $this->lock = (new Factory($store))->createLock($name ?: $this->getName());
        if (!$this->lock->acquire($blocking)) {
            $this->lock = null;

            return false;
        }

        return true;
    }

    /**
     * Releases the command lock if there is one.
     */
    private function release()
    {
        if ($this->lock) {
            $this->lock->release();
            $this->lock = null;
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * HelpCommand displays the help for a given command.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class HelpCommand extends Command
{
    private $command;

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this->ignoreValidationErrors();

        $this
            ->setName('help')
            ->setDefinition([
                new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'),
                new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
                new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'),
            ])
            ->setDescription('Displays help for a command')
            ->setHelp(<<<'EOF'
The <info>%command.name%</info> command displays help for a given command:

  <info>php %command.full_name% list</info>

You can also output the help in other formats by using the <comment>--format</comment> option:

  <info>php %command.full_name% --format=xml list</info>

To display the list of available commands, please use the <info>list</info> command.
EOF
            )
        ;
    }

    public function setCommand(Command $command)
    {
        $this->command = $command;
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        if (null === $this->command) {
            $this->command = $this->getApplication()->find($input->getArgument('command_name'));
        }

        $helper = new DescriptorHelper();
        $helper->describe($output, $this->command, [
            'format' => $input->getOption('format'),
            'raw_text' => $input->getOption('raw'),
        ]);

        $this->command = null;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * ListCommand displays the list of all available commands for the application.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ListCommand extends Command
{
    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this
            ->setName('list')
            ->setDefinition($this->createDefinition())
            ->setDescription('Lists commands')
            ->setHelp(<<<'EOF'
The <info>%command.name%</info> command lists all commands:

  <info>php %command.full_name%</info>

You can also display the commands for a specific namespace:

  <info>php %command.full_name% test</info>

You can also output the information in other formats by using the <comment>--format</comment> option:

  <info>php %command.full_name% --format=xml</info>

It's also possible to get raw list of commands (useful for embedding command runner):

  <info>php %command.full_name% --raw</info>
EOF
            )
        ;
    }

    /**
     * {@inheritdoc}
     */
    public function getNativeDefinition()
    {
        return $this->createDefinition();
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $helper = new DescriptorHelper();
        $helper->describe($output, $this->getApplication(), [
            'format' => $input->getOption('format'),
            'raw_text' => $input->getOption('raw'),
            'namespace' => $input->getArgument('namespace'),
        ]);
    }

    /**
     * {@inheritdoc}
     */
    private function createDefinition()
    {
        return new InputDefinition([
            new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'),
            new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'),
            new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
        ]);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Base class for all commands.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Command
{
    /**
     * @var string|null The default command name
     */
    protected static $defaultName;

    private $application;
    private $name;
    private $processTitle;
    private $aliases = [];
    private $definition;
    private $hidden = false;
    private $help;
    private $description;
    private $ignoreValidationErrors = false;
    private $applicationDefinitionMerged = false;
    private $applicationDefinitionMergedWithArgs = false;
    private $code;
    private $synopsis = [];
    private $usages = [];
    private $helperSet;

    /**
     * @return string|null The default command name or null when no default name is set
     */
    public static function getDefaultName()
    {
        $class = \get_called_class();
        $r = new \ReflectionProperty($class, 'defaultName');

        return $class === $r->class ? static::$defaultName : null;
    }

    /**
     * @param string|null $name The name of the command; passing null means it must be set in configure()
     *
     * @throws LogicException When the command name is empty
     */
    public function __construct($name = null)
    {
        $this->definition = new InputDefinition();

        if (null !== $name || null !== $name = static::getDefaultName()) {
            $this->setName($name);
        }

        $this->configure();
    }

    /**
     * Ignores validation errors.
     *
     * This is mainly useful for the help command.
     */
    public function ignoreValidationErrors()
    {
        $this->ignoreValidationErrors = true;
    }

    public function setApplication(Application $application = null)
    {
        $this->application = $application;
        if ($application) {
            $this->setHelperSet($application->getHelperSet());
        } else {
            $this->helperSet = null;
        }
    }

    public function setHelperSet(HelperSet $helperSet)
    {
        $this->helperSet = $helperSet;
    }

    /**
     * Gets the helper set.
     *
     * @return HelperSet A HelperSet instance
     */
    public function getHelperSet()
    {
        return $this->helperSet;
    }

    /**
     * Gets the application instance for this command.
     *
     * @return Application An Application instance
     */
    public function getApplication()
    {
        return $this->application;
    }

    /**
     * Checks whether the command is enabled or not in the current environment.
     *
     * Override this to check for x or y and return false if the command can not
     * run properly under the current conditions.
     *
     * @return bool
     */
    public function isEnabled()
    {
        return true;
    }

    /**
     * Configures the current command.
     */
    protected function configure()
    {
    }

    /**
     * Executes the current command.
     *
     * This method is not abstract because you can use this class
     * as a concrete class. In this case, instead of defining the
     * execute() method, you set the code to execute by passing
     * a Closure to the setCode() method.
     *
     * @return int|null null or 0 if everything went fine, or an error code
     *
     * @throws LogicException When this abstract method is not implemented
     *
     * @see setCode()
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        throw new LogicException('You must override the execute() method in the concrete command class.');
    }

    /**
     * Interacts with the user.
     *
     * This method is executed before the InputDefinition is validated.
     * This means that this is the only place where the command can
     * interactively ask for values of missing required arguments.
     */
    protected function interact(InputInterface $input, OutputInterface $output)
    {
    }

    /**
     * Initializes the command after the input has been bound and before the input
     * is validated.
     *
     * This is mainly useful when a lot of commands extends one main command
     * where some things need to be initialized based on the input arguments and options.
     *
     * @see InputInterface::bind()
     * @see InputInterface::validate()
     */
    protected function initialize(InputInterface $input, OutputInterface $output)
    {
    }

    /**
     * Runs the command.
     *
     * The code to execute is either defined directly with the
     * setCode() method or by overriding the execute() method
     * in a sub-class.
     *
     * @return int The command exit code
     *
     * @throws \Exception When binding input fails. Bypass this by calling {@link ignoreValidationErrors()}.
     *
     * @see setCode()
     * @see execute()
     */
    public function run(InputInterface $input, OutputInterface $output)
    {
        // force the creation of the synopsis before the merge with the app definition
        $this->getSynopsis(true);
        $this->getSynopsis(false);

        // add the application arguments and options
        $this->mergeApplicationDefinition();

        // bind the input against the command specific arguments/options
        try {
            $input->bind($this->definition);
        } catch (ExceptionInterface $e) {
            if (!$this->ignoreValidationErrors) {
                throw $e;
            }
        }

        $this->initialize($input, $output);

        if (null !== $this->processTitle) {
            if (\function_exists('cli_set_process_title')) {
                if (!@cli_set_process_title($this->processTitle)) {
                    if ('Darwin' === PHP_OS) {
                        $output->writeln('<comment>Running "cli_set_process_title" as an unprivileged user is not supported on MacOS.</comment>', OutputInterface::VERBOSITY_VERY_VERBOSE);
                    } else {
                        cli_set_process_title($this->processTitle);
                    }
                }
            } elseif (\function_exists('setproctitle')) {
                setproctitle($this->processTitle);
            } elseif (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) {
                $output->writeln('<comment>Install the proctitle PECL to be able to change the process title.</comment>');
            }
        }

        if ($input->isInteractive()) {
            $this->interact($input, $output);
        }

        // The command name argument is often omitted when a command is executed directly with its run() method.
        // It would fail the validation if we didn't make sure the command argument is present,
        // since it's required by the application.
        if ($input->hasArgument('command') && null === $input->getArgument('command')) {
            $input->setArgument('command', $this->getName());
        }

        $input->validate();

        if ($this->code) {
            $statusCode = \call_user_func($this->code, $input, $output);
        } else {
            $statusCode = $this->execute($input, $output);
        }

        return is_numeric($statusCode) ? (int) $statusCode : 0;
    }

    /**
     * Sets the code to execute when running this command.
     *
     * If this method is used, it overrides the code defined
     * in the execute() method.
     *
     * @param callable $code A callable(InputInterface $input, OutputInterface $output)
     *
     * @return $this
     *
     * @throws InvalidArgumentException
     *
     * @see execute()
     */
    public function setCode(callable $code)
    {
        if ($code instanceof \Closure) {
            $r = new \ReflectionFunction($code);
            if (null === $r->getClosureThis()) {
                if (\PHP_VERSION_ID < 70000) {
                    // Bug in PHP5: https://bugs.php.net/bug.php?id=64761
                    // This means that we cannot bind static closures and therefore we must
                    // ignore any errors here.  There is no way to test if the closure is
                    // bindable.
                    $code = @\Closure::bind($code, $this);
                } else {
                    $code = \Closure::bind($code, $this);
                }
            }
        }

        $this->code = $code;

        return $this;
    }

    /**
     * Merges the application definition with the command definition.
     *
     * This method is not part of public API and should not be used directly.
     *
     * @param bool $mergeArgs Whether to merge or not the Application definition arguments to Command definition arguments
     */
    public function mergeApplicationDefinition($mergeArgs = true)
    {
        if (null === $this->application || (true === $this->applicationDefinitionMerged && ($this->applicationDefinitionMergedWithArgs || !$mergeArgs))) {
            return;
        }

        $this->definition->addOptions($this->application->getDefinition()->getOptions());

        $this->applicationDefinitionMerged = true;

        if ($mergeArgs) {
            $currentArguments = $this->definition->getArguments();
            $this->definition->setArguments($this->application->getDefinition()->getArguments());
            $this->definition->addArguments($currentArguments);

            $this->applicationDefinitionMergedWithArgs = true;
        }
    }

    /**
     * Sets an array of argument and option instances.
     *
     * @param array|InputDefinition $definition An array of argument and option instances or a definition instance
     *
     * @return $this
     */
    public function setDefinition($definition)
    {
        if ($definition instanceof InputDefinition) {
            $this->definition = $definition;
        } else {
            $this->definition->setDefinition($definition);
        }

        $this->applicationDefinitionMerged = false;

        return $this;
    }

    /**
     * Gets the InputDefinition attached to this Command.
     *
     * @return InputDefinition An InputDefinition instance
     */
    public function getDefinition()
    {
        return $this->definition;
    }

    /**
     * Gets the InputDefinition to be used to create representations of this Command.
     *
     * Can be overridden to provide the original command representation when it would otherwise
     * be changed by merging with the application InputDefinition.
     *
     * This method is not part of public API and should not be used directly.
     *
     * @return InputDefinition An InputDefinition instance
     */
    public function getNativeDefinition()
    {
        return $this->getDefinition();
    }

    /**
     * Adds an argument.
     *
     * @param string               $name        The argument name
     * @param int|null             $mode        The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL
     * @param string               $description A description text
     * @param string|string[]|null $default     The default value (for InputArgument::OPTIONAL mode only)
     *
     * @throws InvalidArgumentException When argument mode is not valid
     *
     * @return $this
     */
    public function addArgument($name, $mode = null, $description = '', $default = null)
    {
        $this->definition->addArgument(new InputArgument($name, $mode, $description, $default));

        return $this;
    }

    /**
     * Adds an option.
     *
     * @param string                        $name        The option name
     * @param string|array|null             $shortcut    The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
     * @param int|null                      $mode        The option mode: One of the InputOption::VALUE_* constants
     * @param string                        $description A description text
     * @param string|string[]|int|bool|null $default     The default value (must be null for InputOption::VALUE_NONE)
     *
     * @throws InvalidArgumentException If option mode is invalid or incompatible
     *
     * @return $this
     */
    public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null)
    {
        $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default));

        return $this;
    }

    /**
     * Sets the name of the command.
     *
     * This method can set both the namespace and the name if
     * you separate them by a colon (:)
     *
     *     $command->setName('foo:bar');
     *
     * @param string $name The command name
     *
     * @return $this
     *
     * @throws InvalidArgumentException When the name is invalid
     */
    public function setName($name)
    {
        $this->validateName($name);

        $this->name = $name;

        return $this;
    }

    /**
     * Sets the process title of the command.
     *
     * This feature should be used only when creating a long process command,
     * like a daemon.
     *
     * PHP 5.5+ or the proctitle PECL library is required
     *
     * @param string $title The process title
     *
     * @return $this
     */
    public function setProcessTitle($title)
    {
        $this->processTitle = $title;

        return $this;
    }

    /**
     * Returns the command name.
     *
     * @return string The command name
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @param bool $hidden Whether or not the command should be hidden from the list of commands
     *
     * @return Command The current instance
     */
    public function setHidden($hidden)
    {
        $this->hidden = (bool) $hidden;

        return $this;
    }

    /**
     * @return bool whether the command should be publicly shown or not
     */
    public function isHidden()
    {
        return $this->hidden;
    }

    /**
     * Sets the description for the command.
     *
     * @param string $description The description for the command
     *
     * @return $this
     */
    public function setDescription($description)
    {
        $this->description = $description;

        return $this;
    }

    /**
     * Returns the description for the command.
     *
     * @return string The description for the command
     */
    public function getDescription()
    {
        return $this->description;
    }

    /**
     * Sets the help for the command.
     *
     * @param string $help The help for the command
     *
     * @return $this
     */
    public function setHelp($help)
    {
        $this->help = $help;

        return $this;
    }

    /**
     * Returns the help for the command.
     *
     * @return string The help for the command
     */
    public function getHelp()
    {
        return $this->help;
    }

    /**
     * Returns the processed help for the command replacing the %command.name% and
     * %command.full_name% patterns with the real values dynamically.
     *
     * @return string The processed help for the command
     */
    public function getProcessedHelp()
    {
        $name = $this->name;
        $isSingleCommand = $this->application && $this->application->isSingleCommand();

        $placeholders = [
            '%command.name%',
            '%command.full_name%',
        ];
        $replacements = [
            $name,
            $isSingleCommand ? $_SERVER['PHP_SELF'] : $_SERVER['PHP_SELF'].' '.$name,
        ];

        return str_replace($placeholders, $replacements, $this->getHelp() ?: $this->getDescription());
    }

    /**
     * Sets the aliases for the command.
     *
     * @param string[] $aliases An array of aliases for the command
     *
     * @return $this
     *
     * @throws InvalidArgumentException When an alias is invalid
     */
    public function setAliases($aliases)
    {
        if (!\is_array($aliases) && !$aliases instanceof \Traversable) {
            throw new InvalidArgumentException('$aliases must be an array or an instance of \Traversable');
        }

        foreach ($aliases as $alias) {
            $this->validateName($alias);
        }

        $this->aliases = $aliases;

        return $this;
    }

    /**
     * Returns the aliases for the command.
     *
     * @return array An array of aliases for the command
     */
    public function getAliases()
    {
        return $this->aliases;
    }

    /**
     * Returns the synopsis for the command.
     *
     * @param bool $short Whether to show the short version of the synopsis (with options folded) or not
     *
     * @return string The synopsis
     */
    public function getSynopsis($short = false)
    {
        $key = $short ? 'short' : 'long';

        if (!isset($this->synopsis[$key])) {
            $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short)));
        }

        return $this->synopsis[$key];
    }

    /**
     * Add a command usage example.
     *
     * @param string $usage The usage, it'll be prefixed with the command name
     *
     * @return $this
     */
    public function addUsage($usage)
    {
        if (0 !== strpos($usage, $this->name)) {
            $usage = sprintf('%s %s', $this->name, $usage);
        }

        $this->usages[] = $usage;

        return $this;
    }

    /**
     * Returns alternative usages of the command.
     *
     * @return array
     */
    public function getUsages()
    {
        return $this->usages;
    }

    /**
     * Gets a helper instance by name.
     *
     * @param string $name The helper name
     *
     * @return mixed The helper value
     *
     * @throws LogicException           if no HelperSet is defined
     * @throws InvalidArgumentException if the helper is not defined
     */
    public function getHelper($name)
    {
        if (null === $this->helperSet) {
            throw new LogicException(sprintf('Cannot retrieve helper "%s" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.', $name));
        }

        return $this->helperSet->get($name);
    }

    /**
     * Validates a command name.
     *
     * It must be non-empty and parts can optionally be separated by ":".
     *
     * @param string $name
     *
     * @throws InvalidArgumentException When the name is invalid
     */
    private function validateName($name)
    {
        if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) {
            throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name));
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;

/**
 * Defines the styles for a Table.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Ð¡Ð°ÑˆÐ° Ð¡Ñ‚Ð°Ð¼ÐµÐ½ÐºÐ¾Ð²Ð¸Ñ› <umpirsky@gmail.com>
 */
class TableStyle
{
    private $paddingChar = ' ';
    private $horizontalBorderChar = '-';
    private $verticalBorderChar = '|';
    private $crossingChar = '+';
    private $cellHeaderFormat = '<info>%s</info>';
    private $cellRowFormat = '%s';
    private $cellRowContentFormat = ' %s ';
    private $borderFormat = '%s';
    private $padType = STR_PAD_RIGHT;

    /**
     * Sets padding character, used for cell padding.
     *
     * @param string $paddingChar
     *
     * @return $this
     */
    public function setPaddingChar($paddingChar)
    {
        if (!$paddingChar) {
            throw new LogicException('The padding char must not be empty');
        }

        $this->paddingChar = $paddingChar;

        return $this;
    }

    /**
     * Gets padding character, used for cell padding.
     *
     * @return string
     */
    public function getPaddingChar()
    {
        return $this->paddingChar;
    }

    /**
     * Sets horizontal border character.
     *
     * @param string $horizontalBorderChar
     *
     * @return $this
     */
    public function setHorizontalBorderChar($horizontalBorderChar)
    {
        $this->horizontalBorderChar = $horizontalBorderChar;

        return $this;
    }

    /**
     * Gets horizontal border character.
     *
     * @return string
     */
    public function getHorizontalBorderChar()
    {
        return $this->horizontalBorderChar;
    }

    /**
     * Sets vertical border character.
     *
     * @param string $verticalBorderChar
     *
     * @return $this
     */
    public function setVerticalBorderChar($verticalBorderChar)
    {
        $this->verticalBorderChar = $verticalBorderChar;

        return $this;
    }

    /**
     * Gets vertical border character.
     *
     * @return string
     */
    public function getVerticalBorderChar()
    {
        return $this->verticalBorderChar;
    }

    /**
     * Sets crossing character.
     *
     * @param string $crossingChar
     *
     * @return $this
     */
    public function setCrossingChar($crossingChar)
    {
        $this->crossingChar = $crossingChar;

        return $this;
    }

    /**
     * Gets crossing character.
     *
     * @return string
     */
    public function getCrossingChar()
    {
        return $this->crossingChar;
    }

    /**
     * Sets header cell format.
     *
     * @param string $cellHeaderFormat
     *
     * @return $this
     */
    public function setCellHeaderFormat($cellHeaderFormat)
    {
        $this->cellHeaderFormat = $cellHeaderFormat;

        return $this;
    }

    /**
     * Gets header cell format.
     *
     * @return string
     */
    public function getCellHeaderFormat()
    {
        return $this->cellHeaderFormat;
    }

    /**
     * Sets row cell format.
     *
     * @param string $cellRowFormat
     *
     * @return $this
     */
    public function setCellRowFormat($cellRowFormat)
    {
        $this->cellRowFormat = $cellRowFormat;

        return $this;
    }

    /**
     * Gets row cell format.
     *
     * @return string
     */
    public function getCellRowFormat()
    {
        return $this->cellRowFormat;
    }

    /**
     * Sets row cell content format.
     *
     * @param string $cellRowContentFormat
     *
     * @return $this
     */
    public function setCellRowContentFormat($cellRowContentFormat)
    {
        $this->cellRowContentFormat = $cellRowContentFormat;

        return $this;
    }

    /**
     * Gets row cell content format.
     *
     * @return string
     */
    public function getCellRowContentFormat()
    {
        return $this->cellRowContentFormat;
    }

    /**
     * Sets table border format.
     *
     * @param string $borderFormat
     *
     * @return $this
     */
    public function setBorderFormat($borderFormat)
    {
        $this->borderFormat = $borderFormat;

        return $this;
    }

    /**
     * Gets table border format.
     *
     * @return string
     */
    public function getBorderFormat()
    {
        return $this->borderFormat;
    }

    /**
     * Sets cell padding type.
     *
     * @param int $padType STR_PAD_*
     *
     * @return $this
     */
    public function setPadType($padType)
    {
        if (!\in_array($padType, [STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH], true)) {
            throw new InvalidArgumentException('Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).');
        }

        $this->padType = $padType;

        return $this;
    }

    /**
     * Gets cell padding type.
     *
     * @return int
     */
    public function getPadType()
    {
        return $this->padType;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Style\SymfonyStyle;

/**
 * Symfony Style Guide compliant question helper.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
class SymfonyQuestionHelper extends QuestionHelper
{
    /**
     * {@inheritdoc}
     *
     * To be removed in 4.0
     */
    public function ask(InputInterface $input, OutputInterface $output, Question $question)
    {
        $validator = $question->getValidator();
        $question->setValidator(function ($value) use ($validator) {
            if (null !== $validator) {
                $value = $validator($value);
            } else {
                // make required
                if (!\is_array($value) && !\is_bool($value) && 0 === \strlen($value)) {
                    @trigger_error('The default question validator is deprecated since Symfony 3.3 and will not be used anymore in version 4.0. Set a custom question validator if needed.', E_USER_DEPRECATED);

                    throw new LogicException('A value is required.');
                }
            }

            return $value;
        });

        return parent::ask($input, $output, $question);
    }

    /**
     * {@inheritdoc}
     */
    protected function writePrompt(OutputInterface $output, Question $question)
    {
        $text = OutputFormatter::escapeTrailingBackslash($question->getQuestion());
        $default = $question->getDefault();

        switch (true) {
            case null === $default:
                $text = sprintf(' <info>%s</info>:', $text);

                break;

            case $question instanceof ConfirmationQuestion:
                $text = sprintf(' <info>%s (yes/no)</info> [<comment>%s</comment>]:', $text, $default ? 'yes' : 'no');

                break;

            case $question instanceof ChoiceQuestion && $question->isMultiselect():
                $choices = $question->getChoices();
                $default = explode(',', $default);

                foreach ($default as $key => $value) {
                    $default[$key] = $choices[trim($value)];
                }

                $text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape(implode(', ', $default)));

                break;

            case $question instanceof ChoiceQuestion:
                $choices = $question->getChoices();
                $text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape(isset($choices[$default]) ? $choices[$default] : $default));

                break;

            default:
                $text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($default));
        }

        $output->writeln($text);

        if ($question instanceof ChoiceQuestion) {
            $width = max(array_map('strlen', array_keys($question->getChoices())));

            foreach ($question->getChoices() as $key => $value) {
                $output->writeln(sprintf("  [<comment>%-${width}s</comment>] %s", $key, $value));
            }
        }

        $output->write(' > ');
    }

    /**
     * {@inheritdoc}
     */
    protected function writeError(OutputInterface $output, \Exception $error)
    {
        if ($output instanceof SymfonyStyle) {
            $output->newLine();
            $output->error($error->getMessage());

            return;
        }

        parent::writeError($output, $error);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

/**
 * HelperInterface is the interface all helpers must implement.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
interface HelperInterface
{
    /**
     * Sets the helper set associated with this helper.
     */
    public function setHelperSet(HelperSet $helperSet = null);

    /**
     * Gets the helper set associated with this helper.
     *
     * @return HelperSet A HelperSet instance
     */
    public function getHelperSet();

    /**
     * Returns the canonical name of this helper.
     *
     * @return string The canonical name
     */
    public function getName();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
 */
class TableCell
{
    private $value;
    private $options = [
        'rowspan' => 1,
        'colspan' => 1,
    ];

    /**
     * @param string $value
     * @param array  $options
     */
    public function __construct($value = '', array $options = [])
    {
        if (is_numeric($value) && !\is_string($value)) {
            $value = (string) $value;
        }

        $this->value = $value;

        // check option names
        if ($diff = array_diff(array_keys($options), array_keys($this->options))) {
            throw new InvalidArgumentException(sprintf('The TableCell does not support the following options: \'%s\'.', implode('\', \'', $diff)));
        }

        $this->options = array_merge($this->options, $options);
    }

    /**
     * Returns the cell value.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->value;
    }

    /**
     * Gets number of colspan.
     *
     * @return int
     */
    public function getColspan()
    {
        return (int) $this->options['colspan'];
    }

    /**
     * Gets number of rowspan.
     *
     * @return int
     */
    public function getRowspan()
    {
        return (int) $this->options['rowspan'];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;

/**
 * The ProcessHelper class provides helpers to run external processes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ProcessHelper extends Helper
{
    /**
     * Runs an external process.
     *
     * @param OutputInterface      $output    An OutputInterface instance
     * @param string|array|Process $cmd       An instance of Process or an array of arguments to escape and run or a command to run
     * @param string|null          $error     An error message that must be displayed if something went wrong
     * @param callable|null        $callback  A PHP callback to run whenever there is some
     *                                        output available on STDOUT or STDERR
     * @param int                  $verbosity The threshold for verbosity
     *
     * @return Process The process that ran
     */
    public function run(OutputInterface $output, $cmd, $error = null, callable $callback = null, $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE)
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        $formatter = $this->getHelperSet()->get('debug_formatter');

        if ($cmd instanceof Process) {
            $process = $cmd;
        } else {
            $process = new Process($cmd);
        }

        if ($verbosity <= $output->getVerbosity()) {
            $output->write($formatter->start(spl_object_hash($process), $this->escapeString($process->getCommandLine())));
        }

        if ($output->isDebug()) {
            $callback = $this->wrapCallback($output, $process, $callback);
        }

        $process->run($callback);

        if ($verbosity <= $output->getVerbosity()) {
            $message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode());
            $output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful()));
        }

        if (!$process->isSuccessful() && null !== $error) {
            $output->writeln(sprintf('<error>%s</error>', $this->escapeString($error)));
        }

        return $process;
    }

    /**
     * Runs the process.
     *
     * This is identical to run() except that an exception is thrown if the process
     * exits with a non-zero exit code.
     *
     * @param OutputInterface $output   An OutputInterface instance
     * @param string|Process  $cmd      An instance of Process or a command to run
     * @param string|null     $error    An error message that must be displayed if something went wrong
     * @param callable|null   $callback A PHP callback to run whenever there is some
     *                                  output available on STDOUT or STDERR
     *
     * @return Process The process that ran
     *
     * @throws ProcessFailedException
     *
     * @see run()
     */
    public function mustRun(OutputInterface $output, $cmd, $error = null, callable $callback = null)
    {
        $process = $this->run($output, $cmd, $error, $callback);

        if (!$process->isSuccessful()) {
            throw new ProcessFailedException($process);
        }

        return $process;
    }

    /**
     * Wraps a Process callback to add debugging output.
     *
     * @param OutputInterface $output   An OutputInterface interface
     * @param Process         $process  The Process
     * @param callable|null   $callback A PHP callable
     *
     * @return callable
     */
    public function wrapCallback(OutputInterface $output, Process $process, callable $callback = null)
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        $formatter = $this->getHelperSet()->get('debug_formatter');

        return function ($type, $buffer) use ($output, $process, $callback, $formatter) {
            $output->write($formatter->progress(spl_object_hash($process), $this->escapeString($buffer), Process::ERR === $type));

            if (null !== $callback) {
                \call_user_func($callback, $type, $buffer);
            }
        };
    }

    private function escapeString($str)
    {
        return str_replace('<', '\\<', $str);
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'process';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

/**
 * Marks a row as being a separator.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class TableSeparator extends TableCell
{
    public function __construct(array $options = [])
    {
        parent::__construct('', $options);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Formatter\OutputFormatter;

/**
 * The Formatter class provides helpers to format messages.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class FormatterHelper extends Helper
{
    /**
     * Formats a message within a section.
     *
     * @param string $section The section name
     * @param string $message The message
     * @param string $style   The style to apply to the section
     *
     * @return string The format section
     */
    public function formatSection($section, $message, $style = 'info')
    {
        return sprintf('<%s>[%s]</%s> %s', $style, $section, $style, $message);
    }

    /**
     * Formats a message as a block of text.
     *
     * @param string|array $messages The message to write in the block
     * @param string       $style    The style to apply to the whole block
     * @param bool         $large    Whether to return a large block
     *
     * @return string The formatter message
     */
    public function formatBlock($messages, $style, $large = false)
    {
        if (!\is_array($messages)) {
            $messages = [$messages];
        }

        $len = 0;
        $lines = [];
        foreach ($messages as $message) {
            $message = OutputFormatter::escape($message);
            $lines[] = sprintf($large ? '  %s  ' : ' %s ', $message);
            $len = max($this->strlen($message) + ($large ? 4 : 2), $len);
        }

        $messages = $large ? [str_repeat(' ', $len)] : [];
        for ($i = 0; isset($lines[$i]); ++$i) {
            $messages[] = $lines[$i].str_repeat(' ', $len - $this->strlen($lines[$i]));
        }
        if ($large) {
            $messages[] = str_repeat(' ', $len);
        }

        for ($i = 0; isset($messages[$i]); ++$i) {
            $messages[$i] = sprintf('<%s>%s</%s>', $style, $messages[$i], $style);
        }

        return implode("\n", $messages);
    }

    /**
     * Truncates a message to the given length.
     *
     * @param string $message
     * @param int    $length
     * @param string $suffix
     *
     * @return string
     */
    public function truncate($message, $length, $suffix = '...')
    {
        $computedLength = $length - $this->strlen($suffix);

        if ($computedLength > $this->strlen($message)) {
            return $message;
        }

        if (false === $encoding = mb_detect_encoding($message, null, true)) {
            return substr($message, 0, $length).$suffix;
        }

        return mb_substr($message, 0, $length, $encoding).$suffix;
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'formatter';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Terminal;

/**
 * The ProgressBar provides helpers to display progress output.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Chris Jones <leeked@gmail.com>
 */
final class ProgressBar
{
    private $barWidth = 28;
    private $barChar;
    private $emptyBarChar = '-';
    private $progressChar = '>';
    private $format;
    private $internalFormat;
    private $redrawFreq = 1;
    private $output;
    private $step = 0;
    private $max;
    private $startTime;
    private $stepWidth;
    private $percent = 0.0;
    private $formatLineCount;
    private $messages = [];
    private $overwrite = true;
    private $terminal;
    private $firstRun = true;

    private static $formatters;
    private static $formats;

    /**
     * @param OutputInterface $output An OutputInterface instance
     * @param int             $max    Maximum steps (0 if unknown)
     */
    public function __construct(OutputInterface $output, $max = 0)
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        $this->output = $output;
        $this->setMaxSteps($max);
        $this->terminal = new Terminal();

        if (!$this->output->isDecorated()) {
            // disable overwrite when output does not support ANSI codes.
            $this->overwrite = false;

            // set a reasonable redraw frequency so output isn't flooded
            $this->setRedrawFrequency($max / 10);
        }

        $this->startTime = time();
    }

    /**
     * Sets a placeholder formatter for a given name.
     *
     * This method also allow you to override an existing placeholder.
     *
     * @param string   $name     The placeholder name (including the delimiter char like %)
     * @param callable $callable A PHP callable
     */
    public static function setPlaceholderFormatterDefinition($name, callable $callable)
    {
        if (!self::$formatters) {
            self::$formatters = self::initPlaceholderFormatters();
        }

        self::$formatters[$name] = $callable;
    }

    /**
     * Gets the placeholder formatter for a given name.
     *
     * @param string $name The placeholder name (including the delimiter char like %)
     *
     * @return callable|null A PHP callable
     */
    public static function getPlaceholderFormatterDefinition($name)
    {
        if (!self::$formatters) {
            self::$formatters = self::initPlaceholderFormatters();
        }

        return isset(self::$formatters[$name]) ? self::$formatters[$name] : null;
    }

    /**
     * Sets a format for a given name.
     *
     * This method also allow you to override an existing format.
     *
     * @param string $name   The format name
     * @param string $format A format string
     */
    public static function setFormatDefinition($name, $format)
    {
        if (!self::$formats) {
            self::$formats = self::initFormats();
        }

        self::$formats[$name] = $format;
    }

    /**
     * Gets the format for a given name.
     *
     * @param string $name The format name
     *
     * @return string|null A format string
     */
    public static function getFormatDefinition($name)
    {
        if (!self::$formats) {
            self::$formats = self::initFormats();
        }

        return isset(self::$formats[$name]) ? self::$formats[$name] : null;
    }

    /**
     * Associates a text with a named placeholder.
     *
     * The text is displayed when the progress bar is rendered but only
     * when the corresponding placeholder is part of the custom format line
     * (by wrapping the name with %).
     *
     * @param string $message The text to associate with the placeholder
     * @param string $name    The name of the placeholder
     */
    public function setMessage($message, $name = 'message')
    {
        $this->messages[$name] = $message;
    }

    public function getMessage($name = 'message')
    {
        return $this->messages[$name];
    }

    /**
     * Gets the progress bar start time.
     *
     * @return int The progress bar start time
     */
    public function getStartTime()
    {
        return $this->startTime;
    }

    /**
     * Gets the progress bar maximal steps.
     *
     * @return int The progress bar max steps
     */
    public function getMaxSteps()
    {
        return $this->max;
    }

    /**
     * Gets the current step position.
     *
     * @return int The progress bar step
     */
    public function getProgress()
    {
        return $this->step;
    }

    /**
     * Gets the progress bar step width.
     *
     * @return int The progress bar step width
     */
    private function getStepWidth()
    {
        return $this->stepWidth;
    }

    /**
     * Gets the current progress bar percent.
     *
     * @return float The current progress bar percent
     */
    public function getProgressPercent()
    {
        return $this->percent;
    }

    /**
     * Sets the progress bar width.
     *
     * @param int $size The progress bar size
     */
    public function setBarWidth($size)
    {
        $this->barWidth = max(1, (int) $size);
    }

    /**
     * Gets the progress bar width.
     *
     * @return int The progress bar size
     */
    public function getBarWidth()
    {
        return $this->barWidth;
    }

    /**
     * Sets the bar character.
     *
     * @param string $char A character
     */
    public function setBarCharacter($char)
    {
        $this->barChar = $char;
    }

    /**
     * Gets the bar character.
     *
     * @return string A character
     */
    public function getBarCharacter()
    {
        if (null === $this->barChar) {
            return $this->max ? '=' : $this->emptyBarChar;
        }

        return $this->barChar;
    }

    /**
     * Sets the empty bar character.
     *
     * @param string $char A character
     */
    public function setEmptyBarCharacter($char)
    {
        $this->emptyBarChar = $char;
    }

    /**
     * Gets the empty bar character.
     *
     * @return string A character
     */
    public function getEmptyBarCharacter()
    {
        return $this->emptyBarChar;
    }

    /**
     * Sets the progress bar character.
     *
     * @param string $char A character
     */
    public function setProgressCharacter($char)
    {
        $this->progressChar = $char;
    }

    /**
     * Gets the progress bar character.
     *
     * @return string A character
     */
    public function getProgressCharacter()
    {
        return $this->progressChar;
    }

    /**
     * Sets the progress bar format.
     *
     * @param string $format The format
     */
    public function setFormat($format)
    {
        $this->format = null;
        $this->internalFormat = $format;
    }

    /**
     * Sets the redraw frequency.
     *
     * @param int|float $freq The frequency in steps
     */
    public function setRedrawFrequency($freq)
    {
        $this->redrawFreq = max((int) $freq, 1);
    }

    /**
     * Starts the progress output.
     *
     * @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged
     */
    public function start($max = null)
    {
        $this->startTime = time();
        $this->step = 0;
        $this->percent = 0.0;

        if (null !== $max) {
            $this->setMaxSteps($max);
        }

        $this->display();
    }

    /**
     * Advances the progress output X steps.
     *
     * @param int $step Number of steps to advance
     */
    public function advance($step = 1)
    {
        $this->setProgress($this->step + $step);
    }

    /**
     * Sets whether to overwrite the progressbar, false for new line.
     *
     * @param bool $overwrite
     */
    public function setOverwrite($overwrite)
    {
        $this->overwrite = (bool) $overwrite;
    }

    /**
     * Sets the current progress.
     *
     * @param int $step The current progress
     */
    public function setProgress($step)
    {
        $step = (int) $step;

        if ($this->max && $step > $this->max) {
            $this->max = $step;
        } elseif ($step < 0) {
            $step = 0;
        }

        $prevPeriod = (int) ($this->step / $this->redrawFreq);
        $currPeriod = (int) ($step / $this->redrawFreq);
        $this->step = $step;
        $this->percent = $this->max ? (float) $this->step / $this->max : 0;
        if ($prevPeriod !== $currPeriod || $this->max === $step) {
            $this->display();
        }
    }

    /**
     * Finishes the progress output.
     */
    public function finish()
    {
        if (!$this->max) {
            $this->max = $this->step;
        }

        if ($this->step === $this->max && !$this->overwrite) {
            // prevent double 100% output
            return;
        }

        $this->setProgress($this->max);
    }

    /**
     * Outputs the current progress string.
     */
    public function display()
    {
        if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
            return;
        }

        if (null === $this->format) {
            $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
        }

        $this->overwrite($this->buildLine());
    }

    /**
     * Removes the progress bar from the current line.
     *
     * This is useful if you wish to write some output
     * while a progress bar is running.
     * Call display() to show the progress bar again.
     */
    public function clear()
    {
        if (!$this->overwrite) {
            return;
        }

        if (null === $this->format) {
            $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
        }

        $this->overwrite('');
    }

    /**
     * Sets the progress bar format.
     *
     * @param string $format The format
     */
    private function setRealFormat($format)
    {
        // try to use the _nomax variant if available
        if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) {
            $this->format = self::getFormatDefinition($format.'_nomax');
        } elseif (null !== self::getFormatDefinition($format)) {
            $this->format = self::getFormatDefinition($format);
        } else {
            $this->format = $format;
        }

        $this->formatLineCount = substr_count($this->format, "\n");
    }

    /**
     * Sets the progress bar maximal steps.
     *
     * @param int $max The progress bar max steps
     */
    private function setMaxSteps($max)
    {
        $this->max = max(0, (int) $max);
        $this->stepWidth = $this->max ? Helper::strlen($this->max) : 4;
    }

    /**
     * Overwrites a previous message to the output.
     *
     * @param string $message The message
     */
    private function overwrite($message)
    {
        if ($this->overwrite) {
            if (!$this->firstRun) {
                // Erase previous lines
                if ($this->formatLineCount > 0) {
                    $message = str_repeat("\x1B[1A\x1B[2K", $this->formatLineCount).$message;
                }

                // Move the cursor to the beginning of the line and erase the line
                $message = "\x0D\x1B[2K$message";
            }
        } elseif ($this->step > 0) {
            $message = PHP_EOL.$message;
        }

        $this->firstRun = false;

        $this->output->write($message);
    }

    private function determineBestFormat()
    {
        switch ($this->output->getVerbosity()) {
            // OutputInterface::VERBOSITY_QUIET: display is disabled anyway
            case OutputInterface::VERBOSITY_VERBOSE:
                return $this->max ? 'verbose' : 'verbose_nomax';
            case OutputInterface::VERBOSITY_VERY_VERBOSE:
                return $this->max ? 'very_verbose' : 'very_verbose_nomax';
            case OutputInterface::VERBOSITY_DEBUG:
                return $this->max ? 'debug' : 'debug_nomax';
            default:
                return $this->max ? 'normal' : 'normal_nomax';
        }
    }

    private static function initPlaceholderFormatters()
    {
        return [
            'bar' => function (self $bar, OutputInterface $output) {
                $completeBars = floor($bar->getMaxSteps() > 0 ? $bar->getProgressPercent() * $bar->getBarWidth() : $bar->getProgress() % $bar->getBarWidth());
                $display = str_repeat($bar->getBarCharacter(), $completeBars);
                if ($completeBars < $bar->getBarWidth()) {
                    $emptyBars = $bar->getBarWidth() - $completeBars - Helper::strlenWithoutDecoration($output->getFormatter(), $bar->getProgressCharacter());
                    $display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars);
                }

                return $display;
            },
            'elapsed' => function (self $bar) {
                return Helper::formatTime(time() - $bar->getStartTime());
            },
            'remaining' => function (self $bar) {
                if (!$bar->getMaxSteps()) {
                    throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.');
                }

                if (!$bar->getProgress()) {
                    $remaining = 0;
                } else {
                    $remaining = round((time() - $bar->getStartTime()) / $bar->getProgress() * ($bar->getMaxSteps() - $bar->getProgress()));
                }

                return Helper::formatTime($remaining);
            },
            'estimated' => function (self $bar) {
                if (!$bar->getMaxSteps()) {
                    throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.');
                }

                if (!$bar->getProgress()) {
                    $estimated = 0;
                } else {
                    $estimated = round((time() - $bar->getStartTime()) / $bar->getProgress() * $bar->getMaxSteps());
                }

                return Helper::formatTime($estimated);
            },
            'memory' => function (self $bar) {
                return Helper::formatMemory(memory_get_usage(true));
            },
            'current' => function (self $bar) {
                return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', STR_PAD_LEFT);
            },
            'max' => function (self $bar) {
                return $bar->getMaxSteps();
            },
            'percent' => function (self $bar) {
                return floor($bar->getProgressPercent() * 100);
            },
        ];
    }

    private static function initFormats()
    {
        return [
            'normal' => ' %current%/%max% [%bar%] %percent:3s%%',
            'normal_nomax' => ' %current% [%bar%]',

            'verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%',
            'verbose_nomax' => ' %current% [%bar%] %elapsed:6s%',

            'very_verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%',
            'very_verbose_nomax' => ' %current% [%bar%] %elapsed:6s%',

            'debug' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%',
            'debug_nomax' => ' %current% [%bar%] %elapsed:6s% %memory:6s%',
        ];
    }

    /**
     * @return string
     */
    private function buildLine()
    {
        $regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i";
        $callback = function ($matches) {
            if ($formatter = $this::getPlaceholderFormatterDefinition($matches[1])) {
                $text = \call_user_func($formatter, $this, $this->output);
            } elseif (isset($this->messages[$matches[1]])) {
                $text = $this->messages[$matches[1]];
            } else {
                return $matches[0];
            }

            if (isset($matches[2])) {
                $text = sprintf('%'.$matches[2], $text);
            }

            return $text;
        };
        $line = preg_replace_callback($regex, $callback, $this->format);

        // gets string length for each sub line with multiline format
        $linesLength = array_map(function ($subLine) {
            return Helper::strlenWithoutDecoration($this->output->getFormatter(), rtrim($subLine, "\r"));
        }, explode("\n", $line));

        $linesWidth = max($linesLength);

        $terminalWidth = $this->terminal->getWidth();
        if ($linesWidth <= $terminalWidth) {
            return $line;
        }

        $this->setBarWidth($this->barWidth - $linesWidth + $terminalWidth);

        return preg_replace_callback($regex, $callback, $this->format);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

/**
 * Helps outputting debug information when running an external program from a command.
 *
 * An external program can be a Process, an HTTP request, or anything else.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class DebugFormatterHelper extends Helper
{
    private $colors = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default'];
    private $started = [];
    private $count = -1;

    /**
     * Starts a debug formatting session.
     *
     * @param string $id      The id of the formatting session
     * @param string $message The message to display
     * @param string $prefix  The prefix to use
     *
     * @return string
     */
    public function start($id, $message, $prefix = 'RUN')
    {
        $this->started[$id] = ['border' => ++$this->count % \count($this->colors)];

        return sprintf("%s<bg=blue;fg=white> %s </> <fg=blue>%s</>\n", $this->getBorder($id), $prefix, $message);
    }

    /**
     * Adds progress to a formatting session.
     *
     * @param string $id          The id of the formatting session
     * @param string $buffer      The message to display
     * @param bool   $error       Whether to consider the buffer as error
     * @param string $prefix      The prefix for output
     * @param string $errorPrefix The prefix for error output
     *
     * @return string
     */
    public function progress($id, $buffer, $error = false, $prefix = 'OUT', $errorPrefix = 'ERR')
    {
        $message = '';

        if ($error) {
            if (isset($this->started[$id]['out'])) {
                $message .= "\n";
                unset($this->started[$id]['out']);
            }
            if (!isset($this->started[$id]['err'])) {
                $message .= sprintf('%s<bg=red;fg=white> %s </> ', $this->getBorder($id), $errorPrefix);
                $this->started[$id]['err'] = true;
            }

            $message .= str_replace("\n", sprintf("\n%s<bg=red;fg=white> %s </> ", $this->getBorder($id), $errorPrefix), $buffer);
        } else {
            if (isset($this->started[$id]['err'])) {
                $message .= "\n";
                unset($this->started[$id]['err']);
            }
            if (!isset($this->started[$id]['out'])) {
                $message .= sprintf('%s<bg=green;fg=white> %s </> ', $this->getBorder($id), $prefix);
                $this->started[$id]['out'] = true;
            }

            $message .= str_replace("\n", sprintf("\n%s<bg=green;fg=white> %s </> ", $this->getBorder($id), $prefix), $buffer);
        }

        return $message;
    }

    /**
     * Stops a formatting session.
     *
     * @param string $id         The id of the formatting session
     * @param string $message    The message to display
     * @param bool   $successful Whether to consider the result as success
     * @param string $prefix     The prefix for the end output
     *
     * @return string
     */
    public function stop($id, $message, $successful, $prefix = 'RES')
    {
        $trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : '';

        if ($successful) {
            return sprintf("%s%s<bg=green;fg=white> %s </> <fg=green>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
        }

        $message = sprintf("%s%s<bg=red;fg=white> %s </> <fg=red>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);

        unset($this->started[$id]['out'], $this->started[$id]['err']);

        return $message;
    }

    /**
     * @param string $id The id of the formatting session
     *
     * @return string
     */
    private function getBorder($id)
    {
        return sprintf('<bg=%s> </>', $this->colors[$this->started[$id]['border']]);
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'debug_formatter';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * HelperSet represents a set of helpers to be used with a command.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class HelperSet implements \IteratorAggregate
{
    /**
     * @var Helper[]
     */
    private $helpers = [];
    private $command;

    /**
     * @param Helper[] $helpers An array of helper
     */
    public function __construct(array $helpers = [])
    {
        foreach ($helpers as $alias => $helper) {
            $this->set($helper, \is_int($alias) ? null : $alias);
        }
    }

    /**
     * Sets a helper.
     *
     * @param HelperInterface $helper The helper instance
     * @param string          $alias  An alias
     */
    public function set(HelperInterface $helper, $alias = null)
    {
        $this->helpers[$helper->getName()] = $helper;
        if (null !== $alias) {
            $this->helpers[$alias] = $helper;
        }

        $helper->setHelperSet($this);
    }

    /**
     * Returns true if the helper if defined.
     *
     * @param string $name The helper name
     *
     * @return bool true if the helper is defined, false otherwise
     */
    public function has($name)
    {
        return isset($this->helpers[$name]);
    }

    /**
     * Gets a helper value.
     *
     * @param string $name The helper name
     *
     * @return HelperInterface The helper instance
     *
     * @throws InvalidArgumentException if the helper is not defined
     */
    public function get($name)
    {
        if (!$this->has($name)) {
            throw new InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name));
        }

        return $this->helpers[$name];
    }

    public function setCommand(Command $command = null)
    {
        $this->command = $command;
    }

    /**
     * Gets the command associated with this helper set.
     *
     * @return Command A Command instance
     */
    public function getCommand()
    {
        return $this->command;
    }

    /**
     * @return Helper[]
     */
    public function getIterator()
    {
        return new \ArrayIterator($this->helpers);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Input\InputAwareInterface;
use Symfony\Component\Console\Input\InputInterface;

/**
 * An implementation of InputAwareInterface for Helpers.
 *
 * @author Wouter J <waldio.webdesign@gmail.com>
 */
abstract class InputAwareHelper extends Helper implements InputAwareInterface
{
    protected $input;

    /**
     * {@inheritdoc}
     */
    public function setInput(InputInterface $input)
    {
        $this->input = $input;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\StreamableInputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\Question;

/**
 * The QuestionHelper class provides helpers to interact with the user.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class QuestionHelper extends Helper
{
    private $inputStream;
    private static $shell;
    private static $stty;

    /**
     * Asks a question to the user.
     *
     * @return mixed The user answer
     *
     * @throws RuntimeException If there is no data to read in the input stream
     */
    public function ask(InputInterface $input, OutputInterface $output, Question $question)
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        if (!$input->isInteractive()) {
            $default = $question->getDefault();

            if (null === $default) {
                return $default;
            }

            if ($validator = $question->getValidator()) {
                return \call_user_func($question->getValidator(), $default);
            } elseif ($question instanceof ChoiceQuestion) {
                $choices = $question->getChoices();

                if (!$question->isMultiselect()) {
                    return isset($choices[$default]) ? $choices[$default] : $default;
                }

                $default = explode(',', $default);
                foreach ($default as $k => $v) {
                    $v = trim($v);
                    $default[$k] = isset($choices[$v]) ? $choices[$v] : $v;
                }
            }

            return $default;
        }

        if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) {
            $this->inputStream = $stream;
        }

        if (!$question->getValidator()) {
            return $this->doAsk($output, $question);
        }

        $interviewer = function () use ($output, $question) {
            return $this->doAsk($output, $question);
        };

        return $this->validateAttempts($interviewer, $output, $question);
    }

    /**
     * Sets the input stream to read from when interacting with the user.
     *
     * This is mainly useful for testing purpose.
     *
     * @deprecated since version 3.2, to be removed in 4.0. Use
     *             StreamableInputInterface::setStream() instead.
     *
     * @param resource $stream The input stream
     *
     * @throws InvalidArgumentException In case the stream is not a resource
     */
    public function setInputStream($stream)
    {
        @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.2 and will be removed in 4.0. Use %s::setStream() instead.', __METHOD__, StreamableInputInterface::class), E_USER_DEPRECATED);

        if (!\is_resource($stream)) {
            throw new InvalidArgumentException('Input stream must be a valid resource.');
        }

        $this->inputStream = $stream;
    }

    /**
     * Returns the helper's input stream.
     *
     * @deprecated since version 3.2, to be removed in 4.0. Use
     *             StreamableInputInterface::getStream() instead.
     *
     * @return resource
     */
    public function getInputStream()
    {
        if (0 === \func_num_args() || func_get_arg(0)) {
            @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.2 and will be removed in 4.0. Use %s::getStream() instead.', __METHOD__, StreamableInputInterface::class), E_USER_DEPRECATED);
        }

        return $this->inputStream;
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'question';
    }

    /**
     * Prevents usage of stty.
     */
    public static function disableStty()
    {
        self::$stty = false;
    }

    /**
     * Asks the question to the user.
     *
     * @return bool|mixed|string|null
     *
     * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
     */
    private function doAsk(OutputInterface $output, Question $question)
    {
        $this->writePrompt($output, $question);

        $inputStream = $this->inputStream ?: STDIN;
        $autocomplete = $question->getAutocompleterValues();

        if (null === $autocomplete || !$this->hasSttyAvailable()) {
            $ret = false;
            if ($question->isHidden()) {
                try {
                    $ret = trim($this->getHiddenResponse($output, $inputStream));
                } catch (RuntimeException $e) {
                    if (!$question->isHiddenFallback()) {
                        throw $e;
                    }
                }
            }

            if (false === $ret) {
                $ret = fgets($inputStream, 4096);
                if (false === $ret) {
                    throw new RuntimeException('Aborted.');
                }
                $ret = trim($ret);
            }
        } else {
            $ret = trim($this->autocomplete($output, $question, $inputStream, \is_array($autocomplete) ? $autocomplete : iterator_to_array($autocomplete, false)));
        }

        $ret = \strlen($ret) > 0 ? $ret : $question->getDefault();

        if ($normalizer = $question->getNormalizer()) {
            return $normalizer($ret);
        }

        return $ret;
    }

    /**
     * Outputs the question prompt.
     */
    protected function writePrompt(OutputInterface $output, Question $question)
    {
        $message = $question->getQuestion();

        if ($question instanceof ChoiceQuestion) {
            $maxWidth = max(array_map([$this, 'strlen'], array_keys($question->getChoices())));

            $messages = (array) $question->getQuestion();
            foreach ($question->getChoices() as $key => $value) {
                $width = $maxWidth - $this->strlen($key);
                $messages[] = '  [<info>'.$key.str_repeat(' ', $width).'</info>] '.$value;
            }

            $output->writeln($messages);

            $message = $question->getPrompt();
        }

        $output->write($message);
    }

    /**
     * Outputs an error message.
     */
    protected function writeError(OutputInterface $output, \Exception $error)
    {
        if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) {
            $message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error');
        } else {
            $message = '<error>'.$error->getMessage().'</error>';
        }

        $output->writeln($message);
    }

    /**
     * Autocompletes a question.
     *
     * @param OutputInterface $output
     * @param Question        $question
     * @param resource        $inputStream
     * @param array           $autocomplete
     *
     * @return string
     */
    private function autocomplete(OutputInterface $output, Question $question, $inputStream, array $autocomplete)
    {
        $ret = '';

        $i = 0;
        $ofs = -1;
        $matches = $autocomplete;
        $numMatches = \count($matches);

        $sttyMode = shell_exec('stty -g');

        // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead)
        shell_exec('stty -icanon -echo');

        // Add highlighted text style
        $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white'));

        // Read a keypress
        while (!feof($inputStream)) {
            $c = fread($inputStream, 1);

            // as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false.
            if (false === $c || ('' === $ret && '' === $c && null === $question->getDefault())) {
                shell_exec(sprintf('stty %s', $sttyMode));
                throw new RuntimeException('Aborted.');
            } elseif ("\177" === $c) { // Backspace Character
                if (0 === $numMatches && 0 !== $i) {
                    --$i;
                    // Move cursor backwards
                    $output->write("\033[1D");
                }

                if (0 === $i) {
                    $ofs = -1;
                    $matches = $autocomplete;
                    $numMatches = \count($matches);
                } else {
                    $numMatches = 0;
                }

                // Pop the last character off the end of our string
                $ret = substr($ret, 0, $i);
            } elseif ("\033" === $c) {
                // Did we read an escape sequence?
                $c .= fread($inputStream, 2);

                // A = Up Arrow. B = Down Arrow
                if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) {
                    if ('A' === $c[2] && -1 === $ofs) {
                        $ofs = 0;
                    }

                    if (0 === $numMatches) {
                        continue;
                    }

                    $ofs += ('A' === $c[2]) ? -1 : 1;
                    $ofs = ($numMatches + $ofs) % $numMatches;
                }
            } elseif (\ord($c) < 32) {
                if ("\t" === $c || "\n" === $c) {
                    if ($numMatches > 0 && -1 !== $ofs) {
                        $ret = $matches[$ofs];
                        // Echo out remaining chars for current match
                        $output->write(substr($ret, $i));
                        $i = \strlen($ret);
                    }

                    if ("\n" === $c) {
                        $output->write($c);
                        break;
                    }

                    $numMatches = 0;
                }

                continue;
            } else {
                if ("\x80" <= $c) {
                    $c .= fread($inputStream, ["\xC0" => 1, "\xD0" => 1, "\xE0" => 2, "\xF0" => 3][$c & "\xF0"]);
                }

                $output->write($c);
                $ret .= $c;
                ++$i;

                $numMatches = 0;
                $ofs = 0;

                foreach ($autocomplete as $value) {
                    // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
                    if (0 === strpos($value, $ret)) {
                        $matches[$numMatches++] = $value;
                    }
                }
            }

            // Erase characters from cursor to end of line
            $output->write("\033[K");

            if ($numMatches > 0 && -1 !== $ofs) {
                // Save cursor position
                $output->write("\0337");
                // Write highlighted text
                $output->write('<hl>'.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $i)).'</hl>');
                // Restore cursor position
                $output->write("\0338");
            }
        }

        // Reset stty so it behaves normally again
        shell_exec(sprintf('stty %s', $sttyMode));

        return $ret;
    }

    /**
     * Gets a hidden response from user.
     *
     * @param OutputInterface $output      An Output instance
     * @param resource        $inputStream The handler resource
     *
     * @return string The answer
     *
     * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
     */
    private function getHiddenResponse(OutputInterface $output, $inputStream)
    {
        if ('\\' === \DIRECTORY_SEPARATOR) {
            $exe = __DIR__.'/../Resources/bin/hiddeninput.exe';

            // handle code running from a phar
            if ('phar:' === substr(__FILE__, 0, 5)) {
                $tmpExe = sys_get_temp_dir().'/hiddeninput.exe';
                copy($exe, $tmpExe);
                $exe = $tmpExe;
            }

            $value = rtrim(shell_exec($exe));
            $output->writeln('');

            if (isset($tmpExe)) {
                unlink($tmpExe);
            }

            return $value;
        }

        if ($this->hasSttyAvailable()) {
            $sttyMode = shell_exec('stty -g');

            shell_exec('stty -echo');
            $value = fgets($inputStream, 4096);
            shell_exec(sprintf('stty %s', $sttyMode));

            if (false === $value) {
                throw new RuntimeException('Aborted.');
            }

            $value = trim($value);
            $output->writeln('');

            return $value;
        }

        if (false !== $shell = $this->getShell()) {
            $readCmd = 'csh' === $shell ? 'set mypassword = $<' : 'read -r mypassword';
            $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd);
            $value = rtrim(shell_exec($command));
            $output->writeln('');

            return $value;
        }

        throw new RuntimeException('Unable to hide the response.');
    }

    /**
     * Validates an attempt.
     *
     * @param callable        $interviewer A callable that will ask for a question and return the result
     * @param OutputInterface $output      An Output instance
     * @param Question        $question    A Question instance
     *
     * @return mixed The validated response
     *
     * @throws \Exception In case the max number of attempts has been reached and no valid response has been given
     */
    private function validateAttempts(callable $interviewer, OutputInterface $output, Question $question)
    {
        $error = null;
        $attempts = $question->getMaxAttempts();
        while (null === $attempts || $attempts--) {
            if (null !== $error) {
                $this->writeError($output, $error);
            }

            try {
                return \call_user_func($question->getValidator(), $interviewer());
            } catch (RuntimeException $e) {
                throw $e;
            } catch (\Exception $error) {
            }
        }

        throw $error;
    }

    /**
     * Returns a valid unix shell.
     *
     * @return string|bool The valid shell name, false in case no valid shell is found
     */
    private function getShell()
    {
        if (null !== self::$shell) {
            return self::$shell;
        }

        self::$shell = false;

        if (file_exists('/usr/bin/env')) {
            // handle other OSs with bash/zsh/ksh/csh if available to hide the answer
            $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null";
            foreach (['bash', 'zsh', 'ksh', 'csh'] as $sh) {
                if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) {
                    self::$shell = $sh;
                    break;
                }
            }
        }

        return self::$shell;
    }

    /**
     * Returns whether Stty is available or not.
     *
     * @return bool
     */
    private function hasSttyAvailable()
    {
        if (null !== self::$stty) {
            return self::$stty;
        }

        exec('stty 2>&1', $output, $exitcode);

        return self::$stty = 0 === $exitcode;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * Helper is the base class for all helper classes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
abstract class Helper implements HelperInterface
{
    protected $helperSet = null;

    /**
     * {@inheritdoc}
     */
    public function setHelperSet(HelperSet $helperSet = null)
    {
        $this->helperSet = $helperSet;
    }

    /**
     * {@inheritdoc}
     */
    public function getHelperSet()
    {
        return $this->helperSet;
    }

    /**
     * Returns the length of a string, using mb_strwidth if it is available.
     *
     * @param string $string The string to check its length
     *
     * @return int The length of the string
     */
    public static function strlen($string)
    {
        if (false === $encoding = mb_detect_encoding($string, null, true)) {
            return \strlen($string);
        }

        return mb_strwidth($string, $encoding);
    }

    /**
     * Returns the subset of a string, using mb_substr if it is available.
     *
     * @param string   $string String to subset
     * @param int      $from   Start offset
     * @param int|null $length Length to read
     *
     * @return string The string subset
     */
    public static function substr($string, $from, $length = null)
    {
        if (false === $encoding = mb_detect_encoding($string, null, true)) {
            return substr($string, $from, $length);
        }

        return mb_substr($string, $from, $length, $encoding);
    }

    public static function formatTime($secs)
    {
        static $timeFormats = [
            [0, '< 1 sec'],
            [1, '1 sec'],
            [2, 'secs', 1],
            [60, '1 min'],
            [120, 'mins', 60],
            [3600, '1 hr'],
            [7200, 'hrs', 3600],
            [86400, '1 day'],
            [172800, 'days', 86400],
        ];

        foreach ($timeFormats as $index => $format) {
            if ($secs >= $format[0]) {
                if ((isset($timeFormats[$index + 1]) && $secs < $timeFormats[$index + 1][0])
                    || $index == \count($timeFormats) - 1
                ) {
                    if (2 == \count($format)) {
                        return $format[1];
                    }

                    return floor($secs / $format[2]).' '.$format[1];
                }
            }
        }
    }

    public static function formatMemory($memory)
    {
        if ($memory >= 1024 * 1024 * 1024) {
            return sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024);
        }

        if ($memory >= 1024 * 1024) {
            return sprintf('%.1f MiB', $memory / 1024 / 1024);
        }

        if ($memory >= 1024) {
            return sprintf('%d KiB', $memory / 1024);
        }

        return sprintf('%d B', $memory);
    }

    public static function strlenWithoutDecoration(OutputFormatterInterface $formatter, $string)
    {
        return self::strlen(self::removeDecoration($formatter, $string));
    }

    public static function removeDecoration(OutputFormatterInterface $formatter, $string)
    {
        $isDecorated = $formatter->isDecorated();
        $formatter->setDecorated(false);
        // remove <...> formatting
        $string = $formatter->format($string);
        // remove already formatted characters
        $string = preg_replace("/\033\[[^m]*m/", '', $string);
        $formatter->setDecorated($isDecorated);

        return $string;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Provides helpers to display a table.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Ð¡Ð°ÑˆÐ° Ð¡Ñ‚Ð°Ð¼ÐµÐ½ÐºÐ¾Ð²Ð¸Ñ› <umpirsky@gmail.com>
 * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
 * @author Max Grigorian <maxakawizard@gmail.com>
 */
class Table
{
    /**
     * Table headers.
     */
    private $headers = [];

    /**
     * Table rows.
     */
    private $rows = [];

    /**
     * Column widths cache.
     */
    private $effectiveColumnWidths = [];

    /**
     * Number of columns cache.
     *
     * @var int
     */
    private $numberOfColumns;

    /**
     * @var OutputInterface
     */
    private $output;

    /**
     * @var TableStyle
     */
    private $style;

    /**
     * @var array
     */
    private $columnStyles = [];

    /**
     * User set column widths.
     *
     * @var array
     */
    private $columnWidths = [];

    private static $styles;

    public function __construct(OutputInterface $output)
    {
        $this->output = $output;

        if (!self::$styles) {
            self::$styles = self::initStyles();
        }

        $this->setStyle('default');
    }

    /**
     * Sets a style definition.
     *
     * @param string     $name  The style name
     * @param TableStyle $style A TableStyle instance
     */
    public static function setStyleDefinition($name, TableStyle $style)
    {
        if (!self::$styles) {
            self::$styles = self::initStyles();
        }

        self::$styles[$name] = $style;
    }

    /**
     * Gets a style definition by name.
     *
     * @param string $name The style name
     *
     * @return TableStyle
     */
    public static function getStyleDefinition($name)
    {
        if (!self::$styles) {
            self::$styles = self::initStyles();
        }

        if (isset(self::$styles[$name])) {
            return self::$styles[$name];
        }

        throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
    }

    /**
     * Sets table style.
     *
     * @param TableStyle|string $name The style name or a TableStyle instance
     *
     * @return $this
     */
    public function setStyle($name)
    {
        $this->style = $this->resolveStyle($name);

        return $this;
    }

    /**
     * Gets the current table style.
     *
     * @return TableStyle
     */
    public function getStyle()
    {
        return $this->style;
    }

    /**
     * Sets table column style.
     *
     * @param int               $columnIndex Column index
     * @param TableStyle|string $name        The style name or a TableStyle instance
     *
     * @return $this
     */
    public function setColumnStyle($columnIndex, $name)
    {
        $columnIndex = (int) $columnIndex;

        $this->columnStyles[$columnIndex] = $this->resolveStyle($name);

        return $this;
    }

    /**
     * Gets the current style for a column.
     *
     * If style was not set, it returns the global table style.
     *
     * @param int $columnIndex Column index
     *
     * @return TableStyle
     */
    public function getColumnStyle($columnIndex)
    {
        if (isset($this->columnStyles[$columnIndex])) {
            return $this->columnStyles[$columnIndex];
        }

        return $this->getStyle();
    }

    /**
     * Sets the minimum width of a column.
     *
     * @param int $columnIndex Column index
     * @param int $width       Minimum column width in characters
     *
     * @return $this
     */
    public function setColumnWidth($columnIndex, $width)
    {
        $this->columnWidths[(int) $columnIndex] = (int) $width;

        return $this;
    }

    /**
     * Sets the minimum width of all columns.
     *
     * @param array $widths
     *
     * @return $this
     */
    public function setColumnWidths(array $widths)
    {
        $this->columnWidths = [];
        foreach ($widths as $index => $width) {
            $this->setColumnWidth($index, $width);
        }

        return $this;
    }

    public function setHeaders(array $headers)
    {
        $headers = array_values($headers);
        if (!empty($headers) && !\is_array($headers[0])) {
            $headers = [$headers];
        }

        $this->headers = $headers;

        return $this;
    }

    public function setRows(array $rows)
    {
        $this->rows = [];

        return $this->addRows($rows);
    }

    public function addRows(array $rows)
    {
        foreach ($rows as $row) {
            $this->addRow($row);
        }

        return $this;
    }

    public function addRow($row)
    {
        if ($row instanceof TableSeparator) {
            $this->rows[] = $row;

            return $this;
        }

        if (!\is_array($row)) {
            throw new InvalidArgumentException('A row must be an array or a TableSeparator instance.');
        }

        $this->rows[] = array_values($row);

        return $this;
    }

    public function setRow($column, array $row)
    {
        $this->rows[$column] = $row;

        return $this;
    }

    /**
     * Renders table to output.
     *
     * Example:
     *
     *     +---------------+-----------------------+------------------+
     *     | ISBN          | Title                 | Author           |
     *     +---------------+-----------------------+------------------+
     *     | 99921-58-10-7 | Divine Comedy         | Dante Alighieri  |
     *     | 9971-5-0210-0 | A Tale of Two Cities  | Charles Dickens  |
     *     | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
     *     +---------------+-----------------------+------------------+
     */
    public function render()
    {
        $this->calculateNumberOfColumns();
        $rows = $this->buildTableRows($this->rows);
        $headers = $this->buildTableRows($this->headers);

        $this->calculateColumnsWidth(array_merge($headers, $rows));

        $this->renderRowSeparator();
        if (!empty($headers)) {
            foreach ($headers as $header) {
                $this->renderRow($header, $this->style->getCellHeaderFormat());
                $this->renderRowSeparator();
            }
        }
        foreach ($rows as $row) {
            if ($row instanceof TableSeparator) {
                $this->renderRowSeparator();
            } else {
                $this->renderRow($row, $this->style->getCellRowFormat());
            }
        }
        if (!empty($rows)) {
            $this->renderRowSeparator();
        }

        $this->cleanup();
    }

    /**
     * Renders horizontal header separator.
     *
     * Example:
     *
     *     +-----+-----------+-------+
     */
    private function renderRowSeparator()
    {
        if (0 === $count = $this->numberOfColumns) {
            return;
        }

        if (!$this->style->getHorizontalBorderChar() && !$this->style->getCrossingChar()) {
            return;
        }

        $markup = $this->style->getCrossingChar();
        for ($column = 0; $column < $count; ++$column) {
            $markup .= str_repeat($this->style->getHorizontalBorderChar(), $this->effectiveColumnWidths[$column]).$this->style->getCrossingChar();
        }

        $this->output->writeln(sprintf($this->style->getBorderFormat(), $markup));
    }

    /**
     * Renders vertical column separator.
     */
    private function renderColumnSeparator()
    {
        return sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar());
    }

    /**
     * Renders table row.
     *
     * Example:
     *
     *     | 9971-5-0210-0 | A Tale of Two Cities  | Charles Dickens  |
     *
     * @param array  $row
     * @param string $cellFormat
     */
    private function renderRow(array $row, $cellFormat)
    {
        if (empty($row)) {
            return;
        }

        $rowContent = $this->renderColumnSeparator();
        foreach ($this->getRowColumns($row) as $column) {
            $rowContent .= $this->renderCell($row, $column, $cellFormat);
            $rowContent .= $this->renderColumnSeparator();
        }
        $this->output->writeln($rowContent);
    }

    /**
     * Renders table cell with padding.
     *
     * @param array  $row
     * @param int    $column
     * @param string $cellFormat
     */
    private function renderCell(array $row, $column, $cellFormat)
    {
        $cell = isset($row[$column]) ? $row[$column] : '';
        $width = $this->effectiveColumnWidths[$column];
        if ($cell instanceof TableCell && $cell->getColspan() > 1) {
            // add the width of the following columns(numbers of colspan).
            foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) {
                $width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn];
            }
        }

        // str_pad won't work properly with multi-byte strings, we need to fix the padding
        if (false !== $encoding = mb_detect_encoding($cell, null, true)) {
            $width += \strlen($cell) - mb_strwidth($cell, $encoding);
        }

        $style = $this->getColumnStyle($column);

        if ($cell instanceof TableSeparator) {
            return sprintf($style->getBorderFormat(), str_repeat($style->getHorizontalBorderChar(), $width));
        }

        $width += Helper::strlen($cell) - Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell);
        $content = sprintf($style->getCellRowContentFormat(), $cell);

        return sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $style->getPadType()));
    }

    /**
     * Calculate number of columns for this table.
     */
    private function calculateNumberOfColumns()
    {
        if (null !== $this->numberOfColumns) {
            return;
        }

        $columns = [0];
        foreach (array_merge($this->headers, $this->rows) as $row) {
            if ($row instanceof TableSeparator) {
                continue;
            }

            $columns[] = $this->getNumberOfColumns($row);
        }

        $this->numberOfColumns = max($columns);
    }

    private function buildTableRows($rows)
    {
        $unmergedRows = [];
        for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) {
            $rows = $this->fillNextRows($rows, $rowKey);

            // Remove any new line breaks and replace it with a new line
            foreach ($rows[$rowKey] as $column => $cell) {
                if (!strstr($cell, "\n")) {
                    continue;
                }
                $lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell));
                foreach ($lines as $lineKey => $line) {
                    if ($cell instanceof TableCell) {
                        $line = new TableCell($line, ['colspan' => $cell->getColspan()]);
                    }
                    if (0 === $lineKey) {
                        $rows[$rowKey][$column] = $line;
                    } else {
                        $unmergedRows[$rowKey][$lineKey][$column] = $line;
                    }
                }
            }
        }

        $tableRows = [];
        foreach ($rows as $rowKey => $row) {
            $tableRows[] = $this->fillCells($row);
            if (isset($unmergedRows[$rowKey])) {
                $tableRows = array_merge($tableRows, $unmergedRows[$rowKey]);
            }
        }

        return $tableRows;
    }

    /**
     * fill rows that contains rowspan > 1.
     *
     * @param array $rows
     * @param int   $line
     *
     * @return array
     *
     * @throws InvalidArgumentException
     */
    private function fillNextRows(array $rows, $line)
    {
        $unmergedRows = [];
        foreach ($rows[$line] as $column => $cell) {
            if (null !== $cell && !$cell instanceof TableCell && !is_scalar($cell) && !(\is_object($cell) && method_exists($cell, '__toString'))) {
                throw new InvalidArgumentException(sprintf('A cell must be a TableCell, a scalar or an object implementing __toString, %s given.', \gettype($cell)));
            }
            if ($cell instanceof TableCell && $cell->getRowspan() > 1) {
                $nbLines = $cell->getRowspan() - 1;
                $lines = [$cell];
                if (strstr($cell, "\n")) {
                    $lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell));
                    $nbLines = \count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines;

                    $rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan()]);
                    unset($lines[0]);
                }

                // create a two dimensional array (rowspan x colspan)
                $unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, []), $unmergedRows);
                foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
                    $value = isset($lines[$unmergedRowKey - $line]) ? $lines[$unmergedRowKey - $line] : '';
                    $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, ['colspan' => $cell->getColspan()]);
                    if ($nbLines === $unmergedRowKey - $line) {
                        break;
                    }
                }
            }
        }

        foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
            // we need to know if $unmergedRow will be merged or inserted into $rows
            if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) {
                foreach ($unmergedRow as $cellKey => $cell) {
                    // insert cell into row at cellKey position
                    array_splice($rows[$unmergedRowKey], $cellKey, 0, [$cell]);
                }
            } else {
                $row = $this->copyRow($rows, $unmergedRowKey - 1);
                foreach ($unmergedRow as $column => $cell) {
                    if (!empty($cell)) {
                        $row[$column] = $unmergedRow[$column];
                    }
                }
                array_splice($rows, $unmergedRowKey, 0, [$row]);
            }
        }

        return $rows;
    }

    /**
     * fill cells for a row that contains colspan > 1.
     *
     * @return array
     */
    private function fillCells($row)
    {
        $newRow = [];
        foreach ($row as $column => $cell) {
            $newRow[] = $cell;
            if ($cell instanceof TableCell && $cell->getColspan() > 1) {
                foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) {
                    // insert empty value at column position
                    $newRow[] = '';
                }
            }
        }

        return $newRow ?: $row;
    }

    /**
     * @param array $rows
     * @param int   $line
     *
     * @return array
     */
    private function copyRow(array $rows, $line)
    {
        $row = $rows[$line];
        foreach ($row as $cellKey => $cellValue) {
            $row[$cellKey] = '';
            if ($cellValue instanceof TableCell) {
                $row[$cellKey] = new TableCell('', ['colspan' => $cellValue->getColspan()]);
            }
        }

        return $row;
    }

    /**
     * Gets number of columns by row.
     *
     * @return int
     */
    private function getNumberOfColumns(array $row)
    {
        $columns = \count($row);
        foreach ($row as $column) {
            $columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0;
        }

        return $columns;
    }

    /**
     * Gets list of columns for the given row.
     *
     * @return array
     */
    private function getRowColumns(array $row)
    {
        $columns = range(0, $this->numberOfColumns - 1);
        foreach ($row as $cellKey => $cell) {
            if ($cell instanceof TableCell && $cell->getColspan() > 1) {
                // exclude grouped columns.
                $columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1));
            }
        }

        return $columns;
    }

    /**
     * Calculates columns widths.
     */
    private function calculateColumnsWidth(array $rows)
    {
        for ($column = 0; $column < $this->numberOfColumns; ++$column) {
            $lengths = [];
            foreach ($rows as $row) {
                if ($row instanceof TableSeparator) {
                    continue;
                }

                foreach ($row as $i => $cell) {
                    if ($cell instanceof TableCell) {
                        $textContent = Helper::removeDecoration($this->output->getFormatter(), $cell);
                        $textLength = Helper::strlen($textContent);
                        if ($textLength > 0) {
                            $contentColumns = str_split($textContent, ceil($textLength / $cell->getColspan()));
                            foreach ($contentColumns as $position => $content) {
                                $row[$i + $position] = $content;
                            }
                        }
                    }
                }

                $lengths[] = $this->getCellWidth($row, $column);
            }

            $this->effectiveColumnWidths[$column] = max($lengths) + Helper::strlen($this->style->getCellRowContentFormat()) - 2;
        }
    }

    /**
     * Gets column width.
     *
     * @return int
     */
    private function getColumnSeparatorWidth()
    {
        return Helper::strlen(sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar()));
    }

    /**
     * Gets cell width.
     *
     * @param array $row
     * @param int   $column
     *
     * @return int
     */
    private function getCellWidth(array $row, $column)
    {
        $cellWidth = 0;

        if (isset($row[$column])) {
            $cell = $row[$column];
            $cellWidth = Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell);
        }

        $columnWidth = isset($this->columnWidths[$column]) ? $this->columnWidths[$column] : 0;

        return max($cellWidth, $columnWidth);
    }

    /**
     * Called after rendering to cleanup cache data.
     */
    private function cleanup()
    {
        $this->effectiveColumnWidths = [];
        $this->numberOfColumns = null;
    }

    private static function initStyles()
    {
        $borderless = new TableStyle();
        $borderless
            ->setHorizontalBorderChar('=')
            ->setVerticalBorderChar(' ')
            ->setCrossingChar(' ')
        ;

        $compact = new TableStyle();
        $compact
            ->setHorizontalBorderChar('')
            ->setVerticalBorderChar(' ')
            ->setCrossingChar('')
            ->setCellRowContentFormat('%s')
        ;

        $styleGuide = new TableStyle();
        $styleGuide
            ->setHorizontalBorderChar('-')
            ->setVerticalBorderChar(' ')
            ->setCrossingChar(' ')
            ->setCellHeaderFormat('%s')
        ;

        return [
            'default' => new TableStyle(),
            'borderless' => $borderless,
            'compact' => $compact,
            'symfony-style-guide' => $styleGuide,
        ];
    }

    private function resolveStyle($name)
    {
        if ($name instanceof TableStyle) {
            return $name;
        }

        if (isset(self::$styles[$name])) {
            return self::$styles[$name];
        }

        throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Kevin Bond <kevinbond@gmail.com>
 */
class ProgressIndicator
{
    private $output;
    private $startTime;
    private $format;
    private $message;
    private $indicatorValues;
    private $indicatorCurrent;
    private $indicatorChangeInterval;
    private $indicatorUpdateTime;
    private $started = false;

    private static $formatters;
    private static $formats;

    /**
     * @param OutputInterface $output
     * @param string|null     $format                  Indicator format
     * @param int             $indicatorChangeInterval Change interval in milliseconds
     * @param array|null      $indicatorValues         Animated indicator characters
     */
    public function __construct(OutputInterface $output, $format = null, $indicatorChangeInterval = 100, $indicatorValues = null)
    {
        $this->output = $output;

        if (null === $format) {
            $format = $this->determineBestFormat();
        }

        if (null === $indicatorValues) {
            $indicatorValues = ['-', '\\', '|', '/'];
        }

        $indicatorValues = array_values($indicatorValues);

        if (2 > \count($indicatorValues)) {
            throw new InvalidArgumentException('Must have at least 2 indicator value characters.');
        }

        $this->format = self::getFormatDefinition($format);
        $this->indicatorChangeInterval = $indicatorChangeInterval;
        $this->indicatorValues = $indicatorValues;
        $this->startTime = time();
    }

    /**
     * Sets the current indicator message.
     *
     * @param string|null $message
     */
    public function setMessage($message)
    {
        $this->message = $message;

        $this->display();
    }

    /**
     * Starts the indicator output.
     *
     * @param $message
     */
    public function start($message)
    {
        if ($this->started) {
            throw new LogicException('Progress indicator already started.');
        }

        $this->message = $message;
        $this->started = true;
        $this->startTime = time();
        $this->indicatorUpdateTime = $this->getCurrentTimeInMilliseconds() + $this->indicatorChangeInterval;
        $this->indicatorCurrent = 0;

        $this->display();
    }

    /**
     * Advances the indicator.
     */
    public function advance()
    {
        if (!$this->started) {
            throw new LogicException('Progress indicator has not yet been started.');
        }

        if (!$this->output->isDecorated()) {
            return;
        }

        $currentTime = $this->getCurrentTimeInMilliseconds();

        if ($currentTime < $this->indicatorUpdateTime) {
            return;
        }

        $this->indicatorUpdateTime = $currentTime + $this->indicatorChangeInterval;
        ++$this->indicatorCurrent;

        $this->display();
    }

    /**
     * Finish the indicator with message.
     *
     * @param $message
     */
    public function finish($message)
    {
        if (!$this->started) {
            throw new LogicException('Progress indicator has not yet been started.');
        }

        $this->message = $message;
        $this->display();
        $this->output->writeln('');
        $this->started = false;
    }

    /**
     * Gets the format for a given name.
     *
     * @param string $name The format name
     *
     * @return string|null A format string
     */
    public static function getFormatDefinition($name)
    {
        if (!self::$formats) {
            self::$formats = self::initFormats();
        }

        return isset(self::$formats[$name]) ? self::$formats[$name] : null;
    }

    /**
     * Sets a placeholder formatter for a given name.
     *
     * This method also allow you to override an existing placeholder.
     *
     * @param string   $name     The placeholder name (including the delimiter char like %)
     * @param callable $callable A PHP callable
     */
    public static function setPlaceholderFormatterDefinition($name, $callable)
    {
        if (!self::$formatters) {
            self::$formatters = self::initPlaceholderFormatters();
        }

        self::$formatters[$name] = $callable;
    }

    /**
     * Gets the placeholder formatter for a given name.
     *
     * @param string $name The placeholder name (including the delimiter char like %)
     *
     * @return callable|null A PHP callable
     */
    public static function getPlaceholderFormatterDefinition($name)
    {
        if (!self::$formatters) {
            self::$formatters = self::initPlaceholderFormatters();
        }

        return isset(self::$formatters[$name]) ? self::$formatters[$name] : null;
    }

    private function display()
    {
        if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
            return;
        }

        $self = $this;

        $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) use ($self) {
            if ($formatter = $self::getPlaceholderFormatterDefinition($matches[1])) {
                return \call_user_func($formatter, $self);
            }

            return $matches[0];
        }, $this->format));
    }

    private function determineBestFormat()
    {
        switch ($this->output->getVerbosity()) {
            // OutputInterface::VERBOSITY_QUIET: display is disabled anyway
            case OutputInterface::VERBOSITY_VERBOSE:
                return $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi';
            case OutputInterface::VERBOSITY_VERY_VERBOSE:
            case OutputInterface::VERBOSITY_DEBUG:
                return $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi';
            default:
                return $this->output->isDecorated() ? 'normal' : 'normal_no_ansi';
        }
    }

    /**
     * Overwrites a previous message to the output.
     *
     * @param string $message The message
     */
    private function overwrite($message)
    {
        if ($this->output->isDecorated()) {
            $this->output->write("\x0D\x1B[2K");
            $this->output->write($message);
        } else {
            $this->output->writeln($message);
        }
    }

    private function getCurrentTimeInMilliseconds()
    {
        return round(microtime(true) * 1000);
    }

    private static function initPlaceholderFormatters()
    {
        return [
            'indicator' => function (self $indicator) {
                return $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)];
            },
            'message' => function (self $indicator) {
                return $indicator->message;
            },
            'elapsed' => function (self $indicator) {
                return Helper::formatTime(time() - $indicator->startTime);
            },
            'memory' => function () {
                return Helper::formatMemory(memory_get_usage(true));
            },
        ];
    }

    private static function initFormats()
    {
        return [
            'normal' => ' %indicator% %message%',
            'normal_no_ansi' => ' %message%',

            'verbose' => ' %indicator% %message% (%elapsed:6s%)',
            'verbose_no_ansi' => ' %message% (%elapsed:6s%)',

            'very_verbose' => ' %indicator% %message% (%elapsed:6s%, %memory:6s%)',
            'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)',
        ];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Descriptor\DescriptorInterface;
use Symfony\Component\Console\Descriptor\JsonDescriptor;
use Symfony\Component\Console\Descriptor\MarkdownDescriptor;
use Symfony\Component\Console\Descriptor\TextDescriptor;
use Symfony\Component\Console\Descriptor\XmlDescriptor;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * This class adds helper method to describe objects in various formats.
 *
 * @author Jean-FranÃ§ois Simon <contact@jfsimon.fr>
 */
class DescriptorHelper extends Helper
{
    /**
     * @var DescriptorInterface[]
     */
    private $descriptors = [];

    public function __construct()
    {
        $this
            ->register('txt', new TextDescriptor())
            ->register('xml', new XmlDescriptor())
            ->register('json', new JsonDescriptor())
            ->register('md', new MarkdownDescriptor())
        ;
    }

    /**
     * Describes an object if supported.
     *
     * Available options are:
     * * format: string, the output format name
     * * raw_text: boolean, sets output type as raw
     *
     * @param OutputInterface $output
     * @param object          $object
     * @param array           $options
     *
     * @throws InvalidArgumentException when the given format is not supported
     */
    public function describe(OutputInterface $output, $object, array $options = [])
    {
        $options = array_merge([
            'raw_text' => false,
            'format' => 'txt',
        ], $options);

        if (!isset($this->descriptors[$options['format']])) {
            throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $options['format']));
        }

        $descriptor = $this->descriptors[$options['format']];
        $descriptor->describe($output, $object, $options);
    }

    /**
     * Registers a descriptor.
     *
     * @param string              $format
     * @param DescriptorInterface $descriptor
     *
     * @return $this
     */
    public function register($format, DescriptorInterface $descriptor)
    {
        $this->descriptors[$format] = $descriptor;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'descriptor';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\DependencyInjection;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\TypedReference;

/**
 * Registers console commands.
 *
 * @author GrÃ©goire Pineau <lyrixx@lyrixx.info>
 */
class AddConsoleCommandPass implements CompilerPassInterface
{
    private $commandLoaderServiceId;
    private $commandTag;

    public function __construct($commandLoaderServiceId = 'console.command_loader', $commandTag = 'console.command')
    {
        $this->commandLoaderServiceId = $commandLoaderServiceId;
        $this->commandTag = $commandTag;
    }

    public function process(ContainerBuilder $container)
    {
        $commandServices = $container->findTaggedServiceIds($this->commandTag, true);
        $lazyCommandMap = [];
        $lazyCommandRefs = [];
        $serviceIds = [];
        $lazyServiceIds = [];

        foreach ($commandServices as $id => $tags) {
            $definition = $container->getDefinition($id);
            $class = $container->getParameterBag()->resolveValue($definition->getClass());

            $commandId = 'console.command.'.strtolower(str_replace('\\', '_', $class));

            if (isset($tags[0]['command'])) {
                $commandName = $tags[0]['command'];
            } else {
                if (!$r = $container->getReflectionClass($class)) {
                    throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
                }
                if (!$r->isSubclassOf(Command::class)) {
                    throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class));
                }
                $commandName = $class::getDefaultName();
            }

            if (null === $commandName) {
                if (isset($serviceIds[$commandId]) || $container->hasAlias($commandId)) {
                    $commandId = $commandId.'_'.$id;
                }
                if (!$definition->isPublic() || $definition->isPrivate()) {
                    $container->setAlias($commandId, $id)->setPublic(true);
                    $id = $commandId;
                }
                $serviceIds[$commandId] = $id;

                continue;
            }

            $serviceIds[$commandId] = $id;
            $lazyServiceIds[$id] = true;
            unset($tags[0]);
            $lazyCommandMap[$commandName] = $id;
            $lazyCommandRefs[$id] = new TypedReference($id, $class);
            $aliases = [];

            foreach ($tags as $tag) {
                if (isset($tag['command'])) {
                    $aliases[] = $tag['command'];
                    $lazyCommandMap[$tag['command']] = $id;
                }
            }

            $definition->addMethodCall('setName', [$commandName]);

            if ($aliases) {
                $definition->addMethodCall('setAliases', [$aliases]);
            }
        }

        $container
            ->register($this->commandLoaderServiceId, ContainerCommandLoader::class)
            ->setPublic(true)
            ->setArguments([ServiceLocatorTagPass::register($container, $lazyCommandRefs), $lazyCommandMap]);

        $container->setParameter('console.command.ids', $serviceIds);
        $container->setParameter('console.lazy_command.ids', $lazyServiceIds);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * @author JÃ©rÃ´me Tamarelle <jerome@tamarelle.net>
 */
class LogicException extends \LogicException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * Represents an incorrect option name typed in the console.
 *
 * @author JÃ©rÃ´me Tamarelle <jerome@tamarelle.net>
 */
class InvalidOptionException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * ExceptionInterface.
 *
 * @author JÃ©rÃ´me Tamarelle <jerome@tamarelle.net>
 */
interface ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * @author JÃ©rÃ´me Tamarelle <jerome@tamarelle.net>
 */
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * @author JÃ©rÃ´me Tamarelle <jerome@tamarelle.net>
 */
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * Represents an incorrect command name typed in the console.
 *
 * @author JÃ©rÃ´me Tamarelle <jerome@tamarelle.net>
 */
class CommandNotFoundException extends \InvalidArgumentException implements ExceptionInterface
{
    private $alternatives;

    /**
     * @param string     $message      Exception message to throw
     * @param array      $alternatives List of similar defined names
     * @param int        $code         Exception code
     * @param \Exception $previous     Previous exception used for the exception chaining
     */
    public function __construct($message, array $alternatives = [], $code = 0, \Exception $previous = null)
    {
        parent::__construct($message, $code, $previous);

        $this->alternatives = $alternatives;
    }

    /**
     * @return array A list of similar defined names
     */
    public function getAlternatives()
    {
        return $this->alternatives;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Allows to manipulate the exit code of a command after its execution.
 *
 * @author Francesco Levorato <git@flevour.net>
 */
class ConsoleTerminateEvent extends ConsoleEvent
{
    /**
     * The exit code of the command.
     *
     * @var int
     */
    private $exitCode;

    public function __construct(Command $command, InputInterface $input, OutputInterface $output, $exitCode)
    {
        parent::__construct($command, $input, $output);

        $this->setExitCode($exitCode);
    }

    /**
     * Sets the exit code.
     *
     * @param int $exitCode The command exit code
     */
    public function setExitCode($exitCode)
    {
        $this->exitCode = (int) $exitCode;
    }

    /**
     * Gets the exit code.
     *
     * @return int The command exit code
     */
    public function getExitCode()
    {
        return $this->exitCode;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Allows to handle throwables thrown while running a command.
 *
 * @author Wouter de Jong <wouter@wouterj.nl>
 */
final class ConsoleErrorEvent extends ConsoleEvent
{
    private $error;
    private $exitCode;

    public function __construct(InputInterface $input, OutputInterface $output, $error, Command $command = null)
    {
        parent::__construct($command, $input, $output);

        $this->setError($error);
    }

    /**
     * Returns the thrown error/exception.
     *
     * @return \Throwable
     */
    public function getError()
    {
        return $this->error;
    }

    /**
     * Replaces the thrown error/exception.
     *
     * @param \Throwable $error
     */
    public function setError($error)
    {
        if (!$error instanceof \Throwable && !$error instanceof \Exception) {
            throw new InvalidArgumentException(sprintf('The error passed to ConsoleErrorEvent must be an instance of \Throwable or \Exception, "%s" was passed instead.', \is_object($error) ? \get_class($error) : \gettype($error)));
        }

        $this->error = $error;
    }

    /**
     * Sets the exit code.
     *
     * @param int $exitCode The command exit code
     */
    public function setExitCode($exitCode)
    {
        $this->exitCode = (int) $exitCode;

        $r = new \ReflectionProperty($this->error, 'code');
        $r->setAccessible(true);
        $r->setValue($this->error, $this->exitCode);
    }

    /**
     * Gets the exit code.
     *
     * @return int The command exit code
     */
    public function getExitCode()
    {
        return null !== $this->exitCode ? $this->exitCode : (\is_int($this->error->getCode()) && 0 !== $this->error->getCode() ? $this->error->getCode() : 1);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

/**
 * Allows to do things before the command is executed, like skipping the command or changing the input.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ConsoleCommandEvent extends ConsoleEvent
{
    /**
     * The return code for skipped commands, this will also be passed into the terminate event.
     */
    const RETURN_CODE_DISABLED = 113;

    /**
     * Indicates if the command should be run or skipped.
     */
    private $commandShouldRun = true;

    /**
     * Disables the command, so it won't be run.
     *
     * @return bool
     */
    public function disableCommand()
    {
        return $this->commandShouldRun = false;
    }

    /**
     * Enables the command.
     *
     * @return bool
     */
    public function enableCommand()
    {
        return $this->commandShouldRun = true;
    }

    /**
     * Returns true if the command is runnable, false otherwise.
     *
     * @return bool
     */
    public function commandShouldRun()
    {
        return $this->commandShouldRun;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\Event;

/**
 * Allows to inspect input and output of a command.
 *
 * @author Francesco Levorato <git@flevour.net>
 */
class ConsoleEvent extends Event
{
    protected $command;

    private $input;
    private $output;

    public function __construct(Command $command = null, InputInterface $input, OutputInterface $output)
    {
        $this->command = $command;
        $this->input = $input;
        $this->output = $output;
    }

    /**
     * Gets the command that is executed.
     *
     * @return Command|null A Command instance
     */
    public function getCommand()
    {
        return $this->command;
    }

    /**
     * Gets the input instance.
     *
     * @return InputInterface An InputInterface instance
     */
    public function getInput()
    {
        return $this->input;
    }

    /**
     * Gets the output instance.
     *
     * @return OutputInterface An OutputInterface instance
     */
    public function getOutput()
    {
        return $this->output;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

@trigger_error(sprintf('The "%s" class is deprecated since Symfony 3.3 and will be removed in 4.0. Use the ConsoleErrorEvent instead.', ConsoleExceptionEvent::class), E_USER_DEPRECATED);

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Allows to handle exception thrown in a command.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @deprecated since version 3.3, to be removed in 4.0. Use ConsoleErrorEvent instead.
 */
class ConsoleExceptionEvent extends ConsoleEvent
{
    private $exception;
    private $exitCode;

    public function __construct(Command $command, InputInterface $input, OutputInterface $output, \Exception $exception, $exitCode)
    {
        parent::__construct($command, $input, $output);

        $this->setException($exception);
        $this->exitCode = (int) $exitCode;
    }

    /**
     * Returns the thrown exception.
     *
     * @return \Exception The thrown exception
     */
    public function getException()
    {
        return $this->exception;
    }

    /**
     * Replaces the thrown exception.
     *
     * This exception will be thrown if no response is set in the event.
     *
     * @param \Exception $exception The thrown exception
     */
    public function setException(\Exception $exception)
    {
        $this->exception = $exception;
    }

    /**
     * Gets the exit code.
     *
     * @return int The command exit code
     */
    public function getExitCode()
    {
        return $this->exitCode;
    }
}
{
    "name": "symfony/console",
    "type": "library",
    "description": "Symfony Console Component",
    "keywords": [],
    "homepage": "https://symfony.com",
    "license": "MIT",
    "authors": [
        {
            "name": "Fabien Potencier",
            "email": "fabien@symfony.com"
        },
        {
            "name": "Symfony Community",
            "homepage": "https://symfony.com/contributors"
        }
    ],
    "require": {
        "php": "^5.5.9|>=7.0.8",
        "symfony/polyfill-mbstring": "~1.0",
        "symfony/debug": "~2.8|~3.0|~4.0"
    },
    "require-dev": {
        "symfony/config": "~3.3|~4.0",
        "symfony/event-dispatcher": "~2.8|~3.0|~4.0",
        "symfony/dependency-injection": "~3.4|~4.0",
        "symfony/lock": "~3.4|~4.0",
        "symfony/process": "~3.3|~4.0",
        "psr/log": "~1.0"
    },
    "provide": {
        "psr/log-implementation": "1.0"
    },
    "suggest": {
        "symfony/event-dispatcher": "",
        "symfony/lock": "",
        "symfony/process": "",
        "psr/log": "For using the console logger"
    },
    "conflict": {
        "symfony/dependency-injection": "<3.4",
        "symfony/process": "<3.3"
    },
    "autoload": {
        "psr-4": { "Symfony\\Component\\Console\\": "" },
        "exclude-from-classmap": [
            "/Tests/"
        ]
    },
    "minimum-stability": "dev",
    "extra": {
        "branch-alias": {
            "dev-master": "3.4-dev"
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\EventListener;

use Psr\Log\LoggerInterface;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * @author James Halsall <james.t.halsall@googlemail.com>
 * @author Robin Chalas <robin.chalas@gmail.com>
 */
class ErrorListener implements EventSubscriberInterface
{
    private $logger;

    public function __construct(LoggerInterface $logger = null)
    {
        $this->logger = $logger;
    }

    public function onConsoleError(ConsoleErrorEvent $event)
    {
        if (null === $this->logger) {
            return;
        }

        $error = $event->getError();

        if (!$inputString = $this->getInputString($event)) {
            return $this->logger->error('An error occurred while using the console. Message: "{message}"', ['exception' => $error, 'message' => $error->getMessage()]);
        }

        $this->logger->error('Error thrown while running command "{command}". Message: "{message}"', ['exception' => $error, 'command' => $inputString, 'message' => $error->getMessage()]);
    }

    public function onConsoleTerminate(ConsoleTerminateEvent $event)
    {
        if (null === $this->logger) {
            return;
        }

        $exitCode = $event->getExitCode();

        if (0 === $exitCode) {
            return;
        }

        if (!$inputString = $this->getInputString($event)) {
            return $this->logger->debug('The console exited with code "{code}"', ['code' => $exitCode]);
        }

        $this->logger->debug('Command "{command}" exited with code "{code}"', ['command' => $inputString, 'code' => $exitCode]);
    }

    public static function getSubscribedEvents()
    {
        return [
            ConsoleEvents::ERROR => ['onConsoleError', -128],
            ConsoleEvents::TERMINATE => ['onConsoleTerminate', -128],
        ];
    }

    private static function getInputString(ConsoleEvent $event)
    {
        $commandName = $event->getCommand() ? $event->getCommand()->getName() : null;
        $input = $event->getInput();

        if (method_exists($input, '__toString')) {
            if ($commandName) {
                return str_replace(["'$commandName'", "\"$commandName\""], $commandName, (string) $input);
            }

            return (string) $input;
        }

        return $commandName;
    }
}
<?php

return array(
  'A' => 'a',
  'B' => 'b',
  'C' => 'c',
  'D' => 'd',
  'E' => 'e',
  'F' => 'f',
  'G' => 'g',
  'H' => 'h',
  'I' => 'i',
  'J' => 'j',
  'K' => 'k',
  'L' => 'l',
  'M' => 'm',
  'N' => 'n',
  'O' => 'o',
  'P' => 'p',
  'Q' => 'q',
  'R' => 'r',
  'S' => 's',
  'T' => 't',
  'U' => 'u',
  'V' => 'v',
  'W' => 'w',
  'X' => 'x',
  'Y' => 'y',
  'Z' => 'z',
  'Ã€' => 'Ã ',
  'Ã�' => 'Ã¡',
  'Ã‚' => 'Ã¢',
  'Ãƒ' => 'Ã£',
  'Ã„' => 'Ã¤',
  'Ã…' => 'Ã¥',
  'Ã†' => 'Ã¦',
  'Ã‡' => 'Ã§',
  'Ãˆ' => 'Ã¨',
  'Ã‰' => 'Ã©',
  'ÃŠ' => 'Ãª',
  'Ã‹' => 'Ã«',
  'ÃŒ' => 'Ã¬',
  'Ã�' => 'Ã­',
  'ÃŽ' => 'Ã®',
  'Ã�' => 'Ã¯',
  'Ã�' => 'Ã°',
  'Ã‘' => 'Ã±',
  'Ã’' => 'Ã²',
  'Ã“' => 'Ã³',
  'Ã”' => 'Ã´',
  'Ã•' => 'Ãµ',
  'Ã–' => 'Ã¶',
  'Ã˜' => 'Ã¸',
  'Ã™' => 'Ã¹',
  'Ãš' => 'Ãº',
  'Ã›' => 'Ã»',
  'Ãœ' => 'Ã¼',
  'Ã�' => 'Ã½',
  'Ãž' => 'Ã¾',
  'Ä€' => 'Ä�',
  'Ä‚' => 'Äƒ',
  'Ä„' => 'Ä…',
  'Ä†' => 'Ä‡',
  'Äˆ' => 'Ä‰',
  'ÄŠ' => 'Ä‹',
  'ÄŒ' => 'Ä�',
  'ÄŽ' => 'Ä�',
  'Ä�' => 'Ä‘',
  'Ä’' => 'Ä“',
  'Ä”' => 'Ä•',
  'Ä–' => 'Ä—',
  'Ä˜' => 'Ä™',
  'Äš' => 'Ä›',
  'Äœ' => 'Ä�',
  'Äž' => 'ÄŸ',
  'Ä ' => 'Ä¡',
  'Ä¢' => 'Ä£',
  'Ä¤' => 'Ä¥',
  'Ä¦' => 'Ä§',
  'Ä¨' => 'Ä©',
  'Äª' => 'Ä«',
  'Ä¬' => 'Ä­',
  'Ä®' => 'Ä¯',
  'Ä°' => 'i',
  'Ä²' => 'Ä³',
  'Ä´' => 'Äµ',
  'Ä¶' => 'Ä·',
  'Ä¹' => 'Äº',
  'Ä»' => 'Ä¼',
  'Ä½' => 'Ä¾',
  'Ä¿' => 'Å€',
  'Å�' => 'Å‚',
  'Åƒ' => 'Å„',
  'Å…' => 'Å†',
  'Å‡' => 'Åˆ',
  'ÅŠ' => 'Å‹',
  'ÅŒ' => 'Å�',
  'ÅŽ' => 'Å�',
  'Å�' => 'Å‘',
  'Å’' => 'Å“',
  'Å”' => 'Å•',
  'Å–' => 'Å—',
  'Å˜' => 'Å™',
  'Åš' => 'Å›',
  'Åœ' => 'Å�',
  'Åž' => 'ÅŸ',
  'Å ' => 'Å¡',
  'Å¢' => 'Å£',
  'Å¤' => 'Å¥',
  'Å¦' => 'Å§',
  'Å¨' => 'Å©',
  'Åª' => 'Å«',
  'Å¬' => 'Å­',
  'Å®' => 'Å¯',
  'Å°' => 'Å±',
  'Å²' => 'Å³',
  'Å´' => 'Åµ',
  'Å¶' => 'Å·',
  'Å¸' => 'Ã¿',
  'Å¹' => 'Åº',
  'Å»' => 'Å¼',
  'Å½' => 'Å¾',
  'Æ�' => 'É“',
  'Æ‚' => 'Æƒ',
  'Æ„' => 'Æ…',
  'Æ†' => 'É”',
  'Æ‡' => 'Æˆ',
  'Æ‰' => 'É–',
  'ÆŠ' => 'É—',
  'Æ‹' => 'ÆŒ',
  'ÆŽ' => 'Ç�',
  'Æ�' => 'É™',
  'Æ�' => 'É›',
  'Æ‘' => 'Æ’',
  'Æ“' => 'É ',
  'Æ”' => 'É£',
  'Æ–' => 'É©',
  'Æ—' => 'É¨',
  'Æ˜' => 'Æ™',
  'Æœ' => 'É¯',
  'Æ�' => 'É²',
  'ÆŸ' => 'Éµ',
  'Æ ' => 'Æ¡',
  'Æ¢' => 'Æ£',
  'Æ¤' => 'Æ¥',
  'Æ¦' => 'Ê€',
  'Æ§' => 'Æ¨',
  'Æ©' => 'Êƒ',
  'Æ¬' => 'Æ­',
  'Æ®' => 'Êˆ',
  'Æ¯' => 'Æ°',
  'Æ±' => 'ÊŠ',
  'Æ²' => 'Ê‹',
  'Æ³' => 'Æ´',
  'Æµ' => 'Æ¶',
  'Æ·' => 'Ê’',
  'Æ¸' => 'Æ¹',
  'Æ¼' => 'Æ½',
  'Ç„' => 'Ç†',
  'Ç…' => 'Ç†',
  'Ç‡' => 'Ç‰',
  'Çˆ' => 'Ç‰',
  'ÇŠ' => 'ÇŒ',
  'Ç‹' => 'ÇŒ',
  'Ç�' => 'ÇŽ',
  'Ç�' => 'Ç�',
  'Ç‘' => 'Ç’',
  'Ç“' => 'Ç”',
  'Ç•' => 'Ç–',
  'Ç—' => 'Ç˜',
  'Ç™' => 'Çš',
  'Ç›' => 'Çœ',
  'Çž' => 'ÇŸ',
  'Ç ' => 'Ç¡',
  'Ç¢' => 'Ç£',
  'Ç¤' => 'Ç¥',
  'Ç¦' => 'Ç§',
  'Ç¨' => 'Ç©',
  'Çª' => 'Ç«',
  'Ç¬' => 'Ç­',
  'Ç®' => 'Ç¯',
  'Ç±' => 'Ç³',
  'Ç²' => 'Ç³',
  'Ç´' => 'Çµ',
  'Ç¶' => 'Æ•',
  'Ç·' => 'Æ¿',
  'Ç¸' => 'Ç¹',
  'Çº' => 'Ç»',
  'Ç¼' => 'Ç½',
  'Ç¾' => 'Ç¿',
  'È€' => 'È�',
  'È‚' => 'Èƒ',
  'È„' => 'È…',
  'È†' => 'È‡',
  'Èˆ' => 'È‰',
  'ÈŠ' => 'È‹',
  'ÈŒ' => 'È�',
  'ÈŽ' => 'È�',
  'È�' => 'È‘',
  'È’' => 'È“',
  'È”' => 'È•',
  'È–' => 'È—',
  'È˜' => 'È™',
  'Èš' => 'È›',
  'Èœ' => 'È�',
  'Èž' => 'ÈŸ',
  'È ' => 'Æž',
  'È¢' => 'È£',
  'È¤' => 'È¥',
  'È¦' => 'È§',
  'È¨' => 'È©',
  'Èª' => 'È«',
  'È¬' => 'È­',
  'È®' => 'È¯',
  'È°' => 'È±',
  'È²' => 'È³',
  'Èº' => 'â±¥',
  'È»' => 'È¼',
  'È½' => 'Æš',
  'È¾' => 'â±¦',
  'É�' => 'É‚',
  'Éƒ' => 'Æ€',
  'É„' => 'Ê‰',
  'É…' => 'ÊŒ',
  'É†' => 'É‡',
  'Éˆ' => 'É‰',
  'ÉŠ' => 'É‹',
  'ÉŒ' => 'É�',
  'ÉŽ' => 'É�',
  'Í°' => 'Í±',
  'Í²' => 'Í³',
  'Í¶' => 'Í·',
  'Í¿' => 'Ï³',
  'Î†' => 'Î¬',
  'Îˆ' => 'Î­',
  'Î‰' => 'Î®',
  'ÎŠ' => 'Î¯',
  'ÎŒ' => 'ÏŒ',
  'ÎŽ' => 'Ï�',
  'Î�' => 'ÏŽ',
  'Î‘' => 'Î±',
  'Î’' => 'Î²',
  'Î“' => 'Î³',
  'Î”' => 'Î´',
  'Î•' => 'Îµ',
  'Î–' => 'Î¶',
  'Î—' => 'Î·',
  'Î˜' => 'Î¸',
  'Î™' => 'Î¹',
  'Îš' => 'Îº',
  'Î›' => 'Î»',
  'Îœ' => 'Î¼',
  'Î�' => 'Î½',
  'Îž' => 'Î¾',
  'ÎŸ' => 'Î¿',
  'Î ' => 'Ï€',
  'Î¡' => 'Ï�',
  'Î£' => 'Ïƒ',
  'Î¤' => 'Ï„',
  'Î¥' => 'Ï…',
  'Î¦' => 'Ï†',
  'Î§' => 'Ï‡',
  'Î¨' => 'Ïˆ',
  'Î©' => 'Ï‰',
  'Îª' => 'ÏŠ',
  'Î«' => 'Ï‹',
  'Ï�' => 'Ï—',
  'Ï˜' => 'Ï™',
  'Ïš' => 'Ï›',
  'Ïœ' => 'Ï�',
  'Ïž' => 'ÏŸ',
  'Ï ' => 'Ï¡',
  'Ï¢' => 'Ï£',
  'Ï¤' => 'Ï¥',
  'Ï¦' => 'Ï§',
  'Ï¨' => 'Ï©',
  'Ïª' => 'Ï«',
  'Ï¬' => 'Ï­',
  'Ï®' => 'Ï¯',
  'Ï´' => 'Î¸',
  'Ï·' => 'Ï¸',
  'Ï¹' => 'Ï²',
  'Ïº' => 'Ï»',
  'Ï½' => 'Í»',
  'Ï¾' => 'Í¼',
  'Ï¿' => 'Í½',
  'Ð€' => 'Ñ�',
  'Ð�' => 'Ñ‘',
  'Ð‚' => 'Ñ’',
  'Ðƒ' => 'Ñ“',
  'Ð„' => 'Ñ”',
  'Ð…' => 'Ñ•',
  'Ð†' => 'Ñ–',
  'Ð‡' => 'Ñ—',
  'Ðˆ' => 'Ñ˜',
  'Ð‰' => 'Ñ™',
  'ÐŠ' => 'Ñš',
  'Ð‹' => 'Ñ›',
  'ÐŒ' => 'Ñœ',
  'Ð�' => 'Ñ�',
  'ÐŽ' => 'Ñž',
  'Ð�' => 'ÑŸ',
  'Ð�' => 'Ð°',
  'Ð‘' => 'Ð±',
  'Ð’' => 'Ð²',
  'Ð“' => 'Ð³',
  'Ð”' => 'Ð´',
  'Ð•' => 'Ðµ',
  'Ð–' => 'Ð¶',
  'Ð—' => 'Ð·',
  'Ð˜' => 'Ð¸',
  'Ð™' => 'Ð¹',
  'Ðš' => 'Ðº',
  'Ð›' => 'Ð»',
  'Ðœ' => 'Ð¼',
  'Ð�' => 'Ð½',
  'Ðž' => 'Ð¾',
  'ÐŸ' => 'Ð¿',
  'Ð ' => 'Ñ€',
  'Ð¡' => 'Ñ�',
  'Ð¢' => 'Ñ‚',
  'Ð£' => 'Ñƒ',
  'Ð¤' => 'Ñ„',
  'Ð¥' => 'Ñ…',
  'Ð¦' => 'Ñ†',
  'Ð§' => 'Ñ‡',
  'Ð¨' => 'Ñˆ',
  'Ð©' => 'Ñ‰',
  'Ðª' => 'ÑŠ',
  'Ð«' => 'Ñ‹',
  'Ð¬' => 'ÑŒ',
  'Ð­' => 'Ñ�',
  'Ð®' => 'ÑŽ',
  'Ð¯' => 'Ñ�',
  'Ñ ' => 'Ñ¡',
  'Ñ¢' => 'Ñ£',
  'Ñ¤' => 'Ñ¥',
  'Ñ¦' => 'Ñ§',
  'Ñ¨' => 'Ñ©',
  'Ñª' => 'Ñ«',
  'Ñ¬' => 'Ñ­',
  'Ñ®' => 'Ñ¯',
  'Ñ°' => 'Ñ±',
  'Ñ²' => 'Ñ³',
  'Ñ´' => 'Ñµ',
  'Ñ¶' => 'Ñ·',
  'Ñ¸' => 'Ñ¹',
  'Ñº' => 'Ñ»',
  'Ñ¼' => 'Ñ½',
  'Ñ¾' => 'Ñ¿',
  'Ò€' => 'Ò�',
  'ÒŠ' => 'Ò‹',
  'ÒŒ' => 'Ò�',
  'ÒŽ' => 'Ò�',
  'Ò�' => 'Ò‘',
  'Ò’' => 'Ò“',
  'Ò”' => 'Ò•',
  'Ò–' => 'Ò—',
  'Ò˜' => 'Ò™',
  'Òš' => 'Ò›',
  'Òœ' => 'Ò�',
  'Òž' => 'ÒŸ',
  'Ò ' => 'Ò¡',
  'Ò¢' => 'Ò£',
  'Ò¤' => 'Ò¥',
  'Ò¦' => 'Ò§',
  'Ò¨' => 'Ò©',
  'Òª' => 'Ò«',
  'Ò¬' => 'Ò­',
  'Ò®' => 'Ò¯',
  'Ò°' => 'Ò±',
  'Ò²' => 'Ò³',
  'Ò´' => 'Òµ',
  'Ò¶' => 'Ò·',
  'Ò¸' => 'Ò¹',
  'Òº' => 'Ò»',
  'Ò¼' => 'Ò½',
  'Ò¾' => 'Ò¿',
  'Ó€' => 'Ó�',
  'Ó�' => 'Ó‚',
  'Óƒ' => 'Ó„',
  'Ó…' => 'Ó†',
  'Ó‡' => 'Óˆ',
  'Ó‰' => 'ÓŠ',
  'Ó‹' => 'ÓŒ',
  'Ó�' => 'ÓŽ',
  'Ó�' => 'Ó‘',
  'Ó’' => 'Ó“',
  'Ó”' => 'Ó•',
  'Ó–' => 'Ó—',
  'Ó˜' => 'Ó™',
  'Óš' => 'Ó›',
  'Óœ' => 'Ó�',
  'Óž' => 'ÓŸ',
  'Ó ' => 'Ó¡',
  'Ó¢' => 'Ó£',
  'Ó¤' => 'Ó¥',
  'Ó¦' => 'Ó§',
  'Ó¨' => 'Ó©',
  'Óª' => 'Ó«',
  'Ó¬' => 'Ó­',
  'Ó®' => 'Ó¯',
  'Ó°' => 'Ó±',
  'Ó²' => 'Ó³',
  'Ó´' => 'Óµ',
  'Ó¶' => 'Ó·',
  'Ó¸' => 'Ó¹',
  'Óº' => 'Ó»',
  'Ó¼' => 'Ó½',
  'Ó¾' => 'Ó¿',
  'Ô€' => 'Ô�',
  'Ô‚' => 'Ôƒ',
  'Ô„' => 'Ô…',
  'Ô†' => 'Ô‡',
  'Ôˆ' => 'Ô‰',
  'ÔŠ' => 'Ô‹',
  'ÔŒ' => 'Ô�',
  'ÔŽ' => 'Ô�',
  'Ô�' => 'Ô‘',
  'Ô’' => 'Ô“',
  'Ô”' => 'Ô•',
  'Ô–' => 'Ô—',
  'Ô˜' => 'Ô™',
  'Ôš' => 'Ô›',
  'Ôœ' => 'Ô�',
  'Ôž' => 'ÔŸ',
  'Ô ' => 'Ô¡',
  'Ô¢' => 'Ô£',
  'Ô¤' => 'Ô¥',
  'Ô¦' => 'Ô§',
  'Ô¨' => 'Ô©',
  'Ôª' => 'Ô«',
  'Ô¬' => 'Ô­',
  'Ô®' => 'Ô¯',
  'Ô±' => 'Õ¡',
  'Ô²' => 'Õ¢',
  'Ô³' => 'Õ£',
  'Ô´' => 'Õ¤',
  'Ôµ' => 'Õ¥',
  'Ô¶' => 'Õ¦',
  'Ô·' => 'Õ§',
  'Ô¸' => 'Õ¨',
  'Ô¹' => 'Õ©',
  'Ôº' => 'Õª',
  'Ô»' => 'Õ«',
  'Ô¼' => 'Õ¬',
  'Ô½' => 'Õ­',
  'Ô¾' => 'Õ®',
  'Ô¿' => 'Õ¯',
  'Õ€' => 'Õ°',
  'Õ�' => 'Õ±',
  'Õ‚' => 'Õ²',
  'Õƒ' => 'Õ³',
  'Õ„' => 'Õ´',
  'Õ…' => 'Õµ',
  'Õ†' => 'Õ¶',
  'Õ‡' => 'Õ·',
  'Õˆ' => 'Õ¸',
  'Õ‰' => 'Õ¹',
  'ÕŠ' => 'Õº',
  'Õ‹' => 'Õ»',
  'ÕŒ' => 'Õ¼',
  'Õ�' => 'Õ½',
  'ÕŽ' => 'Õ¾',
  'Õ�' => 'Õ¿',
  'Õ�' => 'Ö€',
  'Õ‘' => 'Ö�',
  'Õ’' => 'Ö‚',
  'Õ“' => 'Öƒ',
  'Õ”' => 'Ö„',
  'Õ•' => 'Ö…',
  'Õ–' => 'Ö†',
  'á‚ ' => 'â´€',
  'á‚¡' => 'â´�',
  'á‚¢' => 'â´‚',
  'á‚£' => 'â´ƒ',
  'á‚¤' => 'â´„',
  'á‚¥' => 'â´…',
  'á‚¦' => 'â´†',
  'á‚§' => 'â´‡',
  'á‚¨' => 'â´ˆ',
  'á‚©' => 'â´‰',
  'á‚ª' => 'â´Š',
  'á‚«' => 'â´‹',
  'á‚¬' => 'â´Œ',
  'á‚­' => 'â´�',
  'á‚®' => 'â´Ž',
  'á‚¯' => 'â´�',
  'á‚°' => 'â´�',
  'á‚±' => 'â´‘',
  'á‚²' => 'â´’',
  'á‚³' => 'â´“',
  'á‚´' => 'â´”',
  'á‚µ' => 'â´•',
  'á‚¶' => 'â´–',
  'á‚·' => 'â´—',
  'á‚¸' => 'â´˜',
  'á‚¹' => 'â´™',
  'á‚º' => 'â´š',
  'á‚»' => 'â´›',
  'á‚¼' => 'â´œ',
  'á‚½' => 'â´�',
  'á‚¾' => 'â´ž',
  'á‚¿' => 'â´Ÿ',
  'áƒ€' => 'â´ ',
  'áƒ�' => 'â´¡',
  'áƒ‚' => 'â´¢',
  'áƒƒ' => 'â´£',
  'áƒ„' => 'â´¤',
  'áƒ…' => 'â´¥',
  'áƒ‡' => 'â´§',
  'áƒ�' => 'â´­',
  'á¸€' => 'á¸�',
  'á¸‚' => 'á¸ƒ',
  'á¸„' => 'á¸…',
  'á¸†' => 'á¸‡',
  'á¸ˆ' => 'á¸‰',
  'á¸Š' => 'á¸‹',
  'á¸Œ' => 'á¸�',
  'á¸Ž' => 'á¸�',
  'á¸�' => 'á¸‘',
  'á¸’' => 'á¸“',
  'á¸”' => 'á¸•',
  'á¸–' => 'á¸—',
  'á¸˜' => 'á¸™',
  'á¸š' => 'á¸›',
  'á¸œ' => 'á¸�',
  'á¸ž' => 'á¸Ÿ',
  'á¸ ' => 'á¸¡',
  'á¸¢' => 'á¸£',
  'á¸¤' => 'á¸¥',
  'á¸¦' => 'á¸§',
  'á¸¨' => 'á¸©',
  'á¸ª' => 'á¸«',
  'á¸¬' => 'á¸­',
  'á¸®' => 'á¸¯',
  'á¸°' => 'á¸±',
  'á¸²' => 'á¸³',
  'á¸´' => 'á¸µ',
  'á¸¶' => 'á¸·',
  'á¸¸' => 'á¸¹',
  'á¸º' => 'á¸»',
  'á¸¼' => 'á¸½',
  'á¸¾' => 'á¸¿',
  'á¹€' => 'á¹�',
  'á¹‚' => 'á¹ƒ',
  'á¹„' => 'á¹…',
  'á¹†' => 'á¹‡',
  'á¹ˆ' => 'á¹‰',
  'á¹Š' => 'á¹‹',
  'á¹Œ' => 'á¹�',
  'á¹Ž' => 'á¹�',
  'á¹�' => 'á¹‘',
  'á¹’' => 'á¹“',
  'á¹”' => 'á¹•',
  'á¹–' => 'á¹—',
  'á¹˜' => 'á¹™',
  'á¹š' => 'á¹›',
  'á¹œ' => 'á¹�',
  'á¹ž' => 'á¹Ÿ',
  'á¹ ' => 'á¹¡',
  'á¹¢' => 'á¹£',
  'á¹¤' => 'á¹¥',
  'á¹¦' => 'á¹§',
  'á¹¨' => 'á¹©',
  'á¹ª' => 'á¹«',
  'á¹¬' => 'á¹­',
  'á¹®' => 'á¹¯',
  'á¹°' => 'á¹±',
  'á¹²' => 'á¹³',
  'á¹´' => 'á¹µ',
  'á¹¶' => 'á¹·',
  'á¹¸' => 'á¹¹',
  'á¹º' => 'á¹»',
  'á¹¼' => 'á¹½',
  'á¹¾' => 'á¹¿',
  'áº€' => 'áº�',
  'áº‚' => 'áºƒ',
  'áº„' => 'áº…',
  'áº†' => 'áº‡',
  'áºˆ' => 'áº‰',
  'áºŠ' => 'áº‹',
  'áºŒ' => 'áº�',
  'áºŽ' => 'áº�',
  'áº�' => 'áº‘',
  'áº’' => 'áº“',
  'áº”' => 'áº•',
  'áºž' => 'ÃŸ',
  'áº ' => 'áº¡',
  'áº¢' => 'áº£',
  'áº¤' => 'áº¥',
  'áº¦' => 'áº§',
  'áº¨' => 'áº©',
  'áºª' => 'áº«',
  'áº¬' => 'áº­',
  'áº®' => 'áº¯',
  'áº°' => 'áº±',
  'áº²' => 'áº³',
  'áº´' => 'áºµ',
  'áº¶' => 'áº·',
  'áº¸' => 'áº¹',
  'áºº' => 'áº»',
  'áº¼' => 'áº½',
  'áº¾' => 'áº¿',
  'á»€' => 'á»�',
  'á»‚' => 'á»ƒ',
  'á»„' => 'á»…',
  'á»†' => 'á»‡',
  'á»ˆ' => 'á»‰',
  'á»Š' => 'á»‹',
  'á»Œ' => 'á»�',
  'á»Ž' => 'á»�',
  'á»�' => 'á»‘',
  'á»’' => 'á»“',
  'á»”' => 'á»•',
  'á»–' => 'á»—',
  'á»˜' => 'á»™',
  'á»š' => 'á»›',
  'á»œ' => 'á»�',
  'á»ž' => 'á»Ÿ',
  'á» ' => 'á»¡',
  'á»¢' => 'á»£',
  'á»¤' => 'á»¥',
  'á»¦' => 'á»§',
  'á»¨' => 'á»©',
  'á»ª' => 'á»«',
  'á»¬' => 'á»­',
  'á»®' => 'á»¯',
  'á»°' => 'á»±',
  'á»²' => 'á»³',
  'á»´' => 'á»µ',
  'á»¶' => 'á»·',
  'á»¸' => 'á»¹',
  'á»º' => 'á»»',
  'á»¼' => 'á»½',
  'á»¾' => 'á»¿',
  'á¼ˆ' => 'á¼€',
  'á¼‰' => 'á¼�',
  'á¼Š' => 'á¼‚',
  'á¼‹' => 'á¼ƒ',
  'á¼Œ' => 'á¼„',
  'á¼�' => 'á¼…',
  'á¼Ž' => 'á¼†',
  'á¼�' => 'á¼‡',
  'á¼˜' => 'á¼�',
  'á¼™' => 'á¼‘',
  'á¼š' => 'á¼’',
  'á¼›' => 'á¼“',
  'á¼œ' => 'á¼”',
  'á¼�' => 'á¼•',
  'á¼¨' => 'á¼ ',
  'á¼©' => 'á¼¡',
  'á¼ª' => 'á¼¢',
  'á¼«' => 'á¼£',
  'á¼¬' => 'á¼¤',
  'á¼­' => 'á¼¥',
  'á¼®' => 'á¼¦',
  'á¼¯' => 'á¼§',
  'á¼¸' => 'á¼°',
  'á¼¹' => 'á¼±',
  'á¼º' => 'á¼²',
  'á¼»' => 'á¼³',
  'á¼¼' => 'á¼´',
  'á¼½' => 'á¼µ',
  'á¼¾' => 'á¼¶',
  'á¼¿' => 'á¼·',
  'á½ˆ' => 'á½€',
  'á½‰' => 'á½�',
  'á½Š' => 'á½‚',
  'á½‹' => 'á½ƒ',
  'á½Œ' => 'á½„',
  'á½�' => 'á½…',
  'á½™' => 'á½‘',
  'á½›' => 'á½“',
  'á½�' => 'á½•',
  'á½Ÿ' => 'á½—',
  'á½¨' => 'á½ ',
  'á½©' => 'á½¡',
  'á½ª' => 'á½¢',
  'á½«' => 'á½£',
  'á½¬' => 'á½¤',
  'á½­' => 'á½¥',
  'á½®' => 'á½¦',
  'á½¯' => 'á½§',
  'á¾ˆ' => 'á¾€',
  'á¾‰' => 'á¾�',
  'á¾Š' => 'á¾‚',
  'á¾‹' => 'á¾ƒ',
  'á¾Œ' => 'á¾„',
  'á¾�' => 'á¾…',
  'á¾Ž' => 'á¾†',
  'á¾�' => 'á¾‡',
  'á¾˜' => 'á¾�',
  'á¾™' => 'á¾‘',
  'á¾š' => 'á¾’',
  'á¾›' => 'á¾“',
  'á¾œ' => 'á¾”',
  'á¾�' => 'á¾•',
  'á¾ž' => 'á¾–',
  'á¾Ÿ' => 'á¾—',
  'á¾¨' => 'á¾ ',
  'á¾©' => 'á¾¡',
  'á¾ª' => 'á¾¢',
  'á¾«' => 'á¾£',
  'á¾¬' => 'á¾¤',
  'á¾­' => 'á¾¥',
  'á¾®' => 'á¾¦',
  'á¾¯' => 'á¾§',
  'á¾¸' => 'á¾°',
  'á¾¹' => 'á¾±',
  'á¾º' => 'á½°',
  'á¾»' => 'á½±',
  'á¾¼' => 'á¾³',
  'á¿ˆ' => 'á½²',
  'á¿‰' => 'á½³',
  'á¿Š' => 'á½´',
  'á¿‹' => 'á½µ',
  'á¿Œ' => 'á¿ƒ',
  'á¿˜' => 'á¿�',
  'á¿™' => 'á¿‘',
  'á¿š' => 'á½¶',
  'á¿›' => 'á½·',
  'á¿¨' => 'á¿ ',
  'á¿©' => 'á¿¡',
  'á¿ª' => 'á½º',
  'á¿«' => 'á½»',
  'á¿¬' => 'á¿¥',
  'á¿¸' => 'á½¸',
  'á¿¹' => 'á½¹',
  'á¿º' => 'á½¼',
  'á¿»' => 'á½½',
  'á¿¼' => 'á¿³',
  'â„¦' => 'Ï‰',
  'â„ª' => 'k',
  'â„«' => 'Ã¥',
  'â„²' => 'â…Ž',
  'â… ' => 'â…°',
  'â…¡' => 'â…±',
  'â…¢' => 'â…²',
  'â…£' => 'â…³',
  'â…¤' => 'â…´',
  'â…¥' => 'â…µ',
  'â…¦' => 'â…¶',
  'â…§' => 'â…·',
  'â…¨' => 'â…¸',
  'â…©' => 'â…¹',
  'â…ª' => 'â…º',
  'â…«' => 'â…»',
  'â…¬' => 'â…¼',
  'â…­' => 'â…½',
  'â…®' => 'â…¾',
  'â…¯' => 'â…¿',
  'â†ƒ' => 'â†„',
  'â’¶' => 'â“�',
  'â’·' => 'â“‘',
  'â’¸' => 'â“’',
  'â’¹' => 'â““',
  'â’º' => 'â“”',
  'â’»' => 'â“•',
  'â’¼' => 'â“–',
  'â’½' => 'â“—',
  'â’¾' => 'â“˜',
  'â’¿' => 'â“™',
  'â“€' => 'â“š',
  'â“�' => 'â“›',
  'â“‚' => 'â“œ',
  'â“ƒ' => 'â“�',
  'â“„' => 'â“ž',
  'â“…' => 'â“Ÿ',
  'â“†' => 'â“ ',
  'â“‡' => 'â“¡',
  'â“ˆ' => 'â“¢',
  'â“‰' => 'â“£',
  'â“Š' => 'â“¤',
  'â“‹' => 'â“¥',
  'â“Œ' => 'â“¦',
  'â“�' => 'â“§',
  'â“Ž' => 'â“¨',
  'â“�' => 'â“©',
  'â°€' => 'â°°',
  'â°�' => 'â°±',
  'â°‚' => 'â°²',
  'â°ƒ' => 'â°³',
  'â°„' => 'â°´',
  'â°…' => 'â°µ',
  'â°†' => 'â°¶',
  'â°‡' => 'â°·',
  'â°ˆ' => 'â°¸',
  'â°‰' => 'â°¹',
  'â°Š' => 'â°º',
  'â°‹' => 'â°»',
  'â°Œ' => 'â°¼',
  'â°�' => 'â°½',
  'â°Ž' => 'â°¾',
  'â°�' => 'â°¿',
  'â°�' => 'â±€',
  'â°‘' => 'â±�',
  'â°’' => 'â±‚',
  'â°“' => 'â±ƒ',
  'â°”' => 'â±„',
  'â°•' => 'â±…',
  'â°–' => 'â±†',
  'â°—' => 'â±‡',
  'â°˜' => 'â±ˆ',
  'â°™' => 'â±‰',
  'â°š' => 'â±Š',
  'â°›' => 'â±‹',
  'â°œ' => 'â±Œ',
  'â°�' => 'â±�',
  'â°ž' => 'â±Ž',
  'â°Ÿ' => 'â±�',
  'â° ' => 'â±�',
  'â°¡' => 'â±‘',
  'â°¢' => 'â±’',
  'â°£' => 'â±“',
  'â°¤' => 'â±”',
  'â°¥' => 'â±•',
  'â°¦' => 'â±–',
  'â°§' => 'â±—',
  'â°¨' => 'â±˜',
  'â°©' => 'â±™',
  'â°ª' => 'â±š',
  'â°«' => 'â±›',
  'â°¬' => 'â±œ',
  'â°­' => 'â±�',
  'â°®' => 'â±ž',
  'â± ' => 'â±¡',
  'â±¢' => 'É«',
  'â±£' => 'áµ½',
  'â±¤' => 'É½',
  'â±§' => 'â±¨',
  'â±©' => 'â±ª',
  'â±«' => 'â±¬',
  'â±­' => 'É‘',
  'â±®' => 'É±',
  'â±¯' => 'É�',
  'â±°' => 'É’',
  'â±²' => 'â±³',
  'â±µ' => 'â±¶',
  'â±¾' => 'È¿',
  'â±¿' => 'É€',
  'â²€' => 'â²�',
  'â²‚' => 'â²ƒ',
  'â²„' => 'â²…',
  'â²†' => 'â²‡',
  'â²ˆ' => 'â²‰',
  'â²Š' => 'â²‹',
  'â²Œ' => 'â²�',
  'â²Ž' => 'â²�',
  'â²�' => 'â²‘',
  'â²’' => 'â²“',
  'â²”' => 'â²•',
  'â²–' => 'â²—',
  'â²˜' => 'â²™',
  'â²š' => 'â²›',
  'â²œ' => 'â²�',
  'â²ž' => 'â²Ÿ',
  'â² ' => 'â²¡',
  'â²¢' => 'â²£',
  'â²¤' => 'â²¥',
  'â²¦' => 'â²§',
  'â²¨' => 'â²©',
  'â²ª' => 'â²«',
  'â²¬' => 'â²­',
  'â²®' => 'â²¯',
  'â²°' => 'â²±',
  'â²²' => 'â²³',
  'â²´' => 'â²µ',
  'â²¶' => 'â²·',
  'â²¸' => 'â²¹',
  'â²º' => 'â²»',
  'â²¼' => 'â²½',
  'â²¾' => 'â²¿',
  'â³€' => 'â³�',
  'â³‚' => 'â³ƒ',
  'â³„' => 'â³…',
  'â³†' => 'â³‡',
  'â³ˆ' => 'â³‰',
  'â³Š' => 'â³‹',
  'â³Œ' => 'â³�',
  'â³Ž' => 'â³�',
  'â³�' => 'â³‘',
  'â³’' => 'â³“',
  'â³”' => 'â³•',
  'â³–' => 'â³—',
  'â³˜' => 'â³™',
  'â³š' => 'â³›',
  'â³œ' => 'â³�',
  'â³ž' => 'â³Ÿ',
  'â³ ' => 'â³¡',
  'â³¢' => 'â³£',
  'â³«' => 'â³¬',
  'â³­' => 'â³®',
  'â³²' => 'â³³',
  'ê™€' => 'ê™�',
  'ê™‚' => 'ê™ƒ',
  'ê™„' => 'ê™…',
  'ê™†' => 'ê™‡',
  'ê™ˆ' => 'ê™‰',
  'ê™Š' => 'ê™‹',
  'ê™Œ' => 'ê™�',
  'ê™Ž' => 'ê™�',
  'ê™�' => 'ê™‘',
  'ê™’' => 'ê™“',
  'ê™”' => 'ê™•',
  'ê™–' => 'ê™—',
  'ê™˜' => 'ê™™',
  'ê™š' => 'ê™›',
  'ê™œ' => 'ê™�',
  'ê™ž' => 'ê™Ÿ',
  'ê™ ' => 'ê™¡',
  'ê™¢' => 'ê™£',
  'ê™¤' => 'ê™¥',
  'ê™¦' => 'ê™§',
  'ê™¨' => 'ê™©',
  'ê™ª' => 'ê™«',
  'ê™¬' => 'ê™­',
  'êš€' => 'êš�',
  'êš‚' => 'êšƒ',
  'êš„' => 'êš…',
  'êš†' => 'êš‡',
  'êšˆ' => 'êš‰',
  'êšŠ' => 'êš‹',
  'êšŒ' => 'êš�',
  'êšŽ' => 'êš�',
  'êš�' => 'êš‘',
  'êš’' => 'êš“',
  'êš”' => 'êš•',
  'êš–' => 'êš—',
  'êš˜' => 'êš™',
  'êšš' => 'êš›',
  'êœ¢' => 'êœ£',
  'êœ¤' => 'êœ¥',
  'êœ¦' => 'êœ§',
  'êœ¨' => 'êœ©',
  'êœª' => 'êœ«',
  'êœ¬' => 'êœ­',
  'êœ®' => 'êœ¯',
  'êœ²' => 'êœ³',
  'êœ´' => 'êœµ',
  'êœ¶' => 'êœ·',
  'êœ¸' => 'êœ¹',
  'êœº' => 'êœ»',
  'êœ¼' => 'êœ½',
  'êœ¾' => 'êœ¿',
  'ê�€' => 'ê��',
  'ê�‚' => 'ê�ƒ',
  'ê�„' => 'ê�…',
  'ê�†' => 'ê�‡',
  'ê�ˆ' => 'ê�‰',
  'ê�Š' => 'ê�‹',
  'ê�Œ' => 'ê��',
  'ê�Ž' => 'ê��',
  'ê��' => 'ê�‘',
  'ê�’' => 'ê�“',
  'ê�”' => 'ê�•',
  'ê�–' => 'ê�—',
  'ê�˜' => 'ê�™',
  'ê�š' => 'ê�›',
  'ê�œ' => 'ê��',
  'ê�ž' => 'ê�Ÿ',
  'ê� ' => 'ê�¡',
  'ê�¢' => 'ê�£',
  'ê�¤' => 'ê�¥',
  'ê�¦' => 'ê�§',
  'ê�¨' => 'ê�©',
  'ê�ª' => 'ê�«',
  'ê�¬' => 'ê�­',
  'ê�®' => 'ê�¯',
  'ê�¹' => 'ê�º',
  'ê�»' => 'ê�¼',
  'ê�½' => 'áµ¹',
  'ê�¾' => 'ê�¿',
  'êž€' => 'êž�',
  'êž‚' => 'êžƒ',
  'êž„' => 'êž…',
  'êž†' => 'êž‡',
  'êž‹' => 'êžŒ',
  'êž�' => 'É¥',
  'êž�' => 'êž‘',
  'êž’' => 'êž“',
  'êž–' => 'êž—',
  'êž˜' => 'êž™',
  'êžš' => 'êž›',
  'êžœ' => 'êž�',
  'êžž' => 'êžŸ',
  'êž ' => 'êž¡',
  'êž¢' => 'êž£',
  'êž¤' => 'êž¥',
  'êž¦' => 'êž§',
  'êž¨' => 'êž©',
  'êžª' => 'É¦',
  'êž«' => 'Éœ',
  'êž¬' => 'É¡',
  'êž­' => 'É¬',
  'êž°' => 'Êž',
  'êž±' => 'Ê‡',
  'ï¼¡' => 'ï½�',
  'ï¼¢' => 'ï½‚',
  'ï¼£' => 'ï½ƒ',
  'ï¼¤' => 'ï½„',
  'ï¼¥' => 'ï½…',
  'ï¼¦' => 'ï½†',
  'ï¼§' => 'ï½‡',
  'ï¼¨' => 'ï½ˆ',
  'ï¼©' => 'ï½‰',
  'ï¼ª' => 'ï½Š',
  'ï¼«' => 'ï½‹',
  'ï¼¬' => 'ï½Œ',
  'ï¼­' => 'ï½�',
  'ï¼®' => 'ï½Ž',
  'ï¼¯' => 'ï½�',
  'ï¼°' => 'ï½�',
  'ï¼±' => 'ï½‘',
  'ï¼²' => 'ï½’',
  'ï¼³' => 'ï½“',
  'ï¼´' => 'ï½”',
  'ï¼µ' => 'ï½•',
  'ï¼¶' => 'ï½–',
  'ï¼·' => 'ï½—',
  'ï¼¸' => 'ï½˜',
  'ï¼¹' => 'ï½™',
  'ï¼º' => 'ï½š',
  'ð��€' => 'ð��¨',
  'ð���' => 'ð��©',
  'ð��‚' => 'ð��ª',
  'ð��ƒ' => 'ð��«',
  'ð��„' => 'ð��¬',
  'ð��…' => 'ð��­',
  'ð��†' => 'ð��®',
  'ð��‡' => 'ð��¯',
  'ð��ˆ' => 'ð��°',
  'ð��‰' => 'ð��±',
  'ð��Š' => 'ð��²',
  'ð��‹' => 'ð��³',
  'ð��Œ' => 'ð��´',
  'ð���' => 'ð��µ',
  'ð��Ž' => 'ð��¶',
  'ð���' => 'ð��·',
  'ð���' => 'ð��¸',
  'ð��‘' => 'ð��¹',
  'ð��’' => 'ð��º',
  'ð��“' => 'ð��»',
  'ð��”' => 'ð��¼',
  'ð��•' => 'ð��½',
  'ð��–' => 'ð��¾',
  'ð��—' => 'ð��¿',
  'ð��˜' => 'ð�‘€',
  'ð��™' => 'ð�‘�',
  'ð��š' => 'ð�‘‚',
  'ð��›' => 'ð�‘ƒ',
  'ð��œ' => 'ð�‘„',
  'ð���' => 'ð�‘…',
  'ð��ž' => 'ð�‘†',
  'ð��Ÿ' => 'ð�‘‡',
  'ð�� ' => 'ð�‘ˆ',
  'ð��¡' => 'ð�‘‰',
  'ð��¢' => 'ð�‘Š',
  'ð��£' => 'ð�‘‹',
  'ð��¤' => 'ð�‘Œ',
  'ð��¥' => 'ð�‘�',
  'ð��¦' => 'ð�‘Ž',
  'ð��§' => 'ð�‘�',
  'ð‘¢ ' => 'ð‘£€',
  'ð‘¢¡' => 'ð‘£�',
  'ð‘¢¢' => 'ð‘£‚',
  'ð‘¢£' => 'ð‘£ƒ',
  'ð‘¢¤' => 'ð‘£„',
  'ð‘¢¥' => 'ð‘£…',
  'ð‘¢¦' => 'ð‘£†',
  'ð‘¢§' => 'ð‘£‡',
  'ð‘¢¨' => 'ð‘£ˆ',
  'ð‘¢©' => 'ð‘£‰',
  'ð‘¢ª' => 'ð‘£Š',
  'ð‘¢«' => 'ð‘£‹',
  'ð‘¢¬' => 'ð‘£Œ',
  'ð‘¢­' => 'ð‘£�',
  'ð‘¢®' => 'ð‘£Ž',
  'ð‘¢¯' => 'ð‘£�',
  'ð‘¢°' => 'ð‘£�',
  'ð‘¢±' => 'ð‘£‘',
  'ð‘¢²' => 'ð‘£’',
  'ð‘¢³' => 'ð‘£“',
  'ð‘¢´' => 'ð‘£”',
  'ð‘¢µ' => 'ð‘£•',
  'ð‘¢¶' => 'ð‘£–',
  'ð‘¢·' => 'ð‘£—',
  'ð‘¢¸' => 'ð‘£˜',
  'ð‘¢¹' => 'ð‘£™',
  'ð‘¢º' => 'ð‘£š',
  'ð‘¢»' => 'ð‘£›',
  'ð‘¢¼' => 'ð‘£œ',
  'ð‘¢½' => 'ð‘£�',
  'ð‘¢¾' => 'ð‘£ž',
  'ð‘¢¿' => 'ð‘£Ÿ',
);
<?php

// from Case_Ignorable in https://unicode.org/Public/UNIDATA/DerivedCoreProperties.txt

return '/(?<![\x{0027}\x{002E}\x{003A}\x{005E}\x{0060}\x{00A8}\x{00AD}\x{00AF}\x{00B4}\x{00B7}\x{00B8}\x{02B0}-\x{02C1}\x{02C2}-\x{02C5}\x{02C6}-\x{02D1}\x{02D2}-\x{02DF}\x{02E0}-\x{02E4}\x{02E5}-\x{02EB}\x{02EC}\x{02ED}\x{02EE}\x{02EF}-\x{02FF}\x{0300}-\x{036F}\x{0374}\x{0375}\x{037A}\x{0384}-\x{0385}\x{0387}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{0559}\x{0591}-\x{05BD}\x{05BF}\x{05C1}-\x{05C2}\x{05C4}-\x{05C5}\x{05C7}\x{05F4}\x{0600}-\x{0605}\x{0610}-\x{061A}\x{061C}\x{0640}\x{064B}-\x{065F}\x{0670}\x{06D6}-\x{06DC}\x{06DD}\x{06DF}-\x{06E4}\x{06E5}-\x{06E6}\x{06E7}-\x{06E8}\x{06EA}-\x{06ED}\x{070F}\x{0711}\x{0730}-\x{074A}\x{07A6}-\x{07B0}\x{07EB}-\x{07F3}\x{07F4}-\x{07F5}\x{07FA}\x{07FD}\x{0816}-\x{0819}\x{081A}\x{081B}-\x{0823}\x{0824}\x{0825}-\x{0827}\x{0828}\x{0829}-\x{082D}\x{0859}-\x{085B}\x{08D3}-\x{08E1}\x{08E2}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0971}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0C00}\x{0C04}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C81}\x{0CBC}\x{0CBF}\x{0CC6}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E46}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EB9}\x{0EBB}-\x{0EBC}\x{0EC6}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{10FC}\x{135D}-\x{135F}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17D7}\x{17DD}\x{180B}-\x{180D}\x{180E}\x{1843}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AA7}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1C78}-\x{1C7D}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1D2C}-\x{1D6A}\x{1D78}\x{1D9B}-\x{1DBF}\x{1DC0}-\x{1DF9}\x{1DFB}-\x{1DFF}\x{1FBD}\x{1FBF}-\x{1FC1}\x{1FCD}-\x{1FCF}\x{1FDD}-\x{1FDF}\x{1FED}-\x{1FEF}\x{1FFD}-\x{1FFE}\x{200B}-\x{200F}\x{2018}\x{2019}\x{2024}\x{2027}\x{202A}-\x{202E}\x{2060}-\x{2064}\x{2066}-\x{206F}\x{2071}\x{207F}\x{2090}-\x{209C}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2C7C}-\x{2C7D}\x{2CEF}-\x{2CF1}\x{2D6F}\x{2D7F}\x{2DE0}-\x{2DFF}\x{2E2F}\x{3005}\x{302A}-\x{302D}\x{3031}-\x{3035}\x{303B}\x{3099}-\x{309A}\x{309B}-\x{309C}\x{309D}-\x{309E}\x{30FC}-\x{30FE}\x{A015}\x{A4F8}-\x{A4FD}\x{A60C}\x{A66F}\x{A670}-\x{A672}\x{A674}-\x{A67D}\x{A67F}\x{A69C}-\x{A69D}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A700}-\x{A716}\x{A717}-\x{A71F}\x{A720}-\x{A721}\x{A770}\x{A788}\x{A789}-\x{A78A}\x{A7F8}-\x{A7F9}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}\x{A9CF}\x{A9E5}\x{A9E6}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA70}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AADD}\x{AAEC}-\x{AAED}\x{AAF3}-\x{AAF4}\x{AAF6}\x{AB5B}\x{AB5C}-\x{AB5F}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1E}\x{FBB2}-\x{FBC1}\x{FE00}-\x{FE0F}\x{FE13}\x{FE20}-\x{FE2F}\x{FE52}\x{FE55}\x{FEFF}\x{FF07}\x{FF0E}\x{FF1A}\x{FF3E}\x{FF40}\x{FF70}\x{FF9E}-\x{FF9F}\x{FFE3}\x{FFF9}-\x{FFFB}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10A01}-\x{10A03}\x{10A05}-\x{10A06}\x{10A0C}-\x{10A0F}\x{10A38}-\x{10A3A}\x{10A3F}\x{10AE5}-\x{10AE6}\x{10D24}-\x{10D27}\x{10F46}-\x{10F50}\x{11001}\x{11038}-\x{11046}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{110BD}\x{110CD}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{11A01}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C3F}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16B40}-\x{16B43}\x{16F8F}-\x{16F92}\x{16F93}-\x{16F9F}\x{16FE0}-\x{16FE1}\x{1BC9D}-\x{1BC9E}\x{1BCA0}-\x{1BCA3}\x{1D167}-\x{1D169}\x{1D173}-\x{1D17A}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D242}-\x{1D244}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E8D0}-\x{1E8D6}\x{1E944}-\x{1E94A}\x{1F3FB}-\x{1F3FF}\x{E0001}\x{E0020}-\x{E007F}\x{E0100}-\x{E01EF}])(\pL)(\pL*+)/u';
<?php

return array(
  'a' => 'A',
  'b' => 'B',
  'c' => 'C',
  'd' => 'D',
  'e' => 'E',
  'f' => 'F',
  'g' => 'G',
  'h' => 'H',
  'i' => 'I',
  'j' => 'J',
  'k' => 'K',
  'l' => 'L',
  'm' => 'M',
  'n' => 'N',
  'o' => 'O',
  'p' => 'P',
  'q' => 'Q',
  'r' => 'R',
  's' => 'S',
  't' => 'T',
  'u' => 'U',
  'v' => 'V',
  'w' => 'W',
  'x' => 'X',
  'y' => 'Y',
  'z' => 'Z',
  'Âµ' => 'Îœ',
  'Ã ' => 'Ã€',
  'Ã¡' => 'Ã�',
  'Ã¢' => 'Ã‚',
  'Ã£' => 'Ãƒ',
  'Ã¤' => 'Ã„',
  'Ã¥' => 'Ã…',
  'Ã¦' => 'Ã†',
  'Ã§' => 'Ã‡',
  'Ã¨' => 'Ãˆ',
  'Ã©' => 'Ã‰',
  'Ãª' => 'ÃŠ',
  'Ã«' => 'Ã‹',
  'Ã¬' => 'ÃŒ',
  'Ã­' => 'Ã�',
  'Ã®' => 'ÃŽ',
  'Ã¯' => 'Ã�',
  'Ã°' => 'Ã�',
  'Ã±' => 'Ã‘',
  'Ã²' => 'Ã’',
  'Ã³' => 'Ã“',
  'Ã´' => 'Ã”',
  'Ãµ' => 'Ã•',
  'Ã¶' => 'Ã–',
  'Ã¸' => 'Ã˜',
  'Ã¹' => 'Ã™',
  'Ãº' => 'Ãš',
  'Ã»' => 'Ã›',
  'Ã¼' => 'Ãœ',
  'Ã½' => 'Ã�',
  'Ã¾' => 'Ãž',
  'Ã¿' => 'Å¸',
  'Ä�' => 'Ä€',
  'Äƒ' => 'Ä‚',
  'Ä…' => 'Ä„',
  'Ä‡' => 'Ä†',
  'Ä‰' => 'Äˆ',
  'Ä‹' => 'ÄŠ',
  'Ä�' => 'ÄŒ',
  'Ä�' => 'ÄŽ',
  'Ä‘' => 'Ä�',
  'Ä“' => 'Ä’',
  'Ä•' => 'Ä”',
  'Ä—' => 'Ä–',
  'Ä™' => 'Ä˜',
  'Ä›' => 'Äš',
  'Ä�' => 'Äœ',
  'ÄŸ' => 'Äž',
  'Ä¡' => 'Ä ',
  'Ä£' => 'Ä¢',
  'Ä¥' => 'Ä¤',
  'Ä§' => 'Ä¦',
  'Ä©' => 'Ä¨',
  'Ä«' => 'Äª',
  'Ä­' => 'Ä¬',
  'Ä¯' => 'Ä®',
  'Ä±' => 'I',
  'Ä³' => 'Ä²',
  'Äµ' => 'Ä´',
  'Ä·' => 'Ä¶',
  'Äº' => 'Ä¹',
  'Ä¼' => 'Ä»',
  'Ä¾' => 'Ä½',
  'Å€' => 'Ä¿',
  'Å‚' => 'Å�',
  'Å„' => 'Åƒ',
  'Å†' => 'Å…',
  'Åˆ' => 'Å‡',
  'Å‹' => 'ÅŠ',
  'Å�' => 'ÅŒ',
  'Å�' => 'ÅŽ',
  'Å‘' => 'Å�',
  'Å“' => 'Å’',
  'Å•' => 'Å”',
  'Å—' => 'Å–',
  'Å™' => 'Å˜',
  'Å›' => 'Åš',
  'Å�' => 'Åœ',
  'ÅŸ' => 'Åž',
  'Å¡' => 'Å ',
  'Å£' => 'Å¢',
  'Å¥' => 'Å¤',
  'Å§' => 'Å¦',
  'Å©' => 'Å¨',
  'Å«' => 'Åª',
  'Å­' => 'Å¬',
  'Å¯' => 'Å®',
  'Å±' => 'Å°',
  'Å³' => 'Å²',
  'Åµ' => 'Å´',
  'Å·' => 'Å¶',
  'Åº' => 'Å¹',
  'Å¼' => 'Å»',
  'Å¾' => 'Å½',
  'Å¿' => 'S',
  'Æ€' => 'Éƒ',
  'Æƒ' => 'Æ‚',
  'Æ…' => 'Æ„',
  'Æˆ' => 'Æ‡',
  'ÆŒ' => 'Æ‹',
  'Æ’' => 'Æ‘',
  'Æ•' => 'Ç¶',
  'Æ™' => 'Æ˜',
  'Æš' => 'È½',
  'Æž' => 'È ',
  'Æ¡' => 'Æ ',
  'Æ£' => 'Æ¢',
  'Æ¥' => 'Æ¤',
  'Æ¨' => 'Æ§',
  'Æ­' => 'Æ¬',
  'Æ°' => 'Æ¯',
  'Æ´' => 'Æ³',
  'Æ¶' => 'Æµ',
  'Æ¹' => 'Æ¸',
  'Æ½' => 'Æ¼',
  'Æ¿' => 'Ç·',
  'Ç…' => 'Ç„',
  'Ç†' => 'Ç„',
  'Çˆ' => 'Ç‡',
  'Ç‰' => 'Ç‡',
  'Ç‹' => 'ÇŠ',
  'ÇŒ' => 'ÇŠ',
  'ÇŽ' => 'Ç�',
  'Ç�' => 'Ç�',
  'Ç’' => 'Ç‘',
  'Ç”' => 'Ç“',
  'Ç–' => 'Ç•',
  'Ç˜' => 'Ç—',
  'Çš' => 'Ç™',
  'Çœ' => 'Ç›',
  'Ç�' => 'ÆŽ',
  'ÇŸ' => 'Çž',
  'Ç¡' => 'Ç ',
  'Ç£' => 'Ç¢',
  'Ç¥' => 'Ç¤',
  'Ç§' => 'Ç¦',
  'Ç©' => 'Ç¨',
  'Ç«' => 'Çª',
  'Ç­' => 'Ç¬',
  'Ç¯' => 'Ç®',
  'Ç²' => 'Ç±',
  'Ç³' => 'Ç±',
  'Çµ' => 'Ç´',
  'Ç¹' => 'Ç¸',
  'Ç»' => 'Çº',
  'Ç½' => 'Ç¼',
  'Ç¿' => 'Ç¾',
  'È�' => 'È€',
  'Èƒ' => 'È‚',
  'È…' => 'È„',
  'È‡' => 'È†',
  'È‰' => 'Èˆ',
  'È‹' => 'ÈŠ',
  'È�' => 'ÈŒ',
  'È�' => 'ÈŽ',
  'È‘' => 'È�',
  'È“' => 'È’',
  'È•' => 'È”',
  'È—' => 'È–',
  'È™' => 'È˜',
  'È›' => 'Èš',
  'È�' => 'Èœ',
  'ÈŸ' => 'Èž',
  'È£' => 'È¢',
  'È¥' => 'È¤',
  'È§' => 'È¦',
  'È©' => 'È¨',
  'È«' => 'Èª',
  'È­' => 'È¬',
  'È¯' => 'È®',
  'È±' => 'È°',
  'È³' => 'È²',
  'È¼' => 'È»',
  'È¿' => 'â±¾',
  'É€' => 'â±¿',
  'É‚' => 'É�',
  'É‡' => 'É†',
  'É‰' => 'Éˆ',
  'É‹' => 'ÉŠ',
  'É�' => 'ÉŒ',
  'É�' => 'ÉŽ',
  'É�' => 'â±¯',
  'É‘' => 'â±­',
  'É’' => 'â±°',
  'É“' => 'Æ�',
  'É”' => 'Æ†',
  'É–' => 'Æ‰',
  'É—' => 'ÆŠ',
  'É™' => 'Æ�',
  'É›' => 'Æ�',
  'Éœ' => 'êž«',
  'É ' => 'Æ“',
  'É¡' => 'êž¬',
  'É£' => 'Æ”',
  'É¥' => 'êž�',
  'É¦' => 'êžª',
  'É¨' => 'Æ—',
  'É©' => 'Æ–',
  'É«' => 'â±¢',
  'É¬' => 'êž­',
  'É¯' => 'Æœ',
  'É±' => 'â±®',
  'É²' => 'Æ�',
  'Éµ' => 'ÆŸ',
  'É½' => 'â±¤',
  'Ê€' => 'Æ¦',
  'Êƒ' => 'Æ©',
  'Ê‡' => 'êž±',
  'Êˆ' => 'Æ®',
  'Ê‰' => 'É„',
  'ÊŠ' => 'Æ±',
  'Ê‹' => 'Æ²',
  'ÊŒ' => 'É…',
  'Ê’' => 'Æ·',
  'Êž' => 'êž°',
  'Í…' => 'Î™',
  'Í±' => 'Í°',
  'Í³' => 'Í²',
  'Í·' => 'Í¶',
  'Í»' => 'Ï½',
  'Í¼' => 'Ï¾',
  'Í½' => 'Ï¿',
  'Î¬' => 'Î†',
  'Î­' => 'Îˆ',
  'Î®' => 'Î‰',
  'Î¯' => 'ÎŠ',
  'Î±' => 'Î‘',
  'Î²' => 'Î’',
  'Î³' => 'Î“',
  'Î´' => 'Î”',
  'Îµ' => 'Î•',
  'Î¶' => 'Î–',
  'Î·' => 'Î—',
  'Î¸' => 'Î˜',
  'Î¹' => 'Î™',
  'Îº' => 'Îš',
  'Î»' => 'Î›',
  'Î¼' => 'Îœ',
  'Î½' => 'Î�',
  'Î¾' => 'Îž',
  'Î¿' => 'ÎŸ',
  'Ï€' => 'Î ',
  'Ï�' => 'Î¡',
  'Ï‚' => 'Î£',
  'Ïƒ' => 'Î£',
  'Ï„' => 'Î¤',
  'Ï…' => 'Î¥',
  'Ï†' => 'Î¦',
  'Ï‡' => 'Î§',
  'Ïˆ' => 'Î¨',
  'Ï‰' => 'Î©',
  'ÏŠ' => 'Îª',
  'Ï‹' => 'Î«',
  'ÏŒ' => 'ÎŒ',
  'Ï�' => 'ÎŽ',
  'ÏŽ' => 'Î�',
  'Ï�' => 'Î’',
  'Ï‘' => 'Î˜',
  'Ï•' => 'Î¦',
  'Ï–' => 'Î ',
  'Ï—' => 'Ï�',
  'Ï™' => 'Ï˜',
  'Ï›' => 'Ïš',
  'Ï�' => 'Ïœ',
  'ÏŸ' => 'Ïž',
  'Ï¡' => 'Ï ',
  'Ï£' => 'Ï¢',
  'Ï¥' => 'Ï¤',
  'Ï§' => 'Ï¦',
  'Ï©' => 'Ï¨',
  'Ï«' => 'Ïª',
  'Ï­' => 'Ï¬',
  'Ï¯' => 'Ï®',
  'Ï°' => 'Îš',
  'Ï±' => 'Î¡',
  'Ï²' => 'Ï¹',
  'Ï³' => 'Í¿',
  'Ïµ' => 'Î•',
  'Ï¸' => 'Ï·',
  'Ï»' => 'Ïº',
  'Ð°' => 'Ð�',
  'Ð±' => 'Ð‘',
  'Ð²' => 'Ð’',
  'Ð³' => 'Ð“',
  'Ð´' => 'Ð”',
  'Ðµ' => 'Ð•',
  'Ð¶' => 'Ð–',
  'Ð·' => 'Ð—',
  'Ð¸' => 'Ð˜',
  'Ð¹' => 'Ð™',
  'Ðº' => 'Ðš',
  'Ð»' => 'Ð›',
  'Ð¼' => 'Ðœ',
  'Ð½' => 'Ð�',
  'Ð¾' => 'Ðž',
  'Ð¿' => 'ÐŸ',
  'Ñ€' => 'Ð ',
  'Ñ�' => 'Ð¡',
  'Ñ‚' => 'Ð¢',
  'Ñƒ' => 'Ð£',
  'Ñ„' => 'Ð¤',
  'Ñ…' => 'Ð¥',
  'Ñ†' => 'Ð¦',
  'Ñ‡' => 'Ð§',
  'Ñˆ' => 'Ð¨',
  'Ñ‰' => 'Ð©',
  'ÑŠ' => 'Ðª',
  'Ñ‹' => 'Ð«',
  'ÑŒ' => 'Ð¬',
  'Ñ�' => 'Ð­',
  'ÑŽ' => 'Ð®',
  'Ñ�' => 'Ð¯',
  'Ñ�' => 'Ð€',
  'Ñ‘' => 'Ð�',
  'Ñ’' => 'Ð‚',
  'Ñ“' => 'Ðƒ',
  'Ñ”' => 'Ð„',
  'Ñ•' => 'Ð…',
  'Ñ–' => 'Ð†',
  'Ñ—' => 'Ð‡',
  'Ñ˜' => 'Ðˆ',
  'Ñ™' => 'Ð‰',
  'Ñš' => 'ÐŠ',
  'Ñ›' => 'Ð‹',
  'Ñœ' => 'ÐŒ',
  'Ñ�' => 'Ð�',
  'Ñž' => 'ÐŽ',
  'ÑŸ' => 'Ð�',
  'Ñ¡' => 'Ñ ',
  'Ñ£' => 'Ñ¢',
  'Ñ¥' => 'Ñ¤',
  'Ñ§' => 'Ñ¦',
  'Ñ©' => 'Ñ¨',
  'Ñ«' => 'Ñª',
  'Ñ­' => 'Ñ¬',
  'Ñ¯' => 'Ñ®',
  'Ñ±' => 'Ñ°',
  'Ñ³' => 'Ñ²',
  'Ñµ' => 'Ñ´',
  'Ñ·' => 'Ñ¶',
  'Ñ¹' => 'Ñ¸',
  'Ñ»' => 'Ñº',
  'Ñ½' => 'Ñ¼',
  'Ñ¿' => 'Ñ¾',
  'Ò�' => 'Ò€',
  'Ò‹' => 'ÒŠ',
  'Ò�' => 'ÒŒ',
  'Ò�' => 'ÒŽ',
  'Ò‘' => 'Ò�',
  'Ò“' => 'Ò’',
  'Ò•' => 'Ò”',
  'Ò—' => 'Ò–',
  'Ò™' => 'Ò˜',
  'Ò›' => 'Òš',
  'Ò�' => 'Òœ',
  'ÒŸ' => 'Òž',
  'Ò¡' => 'Ò ',
  'Ò£' => 'Ò¢',
  'Ò¥' => 'Ò¤',
  'Ò§' => 'Ò¦',
  'Ò©' => 'Ò¨',
  'Ò«' => 'Òª',
  'Ò­' => 'Ò¬',
  'Ò¯' => 'Ò®',
  'Ò±' => 'Ò°',
  'Ò³' => 'Ò²',
  'Òµ' => 'Ò´',
  'Ò·' => 'Ò¶',
  'Ò¹' => 'Ò¸',
  'Ò»' => 'Òº',
  'Ò½' => 'Ò¼',
  'Ò¿' => 'Ò¾',
  'Ó‚' => 'Ó�',
  'Ó„' => 'Óƒ',
  'Ó†' => 'Ó…',
  'Óˆ' => 'Ó‡',
  'ÓŠ' => 'Ó‰',
  'ÓŒ' => 'Ó‹',
  'ÓŽ' => 'Ó�',
  'Ó�' => 'Ó€',
  'Ó‘' => 'Ó�',
  'Ó“' => 'Ó’',
  'Ó•' => 'Ó”',
  'Ó—' => 'Ó–',
  'Ó™' => 'Ó˜',
  'Ó›' => 'Óš',
  'Ó�' => 'Óœ',
  'ÓŸ' => 'Óž',
  'Ó¡' => 'Ó ',
  'Ó£' => 'Ó¢',
  'Ó¥' => 'Ó¤',
  'Ó§' => 'Ó¦',
  'Ó©' => 'Ó¨',
  'Ó«' => 'Óª',
  'Ó­' => 'Ó¬',
  'Ó¯' => 'Ó®',
  'Ó±' => 'Ó°',
  'Ó³' => 'Ó²',
  'Óµ' => 'Ó´',
  'Ó·' => 'Ó¶',
  'Ó¹' => 'Ó¸',
  'Ó»' => 'Óº',
  'Ó½' => 'Ó¼',
  'Ó¿' => 'Ó¾',
  'Ô�' => 'Ô€',
  'Ôƒ' => 'Ô‚',
  'Ô…' => 'Ô„',
  'Ô‡' => 'Ô†',
  'Ô‰' => 'Ôˆ',
  'Ô‹' => 'ÔŠ',
  'Ô�' => 'ÔŒ',
  'Ô�' => 'ÔŽ',
  'Ô‘' => 'Ô�',
  'Ô“' => 'Ô’',
  'Ô•' => 'Ô”',
  'Ô—' => 'Ô–',
  'Ô™' => 'Ô˜',
  'Ô›' => 'Ôš',
  'Ô�' => 'Ôœ',
  'ÔŸ' => 'Ôž',
  'Ô¡' => 'Ô ',
  'Ô£' => 'Ô¢',
  'Ô¥' => 'Ô¤',
  'Ô§' => 'Ô¦',
  'Ô©' => 'Ô¨',
  'Ô«' => 'Ôª',
  'Ô­' => 'Ô¬',
  'Ô¯' => 'Ô®',
  'Õ¡' => 'Ô±',
  'Õ¢' => 'Ô²',
  'Õ£' => 'Ô³',
  'Õ¤' => 'Ô´',
  'Õ¥' => 'Ôµ',
  'Õ¦' => 'Ô¶',
  'Õ§' => 'Ô·',
  'Õ¨' => 'Ô¸',
  'Õ©' => 'Ô¹',
  'Õª' => 'Ôº',
  'Õ«' => 'Ô»',
  'Õ¬' => 'Ô¼',
  'Õ­' => 'Ô½',
  'Õ®' => 'Ô¾',
  'Õ¯' => 'Ô¿',
  'Õ°' => 'Õ€',
  'Õ±' => 'Õ�',
  'Õ²' => 'Õ‚',
  'Õ³' => 'Õƒ',
  'Õ´' => 'Õ„',
  'Õµ' => 'Õ…',
  'Õ¶' => 'Õ†',
  'Õ·' => 'Õ‡',
  'Õ¸' => 'Õˆ',
  'Õ¹' => 'Õ‰',
  'Õº' => 'ÕŠ',
  'Õ»' => 'Õ‹',
  'Õ¼' => 'ÕŒ',
  'Õ½' => 'Õ�',
  'Õ¾' => 'ÕŽ',
  'Õ¿' => 'Õ�',
  'Ö€' => 'Õ�',
  'Ö�' => 'Õ‘',
  'Ö‚' => 'Õ’',
  'Öƒ' => 'Õ“',
  'Ö„' => 'Õ”',
  'Ö…' => 'Õ•',
  'Ö†' => 'Õ–',
  'áµ¹' => 'ê�½',
  'áµ½' => 'â±£',
  'á¸�' => 'á¸€',
  'á¸ƒ' => 'á¸‚',
  'á¸…' => 'á¸„',
  'á¸‡' => 'á¸†',
  'á¸‰' => 'á¸ˆ',
  'á¸‹' => 'á¸Š',
  'á¸�' => 'á¸Œ',
  'á¸�' => 'á¸Ž',
  'á¸‘' => 'á¸�',
  'á¸“' => 'á¸’',
  'á¸•' => 'á¸”',
  'á¸—' => 'á¸–',
  'á¸™' => 'á¸˜',
  'á¸›' => 'á¸š',
  'á¸�' => 'á¸œ',
  'á¸Ÿ' => 'á¸ž',
  'á¸¡' => 'á¸ ',
  'á¸£' => 'á¸¢',
  'á¸¥' => 'á¸¤',
  'á¸§' => 'á¸¦',
  'á¸©' => 'á¸¨',
  'á¸«' => 'á¸ª',
  'á¸­' => 'á¸¬',
  'á¸¯' => 'á¸®',
  'á¸±' => 'á¸°',
  'á¸³' => 'á¸²',
  'á¸µ' => 'á¸´',
  'á¸·' => 'á¸¶',
  'á¸¹' => 'á¸¸',
  'á¸»' => 'á¸º',
  'á¸½' => 'á¸¼',
  'á¸¿' => 'á¸¾',
  'á¹�' => 'á¹€',
  'á¹ƒ' => 'á¹‚',
  'á¹…' => 'á¹„',
  'á¹‡' => 'á¹†',
  'á¹‰' => 'á¹ˆ',
  'á¹‹' => 'á¹Š',
  'á¹�' => 'á¹Œ',
  'á¹�' => 'á¹Ž',
  'á¹‘' => 'á¹�',
  'á¹“' => 'á¹’',
  'á¹•' => 'á¹”',
  'á¹—' => 'á¹–',
  'á¹™' => 'á¹˜',
  'á¹›' => 'á¹š',
  'á¹�' => 'á¹œ',
  'á¹Ÿ' => 'á¹ž',
  'á¹¡' => 'á¹ ',
  'á¹£' => 'á¹¢',
  'á¹¥' => 'á¹¤',
  'á¹§' => 'á¹¦',
  'á¹©' => 'á¹¨',
  'á¹«' => 'á¹ª',
  'á¹­' => 'á¹¬',
  'á¹¯' => 'á¹®',
  'á¹±' => 'á¹°',
  'á¹³' => 'á¹²',
  'á¹µ' => 'á¹´',
  'á¹·' => 'á¹¶',
  'á¹¹' => 'á¹¸',
  'á¹»' => 'á¹º',
  'á¹½' => 'á¹¼',
  'á¹¿' => 'á¹¾',
  'áº�' => 'áº€',
  'áºƒ' => 'áº‚',
  'áº…' => 'áº„',
  'áº‡' => 'áº†',
  'áº‰' => 'áºˆ',
  'áº‹' => 'áºŠ',
  'áº�' => 'áºŒ',
  'áº�' => 'áºŽ',
  'áº‘' => 'áº�',
  'áº“' => 'áº’',
  'áº•' => 'áº”',
  'áº›' => 'á¹ ',
  'áº¡' => 'áº ',
  'áº£' => 'áº¢',
  'áº¥' => 'áº¤',
  'áº§' => 'áº¦',
  'áº©' => 'áº¨',
  'áº«' => 'áºª',
  'áº­' => 'áº¬',
  'áº¯' => 'áº®',
  'áº±' => 'áº°',
  'áº³' => 'áº²',
  'áºµ' => 'áº´',
  'áº·' => 'áº¶',
  'áº¹' => 'áº¸',
  'áº»' => 'áºº',
  'áº½' => 'áº¼',
  'áº¿' => 'áº¾',
  'á»�' => 'á»€',
  'á»ƒ' => 'á»‚',
  'á»…' => 'á»„',
  'á»‡' => 'á»†',
  'á»‰' => 'á»ˆ',
  'á»‹' => 'á»Š',
  'á»�' => 'á»Œ',
  'á»�' => 'á»Ž',
  'á»‘' => 'á»�',
  'á»“' => 'á»’',
  'á»•' => 'á»”',
  'á»—' => 'á»–',
  'á»™' => 'á»˜',
  'á»›' => 'á»š',
  'á»�' => 'á»œ',
  'á»Ÿ' => 'á»ž',
  'á»¡' => 'á» ',
  'á»£' => 'á»¢',
  'á»¥' => 'á»¤',
  'á»§' => 'á»¦',
  'á»©' => 'á»¨',
  'á»«' => 'á»ª',
  'á»­' => 'á»¬',
  'á»¯' => 'á»®',
  'á»±' => 'á»°',
  'á»³' => 'á»²',
  'á»µ' => 'á»´',
  'á»·' => 'á»¶',
  'á»¹' => 'á»¸',
  'á»»' => 'á»º',
  'á»½' => 'á»¼',
  'á»¿' => 'á»¾',
  'á¼€' => 'á¼ˆ',
  'á¼�' => 'á¼‰',
  'á¼‚' => 'á¼Š',
  'á¼ƒ' => 'á¼‹',
  'á¼„' => 'á¼Œ',
  'á¼…' => 'á¼�',
  'á¼†' => 'á¼Ž',
  'á¼‡' => 'á¼�',
  'á¼�' => 'á¼˜',
  'á¼‘' => 'á¼™',
  'á¼’' => 'á¼š',
  'á¼“' => 'á¼›',
  'á¼”' => 'á¼œ',
  'á¼•' => 'á¼�',
  'á¼ ' => 'á¼¨',
  'á¼¡' => 'á¼©',
  'á¼¢' => 'á¼ª',
  'á¼£' => 'á¼«',
  'á¼¤' => 'á¼¬',
  'á¼¥' => 'á¼­',
  'á¼¦' => 'á¼®',
  'á¼§' => 'á¼¯',
  'á¼°' => 'á¼¸',
  'á¼±' => 'á¼¹',
  'á¼²' => 'á¼º',
  'á¼³' => 'á¼»',
  'á¼´' => 'á¼¼',
  'á¼µ' => 'á¼½',
  'á¼¶' => 'á¼¾',
  'á¼·' => 'á¼¿',
  'á½€' => 'á½ˆ',
  'á½�' => 'á½‰',
  'á½‚' => 'á½Š',
  'á½ƒ' => 'á½‹',
  'á½„' => 'á½Œ',
  'á½…' => 'á½�',
  'á½‘' => 'á½™',
  'á½“' => 'á½›',
  'á½•' => 'á½�',
  'á½—' => 'á½Ÿ',
  'á½ ' => 'á½¨',
  'á½¡' => 'á½©',
  'á½¢' => 'á½ª',
  'á½£' => 'á½«',
  'á½¤' => 'á½¬',
  'á½¥' => 'á½­',
  'á½¦' => 'á½®',
  'á½§' => 'á½¯',
  'á½°' => 'á¾º',
  'á½±' => 'á¾»',
  'á½²' => 'á¿ˆ',
  'á½³' => 'á¿‰',
  'á½´' => 'á¿Š',
  'á½µ' => 'á¿‹',
  'á½¶' => 'á¿š',
  'á½·' => 'á¿›',
  'á½¸' => 'á¿¸',
  'á½¹' => 'á¿¹',
  'á½º' => 'á¿ª',
  'á½»' => 'á¿«',
  'á½¼' => 'á¿º',
  'á½½' => 'á¿»',
  'á¾€' => 'á¾ˆ',
  'á¾�' => 'á¾‰',
  'á¾‚' => 'á¾Š',
  'á¾ƒ' => 'á¾‹',
  'á¾„' => 'á¾Œ',
  'á¾…' => 'á¾�',
  'á¾†' => 'á¾Ž',
  'á¾‡' => 'á¾�',
  'á¾�' => 'á¾˜',
  'á¾‘' => 'á¾™',
  'á¾’' => 'á¾š',
  'á¾“' => 'á¾›',
  'á¾”' => 'á¾œ',
  'á¾•' => 'á¾�',
  'á¾–' => 'á¾ž',
  'á¾—' => 'á¾Ÿ',
  'á¾ ' => 'á¾¨',
  'á¾¡' => 'á¾©',
  'á¾¢' => 'á¾ª',
  'á¾£' => 'á¾«',
  'á¾¤' => 'á¾¬',
  'á¾¥' => 'á¾­',
  'á¾¦' => 'á¾®',
  'á¾§' => 'á¾¯',
  'á¾°' => 'á¾¸',
  'á¾±' => 'á¾¹',
  'á¾³' => 'á¾¼',
  'á¾¾' => 'Î™',
  'á¿ƒ' => 'á¿Œ',
  'á¿�' => 'á¿˜',
  'á¿‘' => 'á¿™',
  'á¿ ' => 'á¿¨',
  'á¿¡' => 'á¿©',
  'á¿¥' => 'á¿¬',
  'á¿³' => 'á¿¼',
  'â…Ž' => 'â„²',
  'â…°' => 'â… ',
  'â…±' => 'â…¡',
  'â…²' => 'â…¢',
  'â…³' => 'â…£',
  'â…´' => 'â…¤',
  'â…µ' => 'â…¥',
  'â…¶' => 'â…¦',
  'â…·' => 'â…§',
  'â…¸' => 'â…¨',
  'â…¹' => 'â…©',
  'â…º' => 'â…ª',
  'â…»' => 'â…«',
  'â…¼' => 'â…¬',
  'â…½' => 'â…­',
  'â…¾' => 'â…®',
  'â…¿' => 'â…¯',
  'â†„' => 'â†ƒ',
  'â“�' => 'â’¶',
  'â“‘' => 'â’·',
  'â“’' => 'â’¸',
  'â““' => 'â’¹',
  'â“”' => 'â’º',
  'â“•' => 'â’»',
  'â“–' => 'â’¼',
  'â“—' => 'â’½',
  'â“˜' => 'â’¾',
  'â“™' => 'â’¿',
  'â“š' => 'â“€',
  'â“›' => 'â“�',
  'â“œ' => 'â“‚',
  'â“�' => 'â“ƒ',
  'â“ž' => 'â“„',
  'â“Ÿ' => 'â“…',
  'â“ ' => 'â“†',
  'â“¡' => 'â“‡',
  'â“¢' => 'â“ˆ',
  'â“£' => 'â“‰',
  'â“¤' => 'â“Š',
  'â“¥' => 'â“‹',
  'â“¦' => 'â“Œ',
  'â“§' => 'â“�',
  'â“¨' => 'â“Ž',
  'â“©' => 'â“�',
  'â°°' => 'â°€',
  'â°±' => 'â°�',
  'â°²' => 'â°‚',
  'â°³' => 'â°ƒ',
  'â°´' => 'â°„',
  'â°µ' => 'â°…',
  'â°¶' => 'â°†',
  'â°·' => 'â°‡',
  'â°¸' => 'â°ˆ',
  'â°¹' => 'â°‰',
  'â°º' => 'â°Š',
  'â°»' => 'â°‹',
  'â°¼' => 'â°Œ',
  'â°½' => 'â°�',
  'â°¾' => 'â°Ž',
  'â°¿' => 'â°�',
  'â±€' => 'â°�',
  'â±�' => 'â°‘',
  'â±‚' => 'â°’',
  'â±ƒ' => 'â°“',
  'â±„' => 'â°”',
  'â±…' => 'â°•',
  'â±†' => 'â°–',
  'â±‡' => 'â°—',
  'â±ˆ' => 'â°˜',
  'â±‰' => 'â°™',
  'â±Š' => 'â°š',
  'â±‹' => 'â°›',
  'â±Œ' => 'â°œ',
  'â±�' => 'â°�',
  'â±Ž' => 'â°ž',
  'â±�' => 'â°Ÿ',
  'â±�' => 'â° ',
  'â±‘' => 'â°¡',
  'â±’' => 'â°¢',
  'â±“' => 'â°£',
  'â±”' => 'â°¤',
  'â±•' => 'â°¥',
  'â±–' => 'â°¦',
  'â±—' => 'â°§',
  'â±˜' => 'â°¨',
  'â±™' => 'â°©',
  'â±š' => 'â°ª',
  'â±›' => 'â°«',
  'â±œ' => 'â°¬',
  'â±�' => 'â°­',
  'â±ž' => 'â°®',
  'â±¡' => 'â± ',
  'â±¥' => 'Èº',
  'â±¦' => 'È¾',
  'â±¨' => 'â±§',
  'â±ª' => 'â±©',
  'â±¬' => 'â±«',
  'â±³' => 'â±²',
  'â±¶' => 'â±µ',
  'â²�' => 'â²€',
  'â²ƒ' => 'â²‚',
  'â²…' => 'â²„',
  'â²‡' => 'â²†',
  'â²‰' => 'â²ˆ',
  'â²‹' => 'â²Š',
  'â²�' => 'â²Œ',
  'â²�' => 'â²Ž',
  'â²‘' => 'â²�',
  'â²“' => 'â²’',
  'â²•' => 'â²”',
  'â²—' => 'â²–',
  'â²™' => 'â²˜',
  'â²›' => 'â²š',
  'â²�' => 'â²œ',
  'â²Ÿ' => 'â²ž',
  'â²¡' => 'â² ',
  'â²£' => 'â²¢',
  'â²¥' => 'â²¤',
  'â²§' => 'â²¦',
  'â²©' => 'â²¨',
  'â²«' => 'â²ª',
  'â²­' => 'â²¬',
  'â²¯' => 'â²®',
  'â²±' => 'â²°',
  'â²³' => 'â²²',
  'â²µ' => 'â²´',
  'â²·' => 'â²¶',
  'â²¹' => 'â²¸',
  'â²»' => 'â²º',
  'â²½' => 'â²¼',
  'â²¿' => 'â²¾',
  'â³�' => 'â³€',
  'â³ƒ' => 'â³‚',
  'â³…' => 'â³„',
  'â³‡' => 'â³†',
  'â³‰' => 'â³ˆ',
  'â³‹' => 'â³Š',
  'â³�' => 'â³Œ',
  'â³�' => 'â³Ž',
  'â³‘' => 'â³�',
  'â³“' => 'â³’',
  'â³•' => 'â³”',
  'â³—' => 'â³–',
  'â³™' => 'â³˜',
  'â³›' => 'â³š',
  'â³�' => 'â³œ',
  'â³Ÿ' => 'â³ž',
  'â³¡' => 'â³ ',
  'â³£' => 'â³¢',
  'â³¬' => 'â³«',
  'â³®' => 'â³­',
  'â³³' => 'â³²',
  'â´€' => 'á‚ ',
  'â´�' => 'á‚¡',
  'â´‚' => 'á‚¢',
  'â´ƒ' => 'á‚£',
  'â´„' => 'á‚¤',
  'â´…' => 'á‚¥',
  'â´†' => 'á‚¦',
  'â´‡' => 'á‚§',
  'â´ˆ' => 'á‚¨',
  'â´‰' => 'á‚©',
  'â´Š' => 'á‚ª',
  'â´‹' => 'á‚«',
  'â´Œ' => 'á‚¬',
  'â´�' => 'á‚­',
  'â´Ž' => 'á‚®',
  'â´�' => 'á‚¯',
  'â´�' => 'á‚°',
  'â´‘' => 'á‚±',
  'â´’' => 'á‚²',
  'â´“' => 'á‚³',
  'â´”' => 'á‚´',
  'â´•' => 'á‚µ',
  'â´–' => 'á‚¶',
  'â´—' => 'á‚·',
  'â´˜' => 'á‚¸',
  'â´™' => 'á‚¹',
  'â´š' => 'á‚º',
  'â´›' => 'á‚»',
  'â´œ' => 'á‚¼',
  'â´�' => 'á‚½',
  'â´ž' => 'á‚¾',
  'â´Ÿ' => 'á‚¿',
  'â´ ' => 'áƒ€',
  'â´¡' => 'áƒ�',
  'â´¢' => 'áƒ‚',
  'â´£' => 'áƒƒ',
  'â´¤' => 'áƒ„',
  'â´¥' => 'áƒ…',
  'â´§' => 'áƒ‡',
  'â´­' => 'áƒ�',
  'ê™�' => 'ê™€',
  'ê™ƒ' => 'ê™‚',
  'ê™…' => 'ê™„',
  'ê™‡' => 'ê™†',
  'ê™‰' => 'ê™ˆ',
  'ê™‹' => 'ê™Š',
  'ê™�' => 'ê™Œ',
  'ê™�' => 'ê™Ž',
  'ê™‘' => 'ê™�',
  'ê™“' => 'ê™’',
  'ê™•' => 'ê™”',
  'ê™—' => 'ê™–',
  'ê™™' => 'ê™˜',
  'ê™›' => 'ê™š',
  'ê™�' => 'ê™œ',
  'ê™Ÿ' => 'ê™ž',
  'ê™¡' => 'ê™ ',
  'ê™£' => 'ê™¢',
  'ê™¥' => 'ê™¤',
  'ê™§' => 'ê™¦',
  'ê™©' => 'ê™¨',
  'ê™«' => 'ê™ª',
  'ê™­' => 'ê™¬',
  'êš�' => 'êš€',
  'êšƒ' => 'êš‚',
  'êš…' => 'êš„',
  'êš‡' => 'êš†',
  'êš‰' => 'êšˆ',
  'êš‹' => 'êšŠ',
  'êš�' => 'êšŒ',
  'êš�' => 'êšŽ',
  'êš‘' => 'êš�',
  'êš“' => 'êš’',
  'êš•' => 'êš”',
  'êš—' => 'êš–',
  'êš™' => 'êš˜',
  'êš›' => 'êšš',
  'êœ£' => 'êœ¢',
  'êœ¥' => 'êœ¤',
  'êœ§' => 'êœ¦',
  'êœ©' => 'êœ¨',
  'êœ«' => 'êœª',
  'êœ­' => 'êœ¬',
  'êœ¯' => 'êœ®',
  'êœ³' => 'êœ²',
  'êœµ' => 'êœ´',
  'êœ·' => 'êœ¶',
  'êœ¹' => 'êœ¸',
  'êœ»' => 'êœº',
  'êœ½' => 'êœ¼',
  'êœ¿' => 'êœ¾',
  'ê��' => 'ê�€',
  'ê�ƒ' => 'ê�‚',
  'ê�…' => 'ê�„',
  'ê�‡' => 'ê�†',
  'ê�‰' => 'ê�ˆ',
  'ê�‹' => 'ê�Š',
  'ê��' => 'ê�Œ',
  'ê��' => 'ê�Ž',
  'ê�‘' => 'ê��',
  'ê�“' => 'ê�’',
  'ê�•' => 'ê�”',
  'ê�—' => 'ê�–',
  'ê�™' => 'ê�˜',
  'ê�›' => 'ê�š',
  'ê��' => 'ê�œ',
  'ê�Ÿ' => 'ê�ž',
  'ê�¡' => 'ê� ',
  'ê�£' => 'ê�¢',
  'ê�¥' => 'ê�¤',
  'ê�§' => 'ê�¦',
  'ê�©' => 'ê�¨',
  'ê�«' => 'ê�ª',
  'ê�­' => 'ê�¬',
  'ê�¯' => 'ê�®',
  'ê�º' => 'ê�¹',
  'ê�¼' => 'ê�»',
  'ê�¿' => 'ê�¾',
  'êž�' => 'êž€',
  'êžƒ' => 'êž‚',
  'êž…' => 'êž„',
  'êž‡' => 'êž†',
  'êžŒ' => 'êž‹',
  'êž‘' => 'êž�',
  'êž“' => 'êž’',
  'êž—' => 'êž–',
  'êž™' => 'êž˜',
  'êž›' => 'êžš',
  'êž�' => 'êžœ',
  'êžŸ' => 'êžž',
  'êž¡' => 'êž ',
  'êž£' => 'êž¢',
  'êž¥' => 'êž¤',
  'êž§' => 'êž¦',
  'êž©' => 'êž¨',
  'ï½�' => 'ï¼¡',
  'ï½‚' => 'ï¼¢',
  'ï½ƒ' => 'ï¼£',
  'ï½„' => 'ï¼¤',
  'ï½…' => 'ï¼¥',
  'ï½†' => 'ï¼¦',
  'ï½‡' => 'ï¼§',
  'ï½ˆ' => 'ï¼¨',
  'ï½‰' => 'ï¼©',
  'ï½Š' => 'ï¼ª',
  'ï½‹' => 'ï¼«',
  'ï½Œ' => 'ï¼¬',
  'ï½�' => 'ï¼­',
  'ï½Ž' => 'ï¼®',
  'ï½�' => 'ï¼¯',
  'ï½�' => 'ï¼°',
  'ï½‘' => 'ï¼±',
  'ï½’' => 'ï¼²',
  'ï½“' => 'ï¼³',
  'ï½”' => 'ï¼´',
  'ï½•' => 'ï¼µ',
  'ï½–' => 'ï¼¶',
  'ï½—' => 'ï¼·',
  'ï½˜' => 'ï¼¸',
  'ï½™' => 'ï¼¹',
  'ï½š' => 'ï¼º',
  'ð��¨' => 'ð��€',
  'ð��©' => 'ð���',
  'ð��ª' => 'ð��‚',
  'ð��«' => 'ð��ƒ',
  'ð��¬' => 'ð��„',
  'ð��­' => 'ð��…',
  'ð��®' => 'ð��†',
  'ð��¯' => 'ð��‡',
  'ð��°' => 'ð��ˆ',
  'ð��±' => 'ð��‰',
  'ð��²' => 'ð��Š',
  'ð��³' => 'ð��‹',
  'ð��´' => 'ð��Œ',
  'ð��µ' => 'ð���',
  'ð��¶' => 'ð��Ž',
  'ð��·' => 'ð���',
  'ð��¸' => 'ð���',
  'ð��¹' => 'ð��‘',
  'ð��º' => 'ð��’',
  'ð��»' => 'ð��“',
  'ð��¼' => 'ð��”',
  'ð��½' => 'ð��•',
  'ð��¾' => 'ð��–',
  'ð��¿' => 'ð��—',
  'ð�‘€' => 'ð��˜',
  'ð�‘�' => 'ð��™',
  'ð�‘‚' => 'ð��š',
  'ð�‘ƒ' => 'ð��›',
  'ð�‘„' => 'ð��œ',
  'ð�‘…' => 'ð���',
  'ð�‘†' => 'ð��ž',
  'ð�‘‡' => 'ð��Ÿ',
  'ð�‘ˆ' => 'ð�� ',
  'ð�‘‰' => 'ð��¡',
  'ð�‘Š' => 'ð��¢',
  'ð�‘‹' => 'ð��£',
  'ð�‘Œ' => 'ð��¤',
  'ð�‘�' => 'ð��¥',
  'ð�‘Ž' => 'ð��¦',
  'ð�‘�' => 'ð��§',
  'ð‘£€' => 'ð‘¢ ',
  'ð‘£�' => 'ð‘¢¡',
  'ð‘£‚' => 'ð‘¢¢',
  'ð‘£ƒ' => 'ð‘¢£',
  'ð‘£„' => 'ð‘¢¤',
  'ð‘£…' => 'ð‘¢¥',
  'ð‘£†' => 'ð‘¢¦',
  'ð‘£‡' => 'ð‘¢§',
  'ð‘£ˆ' => 'ð‘¢¨',
  'ð‘£‰' => 'ð‘¢©',
  'ð‘£Š' => 'ð‘¢ª',
  'ð‘£‹' => 'ð‘¢«',
  'ð‘£Œ' => 'ð‘¢¬',
  'ð‘£�' => 'ð‘¢­',
  'ð‘£Ž' => 'ð‘¢®',
  'ð‘£�' => 'ð‘¢¯',
  'ð‘£�' => 'ð‘¢°',
  'ð‘£‘' => 'ð‘¢±',
  'ð‘£’' => 'ð‘¢²',
  'ð‘£“' => 'ð‘¢³',
  'ð‘£”' => 'ð‘¢´',
  'ð‘£•' => 'ð‘¢µ',
  'ð‘£–' => 'ð‘¢¶',
  'ð‘£—' => 'ð‘¢·',
  'ð‘£˜' => 'ð‘¢¸',
  'ð‘£™' => 'ð‘¢¹',
  'ð‘£š' => 'ð‘¢º',
  'ð‘£›' => 'ð‘¢»',
  'ð‘£œ' => 'ð‘¢¼',
  'ð‘£�' => 'ð‘¢½',
  'ð‘£ž' => 'ð‘¢¾',
  'ð‘£Ÿ' => 'ð‘¢¿',
);
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Polyfill\Mbstring;

/**
 * Partial mbstring implementation in PHP, iconv based, UTF-8 centric.
 *
 * Implemented:
 * - mb_chr                  - Returns a specific character from its Unicode code point
 * - mb_convert_encoding     - Convert character encoding
 * - mb_convert_variables    - Convert character code in variable(s)
 * - mb_decode_mimeheader    - Decode string in MIME header field
 * - mb_encode_mimeheader    - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED
 * - mb_decode_numericentity - Decode HTML numeric string reference to character
 * - mb_encode_numericentity - Encode character to HTML numeric string reference
 * - mb_convert_case         - Perform case folding on a string
 * - mb_detect_encoding      - Detect character encoding
 * - mb_get_info             - Get internal settings of mbstring
 * - mb_http_input           - Detect HTTP input character encoding
 * - mb_http_output          - Set/Get HTTP output character encoding
 * - mb_internal_encoding    - Set/Get internal character encoding
 * - mb_list_encodings       - Returns an array of all supported encodings
 * - mb_ord                  - Returns the Unicode code point of a character
 * - mb_output_handler       - Callback function converts character encoding in output buffer
 * - mb_scrub                - Replaces ill-formed byte sequences with substitute characters
 * - mb_strlen               - Get string length
 * - mb_strpos               - Find position of first occurrence of string in a string
 * - mb_strrpos              - Find position of last occurrence of a string in a string
 * - mb_strtolower           - Make a string lowercase
 * - mb_strtoupper           - Make a string uppercase
 * - mb_substitute_character - Set/Get substitution character
 * - mb_substr               - Get part of string
 * - mb_stripos              - Finds position of first occurrence of a string within another, case insensitive
 * - mb_stristr              - Finds first occurrence of a string within another, case insensitive
 * - mb_strrchr              - Finds the last occurrence of a character in a string within another
 * - mb_strrichr             - Finds the last occurrence of a character in a string within another, case insensitive
 * - mb_strripos             - Finds position of last occurrence of a string within another, case insensitive
 * - mb_strstr               - Finds first occurrence of a string within another
 * - mb_strwidth             - Return width of string
 * - mb_substr_count         - Count the number of substring occurrences
 *
 * Not implemented:
 * - mb_convert_kana         - Convert "kana" one from another ("zen-kaku", "han-kaku" and more)
 * - mb_ereg_*               - Regular expression with multibyte support
 * - mb_parse_str            - Parse GET/POST/COOKIE data and set global variable
 * - mb_preferred_mime_name  - Get MIME charset string
 * - mb_regex_encoding       - Returns current encoding for multibyte regex as string
 * - mb_regex_set_options    - Set/Get the default options for mbregex functions
 * - mb_send_mail            - Send encoded mail
 * - mb_split                - Split multibyte string using regular expression
 * - mb_strcut               - Get part of string
 * - mb_strimwidth           - Get truncated string with specified width
 *
 * @author Nicolas Grekas <p@tchwork.com>
 *
 * @internal
 */
final class Mbstring
{
    const MB_CASE_FOLD = PHP_INT_MAX;

    private static $encodingList = array('ASCII', 'UTF-8');
    private static $language = 'neutral';
    private static $internalEncoding = 'UTF-8';
    private static $caseFold = array(
        array('Âµ', 'Å¿', "\xCD\x85", 'Ï‚', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"),
        array('Î¼', 's', 'Î¹',        'Ïƒ', 'Î²',        'Î¸',        'Ï†',        'Ï€',        'Îº',        'Ï�',        'Îµ',        "\xE1\xB9\xA1", 'Î¹'),
    );

    public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null)
    {
        if (\is_array($fromEncoding) || false !== strpos($fromEncoding, ',')) {
            $fromEncoding = self::mb_detect_encoding($s, $fromEncoding);
        } else {
            $fromEncoding = self::getEncoding($fromEncoding);
        }

        $toEncoding = self::getEncoding($toEncoding);

        if ('BASE64' === $fromEncoding) {
            $s = base64_decode($s);
            $fromEncoding = $toEncoding;
        }

        if ('BASE64' === $toEncoding) {
            return base64_encode($s);
        }

        if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) {
            if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) {
                $fromEncoding = 'Windows-1252';
            }
            if ('UTF-8' !== $fromEncoding) {
                $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s);
            }

            return preg_replace_callback('/[\x80-\xFF]+/', array(__CLASS__, 'html_encoding_callback'), $s);
        }

        if ('HTML-ENTITIES' === $fromEncoding) {
            $s = html_entity_decode($s, ENT_COMPAT, 'UTF-8');
            $fromEncoding = 'UTF-8';
        }

        return iconv($fromEncoding, $toEncoding.'//IGNORE', $s);
    }

    public static function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null)
    {
        $vars = array(&$a, &$b, &$c, &$d, &$e, &$f);

        $ok = true;
        array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) {
            if (false === $v = Mbstring::mb_convert_encoding($v, $toEncoding, $fromEncoding)) {
                $ok = false;
            }
        });

        return $ok ? $fromEncoding : false;
    }

    public static function mb_decode_mimeheader($s)
    {
        return iconv_mime_decode($s, 2, self::$internalEncoding);
    }

    public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null)
    {
        trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', E_USER_WARNING);
    }

    public static function mb_decode_numericentity($s, $convmap, $encoding = null)
    {
        if (null !== $s && !\is_scalar($s) && !(\is_object($s) && \method_exists($s, '__toString'))) {
            trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', E_USER_WARNING);

            return null;
        }

        if (!\is_array($convmap) || !$convmap) {
            return false;
        }

        if (null !== $encoding && !\is_scalar($encoding)) {
            trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', E_USER_WARNING);

            return '';  // Instead of null (cf. mb_encode_numericentity).
        }

        $s = (string) $s;
        if ('' === $s) {
            return '';
        }

        $encoding = self::getEncoding($encoding);

        if ('UTF-8' === $encoding) {
            $encoding = null;
            if (!preg_match('//u', $s)) {
                $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
            }
        } else {
            $s = iconv($encoding, 'UTF-8//IGNORE', $s);
        }

        $cnt = floor(\count($convmap) / 4) * 4;

        for ($i = 0; $i < $cnt; $i += 4) {
            // collector_decode_htmlnumericentity ignores $convmap[$i + 3]
            $convmap[$i] += $convmap[$i + 2];
            $convmap[$i + 1] += $convmap[$i + 2];
        }

        $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) {
            $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1];
            for ($i = 0; $i < $cnt; $i += 4) {
                if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) {
                    return Mbstring::mb_chr($c - $convmap[$i + 2]);
                }
            }

            return $m[0];
        }, $s);

        if (null === $encoding) {
            return $s;
        }

        return iconv('UTF-8', $encoding.'//IGNORE', $s);
    }

    public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false)
    {
        if (null !== $s && !\is_scalar($s) && !(\is_object($s) && \method_exists($s, '__toString'))) {
            trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', E_USER_WARNING);

            return null;
        }

        if (!\is_array($convmap) || !$convmap) {
            return false;
        }

        if (null !== $encoding && !\is_scalar($encoding)) {
            trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', E_USER_WARNING);

            return null;  // Instead of '' (cf. mb_decode_numericentity).
        }

        if (null !== $is_hex && !\is_scalar($is_hex)) {
            trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', E_USER_WARNING);

            return null;
        }

        $s = (string) $s;
        if ('' === $s) {
            return '';
        }

        $encoding = self::getEncoding($encoding);

        if ('UTF-8' === $encoding) {
            $encoding = null;
            if (!preg_match('//u', $s)) {
                $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
            }
        } else {
            $s = iconv($encoding, 'UTF-8//IGNORE', $s);
        }

        static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4);

        $cnt = floor(\count($convmap) / 4) * 4;
        $i = 0;
        $len = \strlen($s);
        $result = '';

        while ($i < $len) {
            $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
            $uchr = substr($s, $i, $ulen);
            $i += $ulen;
            $c = self::mb_ord($uchr);

            for ($j = 0; $j < $cnt; $j += 4) {
                if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) {
                    $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3];
                    $result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';';
                    continue 2;
                }
            }
            $result .= $uchr;
        }

        if (null === $encoding) {
            return $result;
        }

        return iconv('UTF-8', $encoding.'//IGNORE', $result);
    }

    public static function mb_convert_case($s, $mode, $encoding = null)
    {
        $s = (string) $s;
        if ('' === $s) {
            return '';
        }

        $encoding = self::getEncoding($encoding);

        if ('UTF-8' === $encoding) {
            $encoding = null;
            if (!preg_match('//u', $s)) {
                $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
            }
        } else {
            $s = iconv($encoding, 'UTF-8//IGNORE', $s);
        }

        if (MB_CASE_TITLE == $mode) {
            static $titleRegexp = null;
            if (null === $titleRegexp) {
                $titleRegexp = self::getData('titleCaseRegexp');
            }
            $s = preg_replace_callback($titleRegexp, array(__CLASS__, 'title_case'), $s);
        } else {
            if (MB_CASE_UPPER == $mode) {
                static $upper = null;
                if (null === $upper) {
                    $upper = self::getData('upperCase');
                }
                $map = $upper;
            } else {
                if (self::MB_CASE_FOLD === $mode) {
                    $s = str_replace(self::$caseFold[0], self::$caseFold[1], $s);
                }

                static $lower = null;
                if (null === $lower) {
                    $lower = self::getData('lowerCase');
                }
                $map = $lower;
            }

            static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4);

            $i = 0;
            $len = \strlen($s);

            while ($i < $len) {
                $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
                $uchr = substr($s, $i, $ulen);
                $i += $ulen;

                if (isset($map[$uchr])) {
                    $uchr = $map[$uchr];
                    $nlen = \strlen($uchr);

                    if ($nlen == $ulen) {
                        $nlen = $i;
                        do {
                            $s[--$nlen] = $uchr[--$ulen];
                        } while ($ulen);
                    } else {
                        $s = substr_replace($s, $uchr, $i - $ulen, $ulen);
                        $len += $nlen - $ulen;
                        $i += $nlen - $ulen;
                    }
                }
            }
        }

        if (null === $encoding) {
            return $s;
        }

        return iconv('UTF-8', $encoding.'//IGNORE', $s);
    }

    public static function mb_internal_encoding($encoding = null)
    {
        if (null === $encoding) {
            return self::$internalEncoding;
        }

        $encoding = self::getEncoding($encoding);

        if ('UTF-8' === $encoding || false !== @iconv($encoding, $encoding, ' ')) {
            self::$internalEncoding = $encoding;

            return true;
        }

        return false;
    }

    public static function mb_language($lang = null)
    {
        if (null === $lang) {
            return self::$language;
        }

        switch ($lang = strtolower($lang)) {
            case 'uni':
            case 'neutral':
                self::$language = $lang;

                return true;
        }

        return false;
    }

    public static function mb_list_encodings()
    {
        return array('UTF-8');
    }

    public static function mb_encoding_aliases($encoding)
    {
        switch (strtoupper($encoding)) {
            case 'UTF8':
            case 'UTF-8':
                return array('utf8');
        }

        return false;
    }

    public static function mb_check_encoding($var = null, $encoding = null)
    {
        if (null === $encoding) {
            if (null === $var) {
                return false;
            }
            $encoding = self::$internalEncoding;
        }

        return self::mb_detect_encoding($var, array($encoding)) || false !== @iconv($encoding, $encoding, $var);
    }

    public static function mb_detect_encoding($str, $encodingList = null, $strict = false)
    {
        if (null === $encodingList) {
            $encodingList = self::$encodingList;
        } else {
            if (!\is_array($encodingList)) {
                $encodingList = array_map('trim', explode(',', $encodingList));
            }
            $encodingList = array_map('strtoupper', $encodingList);
        }

        foreach ($encodingList as $enc) {
            switch ($enc) {
                case 'ASCII':
                    if (!preg_match('/[\x80-\xFF]/', $str)) {
                        return $enc;
                    }
                    break;

                case 'UTF8':
                case 'UTF-8':
                    if (preg_match('//u', $str)) {
                        return 'UTF-8';
                    }
                    break;

                default:
                    if (0 === strncmp($enc, 'ISO-8859-', 9)) {
                        return $enc;
                    }
            }
        }

        return false;
    }

    public static function mb_detect_order($encodingList = null)
    {
        if (null === $encodingList) {
            return self::$encodingList;
        }

        if (!\is_array($encodingList)) {
            $encodingList = array_map('trim', explode(',', $encodingList));
        }
        $encodingList = array_map('strtoupper', $encodingList);

        foreach ($encodingList as $enc) {
            switch ($enc) {
                default:
                    if (strncmp($enc, 'ISO-8859-', 9)) {
                        return false;
                    }
                    // no break
                case 'ASCII':
                case 'UTF8':
                case 'UTF-8':
            }
        }

        self::$encodingList = $encodingList;

        return true;
    }

    public static function mb_strlen($s, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);
        if ('CP850' === $encoding || 'ASCII' === $encoding) {
            return \strlen($s);
        }

        return @iconv_strlen($s, $encoding);
    }

    public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);
        if ('CP850' === $encoding || 'ASCII' === $encoding) {
            return strpos($haystack, $needle, $offset);
        }

        $needle = (string) $needle;
        if ('' === $needle) {
            trigger_error(__METHOD__.': Empty delimiter', E_USER_WARNING);

            return false;
        }

        return iconv_strpos($haystack, $needle, $offset, $encoding);
    }

    public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);
        if ('CP850' === $encoding || 'ASCII' === $encoding) {
            return strrpos($haystack, $needle, $offset);
        }

        if ($offset != (int) $offset) {
            $offset = 0;
        } elseif ($offset = (int) $offset) {
            if ($offset < 0) {
                $haystack = self::mb_substr($haystack, 0, $offset, $encoding);
                $offset = 0;
            } else {
                $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding);
            }
        }

        $pos = iconv_strrpos($haystack, $needle, $encoding);

        return false !== $pos ? $offset + $pos : false;
    }

    public static function mb_strtolower($s, $encoding = null)
    {
        return self::mb_convert_case($s, MB_CASE_LOWER, $encoding);
    }

    public static function mb_strtoupper($s, $encoding = null)
    {
        return self::mb_convert_case($s, MB_CASE_UPPER, $encoding);
    }

    public static function mb_substitute_character($c = null)
    {
        if (0 === strcasecmp($c, 'none')) {
            return true;
        }

        return null !== $c ? false : 'none';
    }

    public static function mb_substr($s, $start, $length = null, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);
        if ('CP850' === $encoding || 'ASCII' === $encoding) {
            return substr($s, $start, null === $length ? 2147483647 : $length);
        }

        if ($start < 0) {
            $start = iconv_strlen($s, $encoding) + $start;
            if ($start < 0) {
                $start = 0;
            }
        }

        if (null === $length) {
            $length = 2147483647;
        } elseif ($length < 0) {
            $length = iconv_strlen($s, $encoding) + $length - $start;
            if ($length < 0) {
                return '';
            }
        }

        return (string) iconv_substr($s, $start, $length, $encoding);
    }

    public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null)
    {
        $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding);
        $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding);

        return self::mb_strpos($haystack, $needle, $offset, $encoding);
    }

    public static function mb_stristr($haystack, $needle, $part = false, $encoding = null)
    {
        $pos = self::mb_stripos($haystack, $needle, 0, $encoding);

        return self::getSubpart($pos, $part, $haystack, $encoding);
    }

    public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);
        if ('CP850' === $encoding || 'ASCII' === $encoding) {
            return strrchr($haystack, $needle, $part);
        }
        $needle = self::mb_substr($needle, 0, 1, $encoding);
        $pos = iconv_strrpos($haystack, $needle, $encoding);

        return self::getSubpart($pos, $part, $haystack, $encoding);
    }

    public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null)
    {
        $needle = self::mb_substr($needle, 0, 1, $encoding);
        $pos = self::mb_strripos($haystack, $needle, $encoding);

        return self::getSubpart($pos, $part, $haystack, $encoding);
    }

    public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null)
    {
        $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding);
        $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding);

        return self::mb_strrpos($haystack, $needle, $offset, $encoding);
    }

    public static function mb_strstr($haystack, $needle, $part = false, $encoding = null)
    {
        $pos = strpos($haystack, $needle);
        if (false === $pos) {
            return false;
        }
        if ($part) {
            return substr($haystack, 0, $pos);
        }

        return substr($haystack, $pos);
    }

    public static function mb_get_info($type = 'all')
    {
        $info = array(
            'internal_encoding' => self::$internalEncoding,
            'http_output' => 'pass',
            'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)',
            'func_overload' => 0,
            'func_overload_list' => 'no overload',
            'mail_charset' => 'UTF-8',
            'mail_header_encoding' => 'BASE64',
            'mail_body_encoding' => 'BASE64',
            'illegal_chars' => 0,
            'encoding_translation' => 'Off',
            'language' => self::$language,
            'detect_order' => self::$encodingList,
            'substitute_character' => 'none',
            'strict_detection' => 'Off',
        );

        if ('all' === $type) {
            return $info;
        }
        if (isset($info[$type])) {
            return $info[$type];
        }

        return false;
    }

    public static function mb_http_input($type = '')
    {
        return false;
    }

    public static function mb_http_output($encoding = null)
    {
        return null !== $encoding ? 'pass' === $encoding : 'pass';
    }

    public static function mb_strwidth($s, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);

        if ('UTF-8' !== $encoding) {
            $s = iconv($encoding, 'UTF-8//IGNORE', $s);
        }

        $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide);

        return ($wide << 1) + iconv_strlen($s, 'UTF-8');
    }

    public static function mb_substr_count($haystack, $needle, $encoding = null)
    {
        return substr_count($haystack, $needle);
    }

    public static function mb_output_handler($contents, $status)
    {
        return $contents;
    }

    public static function mb_chr($code, $encoding = null)
    {
        if (0x80 > $code %= 0x200000) {
            $s = \chr($code);
        } elseif (0x800 > $code) {
            $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F);
        } elseif (0x10000 > $code) {
            $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
        } else {
            $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
        }

        if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
            $s = mb_convert_encoding($s, $encoding, 'UTF-8');
        }

        return $s;
    }

    public static function mb_ord($s, $encoding = null)
    {
        if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
            $s = mb_convert_encoding($s, 'UTF-8', $encoding);
        }

        if (1 === \strlen($s)) {
            return \ord($s);
        }

        $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0;
        if (0xF0 <= $code) {
            return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80;
        }
        if (0xE0 <= $code) {
            return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80;
        }
        if (0xC0 <= $code) {
            return (($code - 0xC0) << 6) + $s[2] - 0x80;
        }

        return $code;
    }

    private static function getSubpart($pos, $part, $haystack, $encoding)
    {
        if (false === $pos) {
            return false;
        }
        if ($part) {
            return self::mb_substr($haystack, 0, $pos, $encoding);
        }

        return self::mb_substr($haystack, $pos, null, $encoding);
    }

    private static function html_encoding_callback(array $m)
    {
        $i = 1;
        $entities = '';
        $m = unpack('C*', htmlentities($m[0], ENT_COMPAT, 'UTF-8'));

        while (isset($m[$i])) {
            if (0x80 > $m[$i]) {
                $entities .= \chr($m[$i++]);
                continue;
            }
            if (0xF0 <= $m[$i]) {
                $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
            } elseif (0xE0 <= $m[$i]) {
                $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
            } else {
                $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80;
            }

            $entities .= '&#'.$c.';';
        }

        return $entities;
    }

    private static function title_case(array $s)
    {
        return self::mb_convert_case($s[1], MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], MB_CASE_LOWER, 'UTF-8');
    }

    private static function getData($file)
    {
        if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) {
            return require $file;
        }

        return false;
    }

    private static function getEncoding($encoding)
    {
        if (null === $encoding) {
            return self::$internalEncoding;
        }

        $encoding = strtoupper($encoding);

        if ('8BIT' === $encoding || 'BINARY' === $encoding) {
            return 'CP850';
        }
        if ('UTF8' === $encoding) {
            return 'UTF-8';
        }

        return $encoding;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Symfony\Polyfill\Mbstring as p;

if (!function_exists('mb_strlen')) {
    define('MB_CASE_UPPER', 0);
    define('MB_CASE_LOWER', 1);
    define('MB_CASE_TITLE', 2);

    function mb_convert_encoding($s, $to, $from = null) { return p\Mbstring::mb_convert_encoding($s, $to, $from); }
    function mb_decode_mimeheader($s) { return p\Mbstring::mb_decode_mimeheader($s); }
    function mb_encode_mimeheader($s, $charset = null, $transferEnc = null, $lf = null, $indent = null) { return p\Mbstring::mb_encode_mimeheader($s, $charset, $transferEnc, $lf, $indent); }
    function mb_decode_numericentity($s, $convmap, $enc = null) { return p\Mbstring::mb_decode_numericentity($s, $convmap, $enc); }
    function mb_encode_numericentity($s, $convmap, $enc = null, $is_hex = false) { return p\Mbstring::mb_encode_numericentity($s, $convmap, $enc, $is_hex); }
    function mb_convert_case($s, $mode, $enc = null) { return p\Mbstring::mb_convert_case($s, $mode, $enc); }
    function mb_internal_encoding($enc = null) { return p\Mbstring::mb_internal_encoding($enc); }
    function mb_language($lang = null) { return p\Mbstring::mb_language($lang); }
    function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); }
    function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); }
    function mb_check_encoding($var = null, $encoding = null) { return p\Mbstring::mb_check_encoding($var, $encoding); }
    function mb_detect_encoding($str, $encodingList = null, $strict = false) { return p\Mbstring::mb_detect_encoding($str, $encodingList, $strict); }
    function mb_detect_order($encodingList = null) { return p\Mbstring::mb_detect_order($encodingList); }
    function mb_parse_str($s, &$result = array()) { parse_str($s, $result); }
    function mb_strlen($s, $enc = null) { return p\Mbstring::mb_strlen($s, $enc); }
    function mb_strpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strpos($s, $needle, $offset, $enc); }
    function mb_strtolower($s, $enc = null) { return p\Mbstring::mb_strtolower($s, $enc); }
    function mb_strtoupper($s, $enc = null) { return p\Mbstring::mb_strtoupper($s, $enc); }
    function mb_substitute_character($char = null) { return p\Mbstring::mb_substitute_character($char); }
    function mb_substr($s, $start, $length = 2147483647, $enc = null) { return p\Mbstring::mb_substr($s, $start, $length, $enc); }
    function mb_stripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_stripos($s, $needle, $offset, $enc); }
    function mb_stristr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_stristr($s, $needle, $part, $enc); }
    function mb_strrchr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrchr($s, $needle, $part, $enc); }
    function mb_strrichr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrichr($s, $needle, $part, $enc); }
    function mb_strripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strripos($s, $needle, $offset, $enc); }
    function mb_strrpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strrpos($s, $needle, $offset, $enc); }
    function mb_strstr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strstr($s, $needle, $part, $enc); }
    function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); }
    function mb_http_output($enc = null) { return p\Mbstring::mb_http_output($enc); }
    function mb_strwidth($s, $enc = null) { return p\Mbstring::mb_strwidth($s, $enc); }
    function mb_substr_count($haystack, $needle, $enc = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $enc); }
    function mb_output_handler($contents, $status) { return p\Mbstring::mb_output_handler($contents, $status); }
    function mb_http_input($type = '') { return p\Mbstring::mb_http_input($type); }
    function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null) { return p\Mbstring::mb_convert_variables($toEncoding, $fromEncoding, $a, $b, $c, $d, $e, $f); }
}
if (!function_exists('mb_chr')) {
    function mb_ord($s, $enc = null) { return p\Mbstring::mb_ord($s, $enc); }
    function mb_chr($code, $enc = null) { return p\Mbstring::mb_chr($code, $enc); }
    function mb_scrub($s, $enc = null) { $enc = null === $enc ? mb_internal_encoding() : $enc; return mb_convert_encoding($s, $enc, $enc); }
}
{
    "name": "symfony/polyfill-mbstring",
    "type": "library",
    "description": "Symfony polyfill for the Mbstring extension",
    "keywords": ["polyfill", "shim", "compatibility", "portable", "mbstring"],
    "homepage": "https://symfony.com",
    "license": "MIT",
    "authors": [
        {
            "name": "Nicolas Grekas",
            "email": "p@tchwork.com"
        },
        {
            "name": "Symfony Community",
            "homepage": "https://symfony.com/contributors"
        }
    ],
    "require": {
        "php": ">=5.3.3"
    },
    "autoload": {
        "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" },
        "files": [ "bootstrap.php" ]
    },
    "suggest": {
        "ext-mbstring": "For best performance"
    },
    "minimum-stability": "dev",
    "extra": {
        "branch-alias": {
            "dev-master": "1.11-dev"
        }
    }
}
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
require_once 'web.php';


use com\linways\base\connection\MySqlConnector;
use com\linways\base\connection\MySqlQuery;
use com\linways\base\connection\IMySqlConnector;

class BaseService extends MySqlQuery implements IMySqlConnector
{

    /**
     *
     * @var MySqlConnector
     */
    public $mySqlConnector;

    /**
     * Establish a MYSQL Connection
     * If connection and db properties not set this method
     * help to establish the connection.
     * This will be called from MySqlQuery or also you can call externally
     */
    public function establishConnection()
    {
        $this->mySqlConnector = MySqlConnector::getInstance();
        /**
         * This condition is mandatory to check multiple database connections.
         * If not added then connection will be reused from the first project ie projectA service
         * invoked first then mysqlconnection will established based on the projectA DB configuration
         * after that if we invoke projectB service mysqlConnection will be resused from ProjectA bz of
         * singleton instance of mysqlconnection
         * to avoid this situation we need to set 'instanceof' attribute value in setDBConnectionProperties() method
         */
        if($this->mySqlConnector->instanceOf  != null && !$this->mySqlConnector->instanceOf  instanceof BaseService)
        {
            $this->mySqlConnector->closeConnection();
        }
        if(!$this->mySqlConnector->connection)
        {

            $this->setDBConnectionProperties();
        }
        $this->connection = $this->mySqlConnector->getConnection();
    }
    /**
     * Set DB Connection properties
     */
    protected function setDBConnectionProperties()
    {
        $this->mySqlConnector->instanceOf = $this;
        // check DB configurations
        $dbconfigFilePath = getenv('DB_PROFESIONAL_CONFIG');

        //check file configured correctly
        if(empty($dbconfigFilePath))
        {
            throw new ProfessionalException(ProfessionalException::DB_CONFIG_FILE_PATH_NOT_DEFINED_IN_SERVER,"Professional database configuration file 'db_prof_conf.php' path not configured in server conf!.");
        }
        //include db configuration file to load connection properties
        require $dbconfigFilePath;
//         global  $DB_HOST,$DB_USER,$DB_PASSWD,$DB_NAME;
        if (empty ( $DB_HOST ) || empty ( $DB_USER ) || empty ( $DB_PASSWD ) || empty ( $DB_NAME )) {
            throw new ProfessionalException ( ProfessionalException::DB_CONFIG_NOT_SET, "Professional  DB configuration not set properly" );
        }

        //Set DB Connection properties
        $this->mySqlConnector->DB_HOST = $DB_HOST;
        $this->mySqlConnector->DB_USER = $DB_USER;
        $this->mySqlConnector->DB_PASSWD = $DB_PASSWD;
        $this->mySqlConnector->DB_NAME = $DB_NAME;
    }
}


class TestService extends  BaseService{
    public function __construct(){
        $sub = "Computer Organization and
Architecture";
        $a = new \stdClass();
        $a->sub = [$sub];
        print_r($this->realEscapeObject($a));
    }
}


$t = new TestService();
<?php
use com\linways\base\queue\TaskQueue;
require_once 'web.php';
putenv('AMS_CONFIG=test');

$RABBITMQ_HOST = 'localhost';
$RABBITMQ_USER = 'guest';
$RABBITMQ_PASSWORD = 'guest';

$executeHooks = function($data){
    echo "EXECUTE HOOKS working";
    
    // require_once($data->data->CONF);
    // require_once($data->data->DB_PROFESIONAL_CONFIG);
    // // require_once __DIR__ . '/vendor/linways/ams-addons/bootstrap/attachedHooks.php';
    // global $hooks;
    // $hooks->do_action('test_action', $data->data->param1);
  };
  
  $handleMessage = function($msg){
    echo " [x] Received ", $msg->body, "\n";
    $data = json_decode($msg->body);
    switch ($data->command) {
      case 'EXECUTE QUERY':
        global $executeHooks;
        $executeHooks($data);
        break;
      default:
        echo "Invalid command";
    }
    echo " [x] Done", "\n"; 
    TaskQueue::sendACK($msg);
  };
  

$taskQueue = new TaskQueue();

$taskQueue->basicConsume($handleMessage);<?php
require_once 'web.php';
use com\linways\base\logging\Logger;

$nucleusConfPath = '/Library/WebServer/Documents/nucleus_conf.php';
putenv("NUCLEUS_CONF=$nucleusConfPath");
$GLOBALS['COLLEGE_CODE'] = 'PRO';
putenv('DEBUG=false');

$logger = Logger::getLogger('test_channel',__DIR__ . '/test_logger.log');

$logger->info('Test Log', [
    "username" => "test"
]);<?php
namespace test;

use PHPUnit\Framework\TestCase;
use test\toggles\ReleaseToggle;
use test\toggles\ExperimentToggle;

class ToggleTest extends TestCase
{
    public function testReleaseToggleEnabledTrue(){
        $this->assertTrue(ReleaseToggle::isEnabled('FEATURE_1'));
        $this->assertFalse(ReleaseToggle::isEnabled('FEATURE_2'));
        $this->assertTrue(ReleaseToggle::isEnabled('FEATURE_CLT'));
    }

    public function testReleaseToggleEnabledFalse(){
        $this->assertFalse(ReleaseToggle::isEnabled('FEATURE_2'));
    }
    public function testReleaseToggleEnabledUndefined(){
        $this->assertFalse(ReleaseToggle::isEnabled('FEATURE_3'));
    }

    public  function testMixedToggles(){
        $this->assertTrue(ExperimentToggle::isEnabled('FEATURE_3'));
        $this->assertFalse(ReleaseToggle::isEnabled('FEATURE_3'));
        $this->assertFalse(ReleaseToggle::isEnabled('FEATURE_2'));
        $this->assertTrue(ExperimentToggle::isEnabled('FEATURE_2'));

    }
}
<?php
function testHook(){
    echo "This is from a test hook";
}

$hooks->add_action('test_action', 'testHook');
<?php

namespace test\toggles;

use com\linways\base\toggles\FeatureToggleInterface;
use com\linways\base\toggles\FeatureToggle;

class ExperimentToggle extends FeatureToggle implements FeatureToggleInterface
{
    public function initToggles()
    {
        return [
            'FEATURE_1' => false,
            'FEATURE_2' => true,
            'FEATURE_3' => true,
        ];
    }
}<?php
namespace test\toggles;

use com\linways\base\toggles\FeatureToggleInterface;
use com\linways\base\toggles\FeatureToggle;

class ReleaseToggle extends FeatureToggle implements FeatureToggleInterface
{

    public function initToggles(){
        return [
            'FEATURE_1'=> true,
            'FEATURE_2' => false,
            'FEATURE_CLT' => function(){
                return 1===1;
            }
        ];
    }
}<?php
require_once 'web.php';
use com\linways\base\hooks\LinwaysHooks;

define('HOOKS_LOADER', __DIR__ . '/testHook.php');

$hooks = LinwaysHooks::getInstance();

$hooks->do_action('test_action');
<?php
require_once "../vendor/autoload.php";
$dbPath = '/Library/WebServer/Documents/ams/db_prof_conf.php';
putenv("DB_PROFESIONAL_CONFIG=$dbPath");

<?php
use com\linways\base\dto\Email;
use com\linways\base\dto\EmailTo;
use com\linways\base\service\EmailService;
use com\linways\base\dto\EmailFrom;

require_once 'web.php';


$emailService = EmailService::getInstance();

try {
    $SMTPHost = "smtp.gmail.com";
    $SMTPPort = 465;
    $mailID = "linwaysdev@gmail.com";
    $mailpassword =  "linwaysdev@2017";
    
    $email = new Email();
    
    $emailFrom = new EmailFrom();
    $emailFrom->email = "linwaysdev@gmail.com";
    
    $emailTo = new EmailTo();
    $emailTo->name = "Arun";
    $emailTo->email = "aruntg@linways.com";
    
    $email->from = $emailFrom;
    $email->to = $emailTo;
    
    $email->body = "Txxsdsfest Arun";
    $email->subject = "Treat";
    
    var_dump($emailService->sendEmail($email));
} catch (Exception $e) {
    
    var_dump($e);
}<?php
require_once 'web.php';

use com\linways\base\queue\TaskQueue;
putenv('AMS_CONFIG=test');

$RABBITMQ_HOST = 'localhost';
$RABBITMQ_USER = 'guest';
$RABBITMQ_PASSWORD = 'guest';


class TaskQueueTest extends TaskQueue{

    function __construct($conf){
        $conf = [getenv('AMS_CONFIG')];
        parent::__construct($conf);
    }
}

$conf = getenv('AMS_CONFIG');
$taskQueue = new TaskQueueTest($conf);

$taskQueue->enqueue("EXECUTE QUERY", ["param1" => "rohith"]);{
  "name": "linways/linways-base",
  "description": "Linways base",
  "require": {
    "php": ">=5.4.0",
    "php-amqplib/php-amqplib": "^2.7",
    "voku/php-hooks": "^0.2.11",
    "ezyang/htmlpurifier": "^4.10",
    "monolog/monolog": "^1.23",
    "elasticsearch/elasticsearch": "^5.3"

  },
  "minimum-stability": "stable",
  "autoload": {
    "psr-4": {
      "com\\linways\\base\\": "src/com/linways/base"
    }
  },
  "autoload-dev": {
    "psr-4": {
      "test\\": "test"
    }
  },

  "require-dev": {
    "phpunit/phpunit": "^5.7"
  }
}
<?php
namespace com\linways\base\dto;

use com\linways\base\dto\BaseDTO;

class Email extends BaseDTO
{

    /**
     * Email From : Object of EmailFrom
     * 
     * @var EmailFrom
     */
    public $from;

    /**
     * Email To : Object of EmailTo
     * 
     * @var EmailTo
     */
    public $to;
    /**
     * Email To : Object of EmailTo
     *
     * @var EmailTo
     */
    public $ccList;
    /**
     * Email To : Object of EmailTo
     *
     * @var array [EmailTo]
     */
    public $toList;
    // /**
    // * Email To Addresses : List of EmailTo Objects
    // * @var array of EmailTo
    // */
    // public $to = [];
    /**
     * Email Subject
     * 
     * @var string
     */
    public $subject;

    /**
     *
     * @var string
     */
    public $body;

    /**
     * Word wrap default is 50
     * 
     * @var int
     */
    public $wordWrap = 50;

    /**
     * Set message type to HTML/Plain.Default is HTML.
     * 
     * @var int
     */
    public $isHtml = TRUE;
    
    /**
     * 
     * @var array[EmailAttachment]
     */
    public $attachments=[];
    
    /**
     * If you want to keep the attachement after email successfully send set the value to 'FALSE' else set to 'TRUE'  to
     *  delete the attachment from specified path
     * @var string
     */
    public $deleteAttachmentsAfterSend = FALSE;

    /**
     * Supported mailer type to send mail: MAIL,SEND_MAIL,SMTP
     * 
     * @var string
     */
    const MAIL = "mail";

    const SEND_MAIL = "sendmail";

    const SMTP = "smtp";

    public $mailerType = self::SMTP;
    
    /**
     * Constructor for email
     * @param EmailFrom $from
     * @param array $toList ( [EmailTo])
     * @param string $subject
     * @param string $body
     */
    public function __construct($from = null,$toList = null,$subject = null,$body = null)
    {
        $this->from = $from;
        $this->toList = $toList;
        $this->body = $body;
        $this->subject = $subject;
    }
}<?php
namespace com\linways\base\dto;

use com\linways\base\dto\BaseDTO;

class EmailFrom extends BaseDTO
{

    public $email;

    public $name;

    public function __construct($email = null, $name  = null)
    {
        $this->email = $email;
        $this->name = $name;
    }
}<?php
namespace com\linways\base\dto;

class EmailAttachment extends BaseDTO
{
    public $path;
    public $fileName;
    public $type="application/octet-stream";
    public $encoding = "base64";
}

<?php
namespace com\linways\base\dto;

use com\linways\base\dto\BaseDTO;

class EmailCC extends BaseDTO
{

    public $email;

    public $name;

    public function __construct($email = null, $name = NULL)
    {
        $this->email = $email;
        $this->name = $name;
    }
}<?php
namespace com\linways\base\dto\sms;

use com\linways\base\dto\BaseDTO;

class SMS extends BaseDTO
{
    const ROUTE_TRANSACTIONAL = "ROUTE_TRANSACTIONAL";
    const ROUTE_PROMOTIONAL = "ROUTE_PROMOTIONAL";
    
    const STATUS_SYSTEM_ERROR = "SYSTEM_ERROR";
    const STATUS_SMS_PROVIDER_ERROR = "SMS_PROVIDER_ERROR";
    const STATUS_SUCCESS = "SUCCESS";
    const STATUS_FAILED = "FAILED";
     public $mobileNos=[];
     public $message;
     /**
      * Specify which module sms provider should use while sending the SMS
      * <p> Default value is 'ALL'</p>
      * eg: Admission module : sms must send from Admission module SMS provider.
      * If the customer is using single SMS Provider for all purpose set 'ALL' else specify the module 
      * @var string
      */
     public $module = "ALL";
     /**
      * Specify from which context/menu/section sms is triggering
      *
      * eg: Staff Module-> SMS&Email Menu : set context = 'SMS_EMAIL'
      * <p> This is used to identify from which context/menu/section user send sms
      * @var string
      */
     public $context;
     
     /**
      * SMS Route option: Default is TRANSACTIONAL
      * @var string
      */
     public $route="ROUTE_TRANSACTIONAL";
     
}<?php
namespace com\linways\base\dto\sms;

use com\linways\base\dto\BaseDTO;

class SMSProviderAttributeValue extends BaseDTO
{

    public $id;

    public $smsProviderAttributeId;

    /**
     *
     * @var SMSProviderAttribute
     */
    public $smsProviderAttribute;

    public $smsProviderModuleId;

    /**
     *
     * @var SMSProviderModule
     */
    public $smsProviderModule;

    public $value;
}

<?php
namespace com\linways\base\dto\sms;

class SMSResponse
{
    public $success = false;
    public $message;
    public $errorCode;
}

<?php
namespace com\linways\base\dto\sms;

use com\linways\base\dto\BaseDTO;

class SMSProviderModule extends BaseDTO
{

    public $id;

    public $smsProviderId;

    /**
     *
     * @var SMSProvider
     */
    public $smsProvider;

    public $module;

//     public $category;
}

<?php
namespace com\linways\base\dto\sms;

use com\linways\base\dto\BaseDTO;

class SMSProviderAttribute extends BaseDTO
{
    
    const SMS_USERNAME = "SMS_USERNAME";
    const SMS_PASSWORD = "SMS_PASSWORD";
    
    public $id;
    public $name;
    public $code;
    public $smsProviderId;
    /**
     * 
     * @var SMSProvider
     */
    public $smsProvider;
    /**
     * 
     * @var SMSProviderAttributeValue
     */
    public $smsProviderAttributeValue;
}<?php
namespace com\linways\base\dto\sms;

use com\linways\base\dto\BaseDTO;

class SMSProvider extends BaseDTO
{

    const XPRESSSMS = "XPRESSSMS";

    const GLOZNET = "GLOZNET";

    const TECHSOL = "TECHSOL";

    const BHASHSMS = "BHASHSMS";

    const ZORZACORP = "ZORZACORP";

    const BULK_SMS = "BULK_SMS";

    const SCHOOL_SERVE_SMS = "SCHOOL_SERVE_SMS";

    const MY_SMS_MANTRA = "MY_SMS_MANTRA";

    const MOB_TEXTING = "MOB_TEXTING";

    const SMS_GUPSHUP = "SMS_GUPSHUP";

    const SMS_COUNTRY = "SMS_COUNTRY";

    const SMS_AVEON = "SMS_AVEON";

    const MITHRA = "MITHRA";

    const AASTHA = "AASTHA";

    const AVENTSMS = "AVENTSMS";

    const BULK_SMS_GREEN_ADS = "BULK_SMS_GREEN_ADS";

    const RITSGATEWAY = "RITSGATEWAY";
    const FASTSMS = "FASTSMS";
    const SAPTELESERVICES = "SAPTELESERVICES";
    const BLESSSMS = "BLESSSMS";
    const HINDUSTANSOFT = "HINDUSTANSOFT";
    const MOPLET = "MOPLET";

    public $id;

    public $smsProviderModuleId;

    public $name;

    public $code;

    public $url;

    /**
     *
     * @var SMSProviderAttribute[]
     */
    public $smsProviderAttributes = [];
}

<?php
namespace com\linways\base\dto;

class PageSupport
{

    const DEFAULT_PAGE_SIZE = 5000;

    /**
     * Starting page no
     * 
     * @var int
     */
    public $startIndex = 0;

    /**
     * count of no of records
     * 
     * @var int
     */
    public $endIndex = self::DEFAULT_PAGE_SIZE;

    public $pageSize = self::DEFAULT_PAGE_SIZE;
    
}<?php
namespace com\linways\base\dto;

class BaseDTO
{

    /**
     * 
     * @var int
     */
    public $createdBy; 
    /**
    *
    * @var string
    */
    public $createdUserName;

    /**
     *Type of user eg:STAFF,ADMIN
     *Refer UserType class
     * @var string
     */
    public $createdByUserType;

    /**
     *
     * @var date
     */
    public $createdDate;

    /**
     *
     * @var date
     */
    public $updatedDate;

    /**
     *
     * @var int
     */
    public $updatedBy;
    /**
     *
     * @var string
     */
    public $updatedUserName;

    /**
     *Type of user eg:STAFF,ADMIN
     **Refer UserType class
     * @var string
     */
    public $updatedByUserType;

    public function __construct($resultObject = NULL)
    {
        if ($resultObject != NULL) {
            $this->createdBy = $resultObject->createdBy;
            $this->createdDate = $resultObject->createdDate;
            $this->updatedDate = $resultObject->updatedDate;
            $this->updatedBy = $resultObject->updatedBy;
            $this->createdByUserType = $resultObject->createdByUserType;
            $this->updatedByUserType = $resultObject->updatedByUserType;
        }
    }
}<?php
namespace com\linways\base\dto;
use com\linways\base\dto\BaseDTO;

class EmailTo extends BaseDTO
{
    public $email;
    public $name;
    
    public function __construct($email = null,$name = NULL)
    {
        $this->email = $email;
        $this->name = $name;
    }
}<?php
namespace com\linways\base\dto;

class AjaxPaginator
{
    
    // Maximum data will be displayed on page
    public $maxDataPerPage;
    
    // max page number link in a page
    public $maxTotalLinkPage;

    /**
     *
     * @var integer
     */
    public $totalRecords = 0;

    /**
     * Render paging components
     * @param number $start
     * @return string
     */
    public function render($start = 0)
    {
        $totalData = $this->totalRecords;
        $totalPage = ceil($totalData / $this->maxDataPerPage);
        
        if ($totalPage > 1) {
            $nav = "<ul class='pagination mtm mbm'>";
            // Specify page index.
            $iPage = ($start / $this->maxDataPerPage) + 1;
            
            // Specify start and stop index of page will be displayed as link
            if (($iPage + $this->maxTotalLinkPage - 1) > $totalPage) {
                $startPage = $totalPage - $this->maxTotalLinkPage;
                $endPage = $totalPage;
            } else {
                $startPage = $iPage - 1;
                $endPage = $startPage + $this->maxTotalLinkPage;
            }
            if ($startPage <= 0)
                $startPage = 1;
            
            if ($totalPage > $this->maxTotalLinkPage) {
                if ($startPage == 1)
                    $nav .= '<li class="disabled"><a href="#"><< First</a></li>';
                else
                    $nav .= "<li><a href=\"#\" onClick=\"jumpPage(0); return false;\"><< First</a></li> ";
            }
            
            for ($i = $startPage; $i <= $endPage; $i ++) {
                $startVal = ($i - 1) * $this->maxDataPerPage;
                if ($startVal == $start)
                    $nav .= '<li class="active"><a href="#">' . $i . '</a></li>';
                else
                    $nav .= "<li><a href=\"#\" onClick=\"jumpPage($startVal); return false;\">$i </a></li>";
            }
            
            if ($totalPage > $this->maxTotalLinkPage) {
                $startVal = ($totalPage - 1) * $this->maxDataPerPage;
                if ($startPage == ($totalPage - $this->maxTotalLinkPage))
                    $nav .= '<li class="disabled"><a href="#">Last >></a></li>';
                else
                    $nav .= "<li><a href=\"#\" onClick=\"jumpPage($startVal); return false;\">Last >></a></li>";
            }
            $nav .= '</ul>';
            return $nav;
        }
    }
}<?php
namespace com\linways\base\dto;

class MySqlResult
{

    public $sqlResult;

    public $id;
}<?php
/**
 * Created by PhpStorm.
 * User: rohithb
 * Date: 16/10/18
 * Time: 7:38 PM
 */

namespace com\linways\base\dto;


class Toggle
{
    /**
     * Name of the Toggle
     * @var string
     */
    public $name;
    /**
     * Unique Code for toggle
     * This should be in capital case separated by underscores.
     * EG: AUTOMATIC_TIMETABLE_ALLOCATION
     * @var string
     */
    public $code;
    /**
     * Short description about this toggle
     * @var string
     */
    public $description;
    /**
     * (Optional)
     * Code of parent toggle if any
     * @var string
     */
    public $parentToggleCode;
    /**
     * (Optional)
     * Code of product variants in which this feature is enabled. eg:["LITE","PROFESSIONAL"] or ['ALL']
     * "ALL" Should not be used along with other product variant.
     * You can use "ALL" in $enabledProductVariants and except a particular variant in $disabledProductVariants.
     * Eg: $enabledProductVariants = ["ALL"] and $disabledProductVariants = ["LITE"]
     * This will enable a particular feature on all variants except "LITE"
     * @var string[]
     */
    public $enabledProductVariants;
    /**
     * (Optional)
     * Code of product variants in which this feature is disabled. eg:["LITE","PROFESSIONAL"] or ["ALL"]
     * "ALL" Should not be used along with other product variant.
     * You can use "ALL" in $disabledProductVariants and except a particular variant in $enabledProductVariants.
     * Eg: $enabledProductVariants = ["ENTERPRISE"] and $disabledProductVariants = ["ALL"]
     * This will disable a particular feature on all variants except "ENTERPRISE"
     * @var string[]
     */
    public $disabledProductVariants;
    /**
     * (Optional)
     * Enable this feature to colleges by overriding their product variant.
     * This can be used to override the product variant configuration a particular college.
     * ie., If you specify a college code in $enabledCollegeCodes for a feature, that feature will be enabled
     * for this college even though it is disabled for the product variant of that college
     * NB: "ALL" is not supported
     * @var string[]
     */
    public $enabledCollegeCodes;
    /**
     * (Optional)
     * Disable this feature to colleges by overriding their product variant.
     * This can be used to override the product variant configuration a particular college.
     * ie., If you specify a college code in $disabledCollegeCodes for a feature, that feature will be disabled
     * for this college even though it is enabled for the product variant of that college
     * NB: "ALL" is not supported
     * @var string[]
     */
    public $disabledCollegeCodes;

    /**
     * Toggle constructor.
     * @param array $params
     *                  ['name'] string Name of the Toggle
     *                  ['code'] string Code of parent toggle if any
     *                  ['description'] string Short description about this toggle
     *                  ['parentToggleCode'] string (optional) Code of parent toggle if any
     *                  ['enabledProductVariants'] string[] (optional) Code of product variants in which this feature is enabled. eg:["LITE","PROFESSIONAL"] or ['ALL']
     *                  ['disabledProductVariants'] string[] (optional) Code of product variants in which this feature is disabled. eg:["LITE","PROFESSIONAL"] or ["ALL"]
     *                  ['enabledCollegeCodes'] string[] (optional) Enable this feature to colleges by overriding their product variant.
     *                  ['disabledCollegeCodes'] string[] (optional) Disable this feature to colleges by overriding their product variant.
     */
    public function __construct($params=[])
    {
        if(!empty($params)){
            $this->name = $params['name'];
            $this->code = $params['code'];
            $this->description = $params['description'];
            $this->parentToggleCode = $params['parentToggleCode'];
            $this->enabledProductVariants = $params['enabledProductVariants'];
            $this->disabledProductVariants = $params['disabledProductVariants'];
            $this->enabledCollegeCodes = $params['enabledCollegeCodes'];
            $this->disabledCollegeCodes = $params['disabledCollegeCodes'];
        }
    }

}<?php
namespace com\linways\base\test;

use com\linways\base\exception\CoreException;
use PHPUnit\Framework\TestCase;
/**
 * Base class for all unittest cases.
 * @author Rohith Bhaskaran
 */
class BaseTestCase extends TestCase
{

  /**
   * @return [type] Mysqli database connection
   */
  private function getConnection($DB_CONF_ENV_VAR='TEST_DB_CONFIG'){
    // Reading db config for runnning tests from environment
    $dbconfigFilePath = getenv($DB_CONF_ENV_VAR);
    if (empty($dbconfigFilePath)) {
        throw new \Exception("Database config for testing is not found. Please set env `TEST_DB_CONFIG`");
    }
    require $dbconfigFilePath;
    if (empty ( $DB_HOST ) || empty ( $DB_USER ) || empty ( $DB_PASSWD ) || empty ( $DB_NAME )) {
        throw new CoreException ( CoreException::DB_CONFIG_NOT_SET, 'DB config for test not set properly. Please set `$DB_HOST`, `$DB_USER`, `$DB_PASSWD` and `$DB_NAME`' );
    }
    $connection = mysqli_connect($DB_HOST, $DB_USER, $DB_PASSWD, $DB_NAME);
    return $connection;
  }

    /**
     * Initializes database using sql file
     * @param $sqlFile
     * @throws CoreException
     * @throws \Exception
     */
  public function setInitialDataUsingSQLFile($sqlFile, $DB_CONF_ENV_VAR='TEST_DB_CONFIG'){
      $dbconfigFilePath = getenv($DB_CONF_ENV_VAR);
      if (empty($dbconfigFilePath)) {
          throw new \Exception("Database config for testing is not found. Please set env `TEST_DB_CONFIG`");
      }
      require $dbconfigFilePath;
      if (empty ( $DB_HOST ) || empty ( $DB_USER ) || empty ( $DB_PASSWD ) || empty ( $DB_NAME )) {
          throw new CoreException ( CoreException::DB_CONFIG_NOT_SET, 'DB config for test not set properly. Please set `$DB_HOST`, `$DB_USER`, `$DB_PASSWD` and `$DB_NAME`' );
      }
      exec("mysql -u {$DB_USER} -p{$DB_PASSWD}  {$DB_NAME}< {$sqlFile}");
  }

  /**
   * Helper function to assertDatabaseHas and assertDatabaseHasNot
   * @param  string $tableName Name of the table
   * @param  array $map        A assoc array with atribute name as
   *                           key and attribute value as value.
   * @return object            {result, query} Object with result and prepared query
   */
  public function assertDatabaseHelper($tableName, $map)
  {
      $returnObject = new \stdClass();
      if (! isset($tableName) || ! isset($map)) {
          throw new CoreException('Database name and field map is required');
      }
      $connection = $this->getConnection();
      $query = "SELECT * from $tableName where 1=1 ";
      foreach ($map as $key => $value) {
          $query .= 'and '.$key . "='" . $value . "'";
      }
      $result = mysqli_query($connection, $query);
      mysqli_close($connection);
      $returnObject->result = $result;
      $returnObject->query = $query;
      return $returnObject;
  }

    /**
     * Assert a given database table contain a row with attributes specified in $map
     * @param  string $tableName Name of the table
     * @param  array $map        A assoc array with atribute name as
     *                           key and attribute value as value.
     */
    public function assertDatabaseHas($tableName, $map)
    {
        $result = $this->assertDatabaseHelper($tableName, $map);
        $message = "\033[31m Expected data not found on table `$tableName`. \n Query:$result->query \n\033[0m";
        $this->assertTrue(mysqli_num_rows($result->result) > 0, $message);
    }


    /**
     * Assert a given database table does not contain a row with attributes specified in $map
     * @param  string $tableName Name of the table
     * @param  array $map        A assoc array with atribute name as
     *                           key and attribute value as value.
     */
    public function assertDatabaseHasNot($tableName, $map)
    {
        $result = $this->assertDatabaseHelper($tableName, $map);
        $message = "\033[31m Expected data not found on table `$tableName`. \nQuery: $result->query \n\033[0m";
        $this->assertEquals(0, mysqli_num_rows($result->result), $message);
    }




    /**
     * Trucate a given database table
     * @param $tableNames   string/array: Name of a single table. or array of names of tables to clear.
     * @throws CoreException
     * @throws \Exception
     */
    public function clearDBTable($tableNames, $DB_CONF_ENV_VAR='TEST_DB_CONFIG')
    {
        if (! isset($tableNames)) {
            throw new CoreException('table name  is required');
        }
        // For clearing multiple table in a single command
        if(is_array($tableNames)){
            foreach ($tableNames as $tableName){
                $this->clearDBTable($tableName, $DB_CONF_ENV_VAR);
            }
        }else{
          $connection = $connection = $this->getConnection($DB_CONF_ENV_VAR);
          mysqli_query($connection, "SET FOREIGN_KEY_CHECKS = 0");
          mysqli_query($connection, "TRUNCATE table $tableNames");
          mysqli_query($connection, "SET FOREIGN_KEY_CHECKS = 1");
        }
    }
}
<?php
namespace com\linways\base\messages;

class CommonMessages
{

    const NO_ACTION_DEFINED = "No action defined!";

    const FETCH_ERROR = "An error occured while fetching the data.Please contact systemadminsitrator!";

    const SAVE_FAILED_ERROR = "An error occured while saving the details.Please try again or contact system administrator!";

    const DELETE_SUCCESS = "Record deleted successfully!.";

    const DELETE_ERROR = "An error occured while deleting the record.Please try again or contact system administrator!.";
}<?php
namespace com\linways\base\response;
/**
 * @author arun
 *
 */
class BaseResponse
{
    /**
     * used for pagination
     * @var integer
     */
    public $totalRecords;
    
}<?php

namespace com\linways\base\cache;
/**
 * LinCache - Server memory cache manager
 * 
 * @author arun
 *
 */
class LinCache {
	private $cache = null;
	const DEFAULT_SERVER_IP = "127.0.0.1";
	const DEFAULT_PORT = 11211;
	
	/**
	 * If server ip and port is  not set then setting default config 
	 * @param string $serverIP
	 * @param int $port
	 */
	public function __construct($serverIP = NULL, $port = NULL) {
		$this->init ( $serverIP, $port );
	}
	/**
	 * Inititalize the mem server
	 * @param string $serverIP
	 * @param int $port
	 */
	public function init($serverIP = NULL, $port = NULL) {
		$serverIP = empty ( $serverIP ) ? self::DEFAULT_SERVER_IP : $serverIP;
		$port = empty ( $port ) ? self::DEFAULT_PORT : $port;
		
		$this->cache = new \Memcached ();
		$this->cache->addServer ( $serverIP, $port );
	}
	
	/**
	 * Get value from cache
	 * @param string $key
	 * @return object
	 */
	public function getValue($key) {
		$value = null;
		$result = $this->cache->get ( $key );
		
		$value = ! $result ? null : $result;
		
		return $value;
	}
	/**
	 * Set value in cache
	 * @param string $key
	 * @param object $value
	 */
	public function setValue($key, $value) {
		$this->cache->set ( $key, $value );
	}

    /**
     * Invalidate all items in the cache
     * @link http://php.net/manual/en/memcached.flush.php
     * @param int $delay [optional] <p>
     * Numer of seconds to wait before invalidating the items.
     * </p>
     * @return bool <b>TRUE</b> on success or <b>FALSE</b> on failure.
     */
	public function flush($delay = 0){
        return $this->cache->flush($delay);
    }
}<?php
namespace com\linways\base\connection;

use com\linways\base\exception\CoreException;
use com\linways\base\util\MySqlUtil;

/**
 * MySql Connector class for managing mysql connection
 * <p>This class is responsible for supplying DB connection properties.Which has to be set externally
 * 
 * </p>@author arun
 *        
 */
class MySqlConnector 
{
    /**
     * To store the instance of base service. To use multiple connection
     * @var object  (Object of BaseService of project)
     */
    public $instanceOf;
    /**
     * DataBase Host eg:localhost
     * @var string
     */
    public $DB_HOST;
    /**
     * DataBase username 
     * @var string
     */
    public $DB_USER;
    /**
     * DataBase password
     * @var string
     */
    public $DB_PASSWD;
    /**
     * DataBase name
     * @var string
     */
    public $DB_NAME;
    private static $_instance = null;

    public  $connection = NULL;
    // /Condition 2 - Locked down the constructor
    private function __construct()
    {}
    // Prevent any oustide instantiation of this class
    
    // /Condition 3 - Prevent any object or instance of that class to be cloned
    private function __clone()
    {}
    // Prevent any copy of this object
    
    // /Condition 4 - Have a single globally accessible static method
    public static function getInstance()
    {
        if (! is_object(self::$_instance))
        {
            // or if( is_null(self::$_instance) ) or if( self::$_instance == null )
            self::$_instance = new self();
        }
        
        return self::$_instance;
    }
 
    /**
     * Get DB Connection
     *
     * @param
     *            $dbConfigPath
     * @throws CoreException
     * @return NULL|\mysqli
     */
    public function getConnection()
    {
        
        // Create connection
        // If connection exist reuse the connection else create new connection
        if ($this->connection == NULL) {
            // check DB configurations
            
//             $dbconfigFilePath = getenv($dbConfigPath);
            
//             // check filed configured correctly
//             if (empty($dbconfigFilePath)) {
//                 throw new CoreException(CoreException::DB_CONFIG_FILE_PATH_NOT_DEFINED_IN_SERVER, "Database configuration '$dbConfigPath' not configured in server conf!.");
//             }
            
//             // include db configuration file to load connection properties
//             require_once $dbconfigFilePath;
            
            if (empty($this->DB_HOST) || empty($this->DB_USER) || empty($this->DB_PASSWD) || empty($this->DB_NAME)) {
                throw new CoreException(CoreException::DB_CONFIG_NOT_SET, "DB configuration connection variables not set properly");
            }
            
            $this->connection = new \mysqli($this->DB_HOST, $this->DB_USER, $this->DB_PASSWD, $this->DB_NAME);
        }
        
        // Check connection
        if ($this->connection->connect_error) {
            $this->connection = NULL;
            throw new CoreException(CoreException::CONNECTION_FAILED, $this->connection->connect_error);
        }
        return $this->connection;
    }

    /**
     * close db connection
     *
     * @param unknown $connection            
     */
    public function closeConnection()
    {
        // close connection if exist
        if ($this->connection) {
            MySqlUtil::closeConnection($this->connection);
        }
        
        $this->connection = NULL;
    }

    public function __destruct()
    {
        // close connection if exist
        $this->closeConnection();
    }
}<?php
namespace com\linways\base\connection;
/**
 * Interface for establishing mysqlConnection
 * <p> This interface must implmenet in subproject/projects to getconnection,which is used in MySqlQuery.</p>
 * @author arun
 *
 */
interface IMySqlConnector
{
    
    public function establishConnection();
}<?php
namespace com\linways\base\connection;

use com\linways\base\dto\MySqlResult;
use com\linways\base\exception\SQLException;
use com\linways\base\mapper\Result;
use com\linways\base\util\MapperUtil;
use com\linways\base\util\ObjectUtil;

class MySqlQuery implements IMySqlConnector

{

    /**
     *
     * @var \mysqli connection
     */
    public $connection;

    /**
     * Method for executing the mysql query
     *
     * @param string $sql
     *            : sql to execute
     * @param boolean $isReturnKey
     *            : If you
     *            need id in case of add operation specify
     *            true to get the last inserted id
     */
    protected function executeQuery($sql, $isReturnKey = FALSE)
    {
        $sqlresult = NULL;
        // Establish the connection by invoking establishConnection which is defined in IMySqlConnector .
        //
        $this->establishConnection();
        
        $result = mysqli_query($this->connection, $sql);
        
        try {
            $sqlresult = $this->processResult($result, $isReturnKey);
        } catch (\Exception $e) {
            throw $e;
        }
        
        return $sqlresult;
    }

    /**
     * Method for executing the mysql query and return result as object list
     *
     * @param string $sql
     *            : sql to execute
     * @param boolean $isReturnKey
     *            : If you
     *            need id in case of add operation specify
     *            true to get the last inserted id
     * @return Object List
     */
    protected function executeQueryForList($sql, $mapper = NULL)
    {
        $sqlresult = NULL;
        $objectList = [];
        // Establish the connection by invoking establishConnection which is defined in IMySqlConnector .
        //
        $this->establishConnection();
        
        $result = mysqli_query($this->connection, $sql);
        
        try {
            $sqlresult = $this->processResult($result);
            
            if (empty($mapper)) {
                $objectList = ObjectUtil::fetchObjectList($sqlresult);
            }
            else {
                $objectList = MapperUtil::fetchObjectList($sqlresult,$mapper);
            }
        } catch (\Exception $e) {
            throw $e;
        }
        
        return $objectList;
    }

    private function getConnection()
    {
        $this->establishConnection();
        return $this->connection;
    }

    /**
     * Method for executing the mysql query and return result as object
     *
     * @param string $sql
     *            : sql to execute
     * @param boolean $isReturnKey
     *            : If you
     *            need id in case of add operation specify
     *            true to get the last inserted id
     * @return Object - if $isReturnKey is TRUE then return id else queryresult object
     */
    protected function executeQueryForObject($sql, $isReturnKey = FALSE, $mapper = null)
    {
        $sqlresult = NULL;
        $object = NULL;
        // Establish the connection by invoking establishConnection which is defined in IMySqlConnector .
        //
        // $this->establishConnection();
        
        $result = mysqli_query($this->getConnection(), $sql);
        
        try {
            $sqlresult = $this->processResult($result, $isReturnKey);
            
            if ($isReturnKey) {
                $object = $sqlresult->id;
            } else {
                
                if ($mapper == null) {
                    $object = ObjectUtil::fetchObject($sqlresult);
                } else {
                    
                    $object = MapperUtil::fetchObject($sqlresult, $mapper);
                }
            }
        } catch (\Exception $e) {
            throw $e;
        }
        
        return $object;
    }

    /**
     * Process the mysql result
     *
     * @param unknown $mysqlResult            
     * @param unknown $isReturnKey            
     * @throws unknown
     * @return \com\linways\base\dto\MySqlResult
     */
    private function processResult($mysqlResult, $isReturnKey = FALSE)
    {
        $sqlresult = NULL;
        
        if (! $mysqlResult) {
            
            $sqlException = $this->parseSqlException();
            throw $sqlException;
        }
        
        $sqlresult = new MySqlResult();
        $sqlresult->sqlResult = $mysqlResult;
        $sqlresult->id = $isReturnKey ? mysqli_insert_id($this->connection) : NULL;
        
        return $sqlresult;
    }

    /**
     * Parse and create sql exception
     *
     * @return \com\linways\base\exception\SQLException
     */
    private function parseSqlException()
    {
        $errorCode = NULL;
        $errorMessage = mysqli_error($this->connection);
        switch (mysqli_errno($this->connection)) {
            case 1062:
                $errorCode = SQLException::DUPLICATE_ENTRY;
                break;
            case 1215:
                $errorCode = SQLException::CANNOT_ADD_FOREIGN_KEY;
                break;
            case 1451:
                $errorCode = SQLException::CANNOT_DELETE_OR_UPDATE_ROW_FOREIGN_KEY_FAILED;
                break;
            
            default:
                // TO DO Add more errors
                $errorCode = mysqli_errno($this->connection);
                break;
        }
        return new SQLException($errorCode, $errorMessage);
    }

    /**
     * mysql real escape string
     *
     * @param $string
     * @param bool $encodeHtmlSpecialChars Escape html special characters. Only disable to handle old implemetations.
     * @return string
     */
    public function realEscapeString($string, $encodeHtmlSpecialChars=true)
    {
        if($encodeHtmlSpecialChars)
            return mysqli_real_escape_string($this->getConnection(), $this->escapeHtmlSpecialChars($string));
        else
            return mysqli_real_escape_string($this->getConnection(), trim($string));
    }

    private function escapeHtmlSpecialChars($string){
        return trim(htmlspecialchars($string, ENT_COMPAT | ENT_HTML401, ini_get("default_charset"), false));
    }

    /**
     * mysql real escape array of objects
     *
     * @param array $objArray
     */
    public  function realEscapeArray($objArray)
    {
    	if(!empty($objArray))
    	{
    		foreach ($objArray as $key => $object) {
    			$objArray[$key] = $this->realEscapeObject($object);
    		}
    
    	}
    	return $objArray;
    }
    /**
     * mysql real escape object
     *
     * @param object $object
     * @return object
     */
    public function realEscapeObject($object)
    {
        if(!empty($object))
        {
            if(is_array($object) || is_object($object)){
                foreach ($object as $key => $value) {
                    if(is_array($value)){
                        $object->{$key} = $this->realEscapeArray($value);
                    }
                    else if(is_object($value)) {
                        $object->{$key} = $this->realEscapeObject($value);
                    }
                    else{
                        $object->{$key} = $this->realEscapeString($value);
                    }
                }
            }else{
                return $this->realEscapeString($object);
            }
        }
        return $object;
    }

    /**
     *
     * {@inheritdoc}
     *
     * @see \com\linways\base\connection\IMySqlConnector::establishConnection()
     */
    public function establishConnection()
    {
        // TODO: Auto-generated method stub
    }
    /**
     * Start the sql transaction 
     * <p>
     *    Note: MyISAM does not support the transaction, however, InnoDB does.
     * </p>
     */
    public function beginTransaction()
    {
        $this->executeQuery("START TRANSACTION");
    }
    /**
     * Commit the changes you made for the sql queries this is applicable when you use beginTransaction() 
     */
    public function commit()
    {
        $this->executeQuery("COMMIT");
    }
    /**
     * Rollback the changes you made for current transactions
     */
    public function rollBack()
    {
        $this->executeQuery("ROLLBACK");
    }
}<?php
namespace com\linways\base\util;

/**
 *
 * @author arun
 *        
 */
class StringUtil
{

    /**
     * replace_tags replaces tags in the source string with data from the tags map.
     * The tags are identified by being wrapped in '{{' and '}}' i.e. '{{tag}}'.
     * If a tag value is not present in the tags map, it is replaced with an empty
     * string
     * 
     * @param string $string
     *            A string containing 1 or more tags wrapped in '{{}}'.Eg.Hi student {{studentName}}.Hi teacher {{teacherName}}
     * @param array $tags
     *            A map of key-value pairs used to replace tags.Eg. array('studentName'=>'Rohith','teacherName'=>'Bastin')
     * @param
     *            force_lower if true, converts matching tags in string via strtolower()
     *            before checking the tags map.
     * @return string The resulting string with all matching tags replaced.
     */
    public static function replace_tags($string, $tags, $force_lower = false)
    {
        return preg_replace_callback('/\\{\\{([^{}]+)\}\\}/', function ($matches) use ($force_lower, $tags) {
            $key = $force_lower ? strtolower($matches[1]) : $matches[1];
            return array_key_exists($key, $tags) ? $tags[$key] : '';
        }, $string);
    }
}<?php
namespace com\linways\base\util;

class CalendarUtil
{

    public static function getAllMonth()
    {
        $monthList = [];
        $months = array(
            '01' => array(
                'name' => 'January',
                'code' => 'Jan'
            ),
            '02' => array(
                'name' => 'February',
                'code' => 'Feb'
            ),
            '03' => array(
                'name' => 'March',
                'code' => 'Mar'
            ),
            '04' => array(
                'name' => 'April',
                'code' => 'Apr'
            ),
            '05' => array(
                'name' => 'May',
                'code' => 'May'
            ),
            '06' => array(
                'name' => 'June',
                'code' => 'Jun'
            ),
            '07' => array(
                'name' => 'July',
                'code' => 'Jul'
            ),
            '08' => array(
                'name' => 'August',
                'code' => 'Aug'
            ),
            '09' => array(
                'name' => 'September',
                'code' => 'Sep'
            ),
            '10' => array(
                'name' => 'October',
                'code' => 'Oct'
            ),
            '11' => array(
                'name' => 'November',
                'code' => 'Nov'
            ),
            '12' => array(
                'name' => 'December',
                'code' => 'Dec'
            )
        );
        foreach ($months as $num => $month) {
            $monthList[$num] = (object) $month;
        }
        
        return $monthList;
    }
}<?php
namespace com\linways\base\util;

class CommonUtil
{

    /**
     * method for addOrdinalNumberSuffix
     * 
     * @param string $num            
     * @return string
     */
    public static function addOrdinalNumberSuffix($num)
    {
        if (! in_array(($num % 100), array(
            11,
            12,
            13
        ))) {
            switch ($num % 10) {
                // Handle 1st, 2nd, 3rd
                case 1:
                    return $num . 'st';
                case 2:
                    return $num . 'nd';
                case 3:
                    return $num . 'rd';
            }
        }
        return $num . 'th';
    }
}<?php
namespace com\linways\base\util;

use com\linways\base\exception\CoreException;

class RequestUtil
{

    /**
     * Method for validating sort request
     *
     * @param string $sortBy            
     * @param string $sortOrder            
     * @throws CoreException
     * @return boolean true if valid sort order request
     */
    public static function validateSortOrder($sortBy, $sortOrder)
    {
        if ($sortBy == NULL || $sortBy == "") {
            throw new CoreException(CoreException::SORTBY_NOT_DEFINED, "Sort By is not defined!");
        }
        
        if ($sortOrder == NULL || $sortOrder == "") {
            throw new CoreException(CoreException::SORT_ORDER_NOT_DEFINED, "Sort order is not defined!");
        }
        
        return true;
    }
    /**
     * method for getting $_GET request parameters value
     * @param string $paramName
     * @return string
     */
    public static function get($paramName)
    {
        return $_GET[$paramName];
    }
    
    /**
     * method for getting $_POST request parameters value
     * @param string $paramName
     * @return string
     */
    public static function post($paramName)
    {
        return $_POST[$paramName];
    }
}<?php
namespace com\linways\base\util;

use com\linways\base\mapper\Result;
use com\linways\base\exception\MapperException;
use com\linways\base\mapper\ResultMap;

class MapperUtil
{
    
    /**
     * Method for creating object from result
     *
     * @param MYSQLResult $result
     * @param ResultMap $mapper
     * @return $objectList[]
     */
    public static function fetchObject($result, $mapper)
    {
        $mappedObject = NULL;
        
        if (MySqlUtil::hasRows($result->sqlResult)) {
            
            $inObjectsArray = [];
            while ($fromObject = MySqlUtil::fetchObject($result->sqlResult)) {
                
                $mappedObject = self::mapObject($mapper, $fromObject, $inObjectsArray);
                $inObjectsArray[$fromObject->{$mapper->primeryKeyColumn}] = $mappedObject;
            }
        }
        // MySqlUtil::freeResult($result->sqlResult);
        return $mappedObject;
    }
    
    /**
     * Method for creating object List from result
     *
     * @param MYSQLResult $result
     * @param ResultMap $mapper
     * @return $objectList[]
     */
    public static function fetchObjectList($result, $mapper)
    {
        $mappedObjectList = [];
        $mappedObject = null;
        
        if (MySqlUtil::hasRows($result->sqlResult)) {
            
            while ($fromObject = MySqlUtil::fetchObject($result->sqlResult)) {
                
                $mappedObject = self::mapObject($mapper, $fromObject, $mappedObjectList);
                $mappedObjectList[$fromObject->{$mapper->primeryKeyColumn}] = $mappedObject;
            }
        }
        
        // MySqlUtil::freeResult($result->sqlResult);
        return array_values($mappedObjectList);
    }
    
    /**
     * Validate Mapper class
     *
     * @param ResultMap $mapper
     */
    public static function validateMapper($mapper)
    {
        // If mapper is null throw exception else go ahead with mapping
        if (empty($mapper)) {
            throw new MapperException(MapperException::MAPPER_NOT_DEFINED, "Mapper class not defined!.");
        }
        
        if (empty($mapper->primeryKeyColumn)) {
            throw new MapperException(MapperException::MAPPER_PRIMARY_KEY_COLUMN_NOT_DEFINED, "Primar Key Column property not set for the mapper '" . $mapper->id . "'");
        }
        if (empty($mapper->primeryKey)) {
            throw new MapperException(MapperException::MAPPER_PRIMARY_KEY_NOT_DEFINED, "Primar Key  property not set for the mapper '" . $mapper->id . "'");
        }
    }
    
    /**
     * Find Object in an objectArray
     *
     * @param array $objectsArray
     * @param string $key
     * @param int $id
     * @return unknown|NULL
     */
    public static function findObject($objectsArray, $key, $id)
    {
        foreach ($objectsArray as $value) {
            
            if ($value->{$key} == $id) {
                return $value;
            }
        }
        
        return null;
    }
    
    /**
     * Map object from sql result to dto or model classes
     *
     * @param ResultMap $mapper
     * @param \stdClass $fromObject
     * @param array $children
     * @return NULL|unknown|\com\linways\base\util\unknown|\com\linways\base\util\NULL
     */
    public static function mapObject($mapper, $fromObject, $children)
    {
        $id = null;
        $toObject = null;
        $subObject = null;
        $alreadyMapped = false;
        // Validate Mapper class
        self::validateMapper($mapper);
        // Find the id of the fromobject to check if it is already mapped or not
        $id = $fromObject->{$mapper->primeryKeyColumn};
        
        // If id is null then no mapping found
        if ($id == null) {
            return null;
        }
        // Find already mapped object else create new object from mapper class
        $toObject = self::findObject($children, $mapper->primeryKey, $fromObject->{$mapper->primeryKeyColumn});
        if ($toObject == null) {
            $toObject = new $mapper->class();
        }
        
        // Map all mapped result property from mysql result object to dto/model classes
        foreach ($mapper->results as $result) {
            // If result type is null then no relation exist.so direclty map the value
            // else check for result type to find about the relationship
            if ($result->resultType == null) {
                $toObject->{$result->property} = $fromObject->{$result->column};
            } else  // One to one relation ship
                if ($result->resultType == Result::OBJECT ) {
                    $subObject = self::mapOnetoOne($result->resultMap, $fromObject,$toObject->{$result->property});
                    $toObject->{$result->property} = $subObject;
                } else  // One to many releation ship
                    if ($result->resultType == Result::OBJECT_ARRAY) {
                        $subObject = null;
                        $alreadyMapped = false;
                        if (empty($toObject->{$result->property})) {
                            $toObject->{$result->property} = [];
                            // map the object
                            $subObject = self::mapObject($result->resultMap, $fromObject, $toObject->{$result->property});
                        } else {
                            
                            $subObject = self::findObject($toObject->{$result->property}, $result->resultMap->primeryKey, $fromObject->{$result->resultMap->primeryKeyColumn});
                            
                            // Check if object already mapped or not
                            // If mapped getthe instance and do the mapping else
                            // map the object and add it to the toobject list
                            if (empty($subObject)) {
                                $alreadyMapped = false;
                            } else {
                                $alreadyMapped = true;
                            }
                            // map the object
                            $subObject = self::mapObject($result->resultMap, $fromObject, $toObject->{$result->property});
                        }
                        if (! empty($subObject) && ! $alreadyMapped) {
                            $toObject->{$result->property}[] = $subObject;
                        }
                    }
        }
        
        return $toObject;
    }
    
    /**
     * Method fro mapping one to one relation ship
     *
     * @param ResultMap $mapper
     * @param \stdClass $fromobject
     * @return object
     */
    public static function mapOnetoOne($mapper, $fromObject,$toObject)
    {
        $id = null;
        //         $toObject = null;
        $subObject = null;
        $alreadyMapped = false;
        // Validate Mapper class
        self::validateMapper($mapper);
        // Find the id of the fromobject
        $id = $fromObject->{$mapper->primeryKeyColumn};
        
        // If id is null then no mapping found
        if ($id == null) {
            return null;
        }
        $toObject = empty($toObject) ? new $mapper->class() : $toObject;
        foreach ($mapper->results as $result) {
            
            if ($result->resultType == null) {
                $toObject->{$result->property} = $fromObject->{$result->column};
            } else
                if ($result->resultType == Result::OBJECT) {
                    $toObject->{$result->property} = self::mapOnetoOne($result->resultMap, $fromObject,$toObject->{$result->property});
                } else  // One to many releation ship
                    if ($result->resultType == Result::OBJECT_ARRAY) {
                        $subObject = null;
                        $alreadyMapped = false;
                        if (empty($toObject->{$result->property})) {
                            $toObject->{$result->property} = [];
                            // map the object
                            $subObject = self::mapObject($result->resultMap, $fromObject, $toObject->{$result->property});
                        } else {
                            
                            $subObject = self::findObject($toObject->{$result->property}, $result->resultMap->primeryKey, $fromObject->{$result->resultMap->primeryKeyColumn});
                            // Check if object already mapped or not
                            // If mapped getthe instance and do the mapping else
                            // map the object and add it to the toobject list
                            if (empty($subObject)) {
                                $alreadyMapped = false;
                            } else {
                                $alreadyMapped = true;
                            }
                            // map the object
                            $subObject = self::mapObject($result->resultMap, $fromObject, $toObject->{$result->property});
                        }
                        
                        if (! empty($subObject) && ! $alreadyMapped) {
                            $toObject->{$result->property}[] = $subObject;
                        }
                    }
        }
        return $toObject;
    }
}<?php
namespace com\linways\base\util;


/**
 * Trait MakeSingletonTrait
 *
 * This is used to make a class singleton
 * @package com\linways\base\util
 */
trait MakeSingletonTrait{
    private static $_instance = null;

    private function __construct()
    {
    }

    private function __clone()
    {
    }

    public static function getInstance()
    {
        if (!is_object(self::$_instance))
            self::$_instance = new self();
        return self::$_instance;
    }
}<?php
namespace com\linways\base\util;

use com\linways\base\mapper\Result;
use com\linways\base\mapper\ResultMap;

class ObjectUtil
{

    /**
     * Method for creating object list from result
     *
     * @param MYSQLResult $result            
     * @return $objectList[]
     */
    public static function fetchObjectList($result)
    {
        $objectList = [];
        
        if (MySqlUtil::hasRows($result->sqlResult)) {
            while ($obj = MySqlUtil::fetchObject($result->sqlResult)) {
                $objectList[] = $obj;
            }
        }
        // MySqlUtil::freeResult($result->sqlResult);
        return $objectList;
    }

    /**
     * Method for creating object from result
     *
     * @param MYSQLResult $result            
     * @param ResultMap $mapper            
     * @return $objectList[]
     */
    public static function fetchObject($result, $mapper = null)
    {
        $object = NULL;
        
        if (MySqlUtil::hasRows($result->sqlResult)) {
            
                $object = MySqlUtil::fetchObject($result->sqlResult);
        }
        // MySqlUtil::freeResult($result->sqlResult);
        return $object;
    }

}<?php
namespace com\linways\base\util;

class PhoneUtil
{

    /**
     *
     * @param unknown $phonNo            
     * @return unknown|string
     */
    public static function checkPhoneNo($phonNo)
    {
        if (preg_match("/^91[0-9]{10}$/", $phonNo)) {
            return $phonNo;
        } else {
            if (preg_match("/^[0-9]{10}$/", $phonNo)) {
                $phonNo = "91" . $phonNo;
                return $phonNo;
            } else {
                $phonNo = "";
            }
        }
    }

    /**
     *
     * @param unknown $num            
     * @return string
     */
    public static function addOrdinalNumberSuffix($num)
    {
        if (! in_array(($num % 100), array(
            11,
            12,
            13
        ))) {
            switch ($num % 10) {
                // Handle 1st, 2nd, 3rd
                case 1:
                    return $num . 'st';
                case 2:
                    return $num . 'nd';
                case 3:
                    return $num . 'rd';
            }
        }
        return $num . 'th';
    }
}<?php

namespace com\linways\base\util;


use com\linways\base\exception\CoreException;

class SecurityUtils
{
    /**
     * Remove unsafe html tags like <script> <iframe>, <link> etc. and other vulnerable tags.
     * @param $string  HTML containing unsafe tags and expressions
     * @return string   Sanitized HTML.
     */
    public static function _HTMLSanitize($string){
        $config = \HTMLPurifier_Config::createDefault();
        $config->set('Cache.DefinitionImpl', null);
//        $config->set('Cache.SerializerPath', );
        $purifier = new \HTMLPurifier($config);
        return $purifier->purify($string);
    }

    /**
     * Convert special HTML entities back to characters and sanitize the HTML to remove vulnerable tags and usages
     * @param $htmlString
     * @return string
     */
    public static function prepareSafeHTML($htmlString){
        $htmlString = htmlspecialchars_decode($htmlString);
        return self::_HTMLSanitize($htmlString);
    }

    /**
     * Encrypts and signs a plain text using given password
     * @param string $plaintext
     * @param $password
     * @return string  Base64encoded cipher text
     */
    public static function encryptAndSign($plainText, $password){
        $ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
        $iv = openssl_random_pseudo_bytes($ivlen);
        $cipherTextRaw = openssl_encrypt($plainText, $cipher, $password, $options=OPENSSL_RAW_DATA, $iv);
        $hmac = hash_hmac('sha256', $cipherTextRaw, $password, $as_binary=true);
        return  base64_encode( $iv.$hmac.$cipherTextRaw );
    }


    /**
     * Decrypts and verify the signature of the given cipher text
     * @param string $cipherText base64 encoded cipher text
     * @param string $password
     * @return string original plain text
     * @throws CoreException
     */
    public static function decryptAndVerify($cipherText, $password){
        $c = base64_decode($cipherText);
        $ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
        $iv = substr($c, 0, $ivlen);
        $hmac = substr($c, $ivlen, $sha2len=32);
        $cipherTextRaw = substr($c, $ivlen+$sha2len);
        $originalPlaintext = openssl_decrypt($cipherTextRaw, $cipher, $password, $options=OPENSSL_RAW_DATA, $iv);
        $calcmac = hash_hmac('sha256', $cipherTextRaw, $password, $as_binary=true);
        if (hash_equals($hmac, $calcmac))
        {
            return $originalPlaintext;
        }else{
            throw new CoreException(CoreException::INVALID_SIGNATURE, "Signature verification failed.");
        }
    }
}<?php
namespace com\linways\base\util;

class MySqlUtil
{

    /**
     * check sql result has records/rows
     *
     * @param mysqlresult $sqlResult            
     * @return boolean true if yes else false
     */
    public static function hasRows($sqlResult)
    {
        return self::getNumOfRows($sqlResult) > 0 ? true : false;
    }

    /**
     * Close mysql connection
     *
     * @param connection $connection            
     * @return unknown
     */
    public static function closeConnection($connection)
    {
        return mysqli_close($connection);
    }

    /**
     * get num of rows in mysql result
     *
     * @param mysqlresult $sqlResult            
     * @return num of rows
     */
    public static function getNumOfRows($sqlResult)
    {
        return mysqli_num_rows($sqlResult);
    }

    /**
     * get num of fields in mysql result
     *
     * @param mysqlresult $sqlResult            
     * @return num of fields
     */
    public static function getNumOfFields($sqlResult)
    {
        return mysqli_num_fields($sqlResult);
    }

    /**
     * fetch rows in mysql result
     *
     * @param mysqlresult $sqlResult            
     * @return row
     */
    public static function fetchRow($sqlResult)
    {
        return mysqli_fetch_row($sqlResult);
    }

    /**
     * fetch array in mysql result
     *
     * @param mysqlresult $sqlResult            
     * @return array
     */
    public static function fetchArray($sqlResult)
    {
        return mysqli_fetch_array($sqlResult);
    }

    /**
     * fetch object in mysql result
     *
     * @param mysqlresult $sqlResult            
     * @return object
     */
    public static function fetchObject($sqlResult, $class_name = NULL, $params = NULL)
    {
        return mysqli_fetch_object($sqlResult);
    }

    /**
     * set data seek in mysql result
     *
     * @param mysqlresult $sqlResult            
     * @return dataseek
     */
    public static function dataSeek($sqlResult, $pos)
    {
        return mysqli_data_seek($sqlResult, $pos);
    }

    /**
     * free mysql result
     *
     * @param mysqlresult $sqlResult            
     * @return boolean
     */
    public static function freeResult($sqlResult)
    {
        return mysqli_free_result($sqlResult);
    }

    /**
     * get insert id of current connection
     *
     * @param connection $connection            
     * @return id
     */
    public static function getInsertId($connection)
    {
        return mysqli_insert_id($connection);
    }

    /**
     * mysql real escape string
     *
     * @param string $string            
     */
    public static function realEscapeString($string)
    {
        return mysql_real_escape_string($string);
    }
}<?php
namespace com\linways\base\mapper;

class Result
{

    const OBJECT = "OBJECT";
    const OBJECT_ARRAY = "OBJECT_ARRAY";

    /**
     * Class property name
     * @var string
     */
    public $property;

    /**
     * SQL column name
     * @var string
     */
    public $column;

    /**
     *Class mapper
     * @var ResultMap
     */
    public $resultMap;


    /**
     * Type of class property.
     * eg:OBJECT,OBJECT_ARRAY
     * If not defined or null,sql column mapped to property 
     * else look for resultMap of specified result type.
     * To DO: primary dataType(int,float,string..etc) conversion if mentioned
     * @var string
     */
    public $resultType;


    public function __construct($property = null, $column = null,$resultType = null, $resultMap = null)
    {
        $this->property = $property;
        $this->column = $column;
        $this->resultMap = $resultMap;
        $this->resultType = $resultType;
    }
}<?php
namespace com\linways\base\mapper;

/**
 * 
 * @author arun
 *
 */
class ResultMap
{

    /**
     * Id of resultmap
     * @var string
     */
    public $id;

    /**
     * Class name of mapper class.Specify the namespace also.
     * eg:com\linways\core\ams\professional\dto\Subject
     * @var string
     */
    public $class;

    /**
     * Class properties mapping details array
     * @var array
     */
    public $results;
    /**
     * PrimayKey property of a class
     * @var unknown
     */
    public $primeryKey;
    /**
     * Sql primaykeycolumn name 
     * @var unknown
     */
    public $primeryKeyColumn;

    public function __construct($id = null, $class = null,$primaryKey = null,$primeryKeyColumn=NULL, $results = null)
    {
        $this->id = $id;
        $this->class = $class;
        $this->results = $results;
        $this->primeryKey = $primaryKey;
        $this->primeryKeyColumn = $primeryKeyColumn;
    }
    
 //Mapper Example   
//     class SubjectServiceMapper implements IMapper
//     {
         
//         // /Condition 1 - Presence of a static member variable
//         private static $_instance = null;
    
//         // /Condition 2 - Locked down the constructor
//         private function __construct()
//         {
//         }
//         // Prevent any oustide instantiation of this class
    
//         // /Condition 3 - Prevent any object or instance of that class to be cloned
//         private function __clone()
//         {}
//         // Prevent any copy of this object
    
//         // /Condition 4 - Have a single globally accessible static method
//         public static function getInstance()
//         {
//             if (! is_object(self::$_instance)) // or if( is_null(self::$_instance) ) or if( self::$_instance == null )
//                 self::$_instance = new self();
    
//                 return self::$_instance;
//         }
//         /**
//          * Mapper array
//          * @var array
//          */
//         private $mapper;
//         /**
//          * {@inheritDoc}
//          * @see \com\linways\core\ams\professional\IMapper::getMapper()
//          */
//         public function getMapper() {
    
//             if(empty( $this->mapper))
//             {
//                 $this->mapper =  [];
//                 $this->mapper ['subjectMapper'] = $this->getSubjectMapper();
                 
//             }
//             return  $this->mapper;
    
//         }
    
//         /**
//          * GetSubject Mapper
//          * @return \com\linways\base\mapper\ResultMap
//          */
//         private function getSubjectMapper()
//         {
//             $mapper = null;
//             $mapper = new ResultMap("subjectMapper",'com\linways\core\ams\professional\dto\Subject','id','subjectID');
//             $mapper->results = [];
//             $mapper->results [] = new Result('id','subjectID');
//             $mapper->results [] = new Result('name','subjectName');
//             $mapper->results [] = new Result('syllabusName','syllabusName');
//             $mapper->results [] = new Result('semester','semester',Result::OBJECT_ARRAY,$this->getSemesterMapper());
    
//             return $mapper;
//         }
    
//         /**
//          * GetSubject semeseter mapper
//          * @return \com\linways\base\mapper\ResultMap
//          */
//         private function getSemesterMapper()
//         {
//             $mapper = null;
//             $mapper = new ResultMap("semesterMapper",'com\linways\core\ams\professional\dto\Semester','id','semID');
//             $mapper->results = [];
//             $mapper->results [] = new Result('id','semID');
//                 //      $mapper->results [] = new Result('subjects','subjects',Result::OBJECT_ARRAY,$this->getSubjectMapper());
    
//             return $mapper;
             
//         }
    
}<?php
namespace com\linways\base\mapper;

interface IMapper
{
    /**
     * Get defined mappers of mapper class
     * @return array  
     */
    public function getMapper();
}<?php
namespace com\linways\base\soap;

/**
 * transport class for sending/receiving data via HTTP and HTTPS
 * NOTE: PHP must be compiled with the CURL extension for HTTPS support
 *
 * @author   Dietrich Ayala <dietrich@ganx4.com>
 * @author   Scott Nichol <snichol@users.sourceforge.net>
 * @version  $Id: nusoap.php,v 1.123 2010/04/26 20:15:08 snichol Exp $
 * @access public
 */
class soap_transport_http extends nusoap_base {
    
    var $url = '';
    var $uri = '';
    var $digest_uri = '';
    var $scheme = '';
    var $host = '';
    var $port = '';
    var $path = '';
    var $request_method = 'POST';
    var $protocol_version = '1.0';
    var $encoding = '';
    var $outgoing_headers = array();
    var $incoming_headers = array();
    var $incoming_cookies = array();
    var $outgoing_payload = '';
    var $incoming_payload = '';
    var $response_status_line;	// HTTP response status line
    var $useSOAPAction = true;
    var $persistentConnection = false;
    var $ch = false;	// cURL handle
    var $ch_options = array();	// cURL custom options
    var $use_curl = false;		// force cURL use
    var $proxy = null;			// proxy information (associative array)
    var $username = '';
    var $password = '';
    var $authtype = '';
    var $digestRequest = array();
    var $certRequest = array();	// keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, certpassword (optional), verifypeer (optional), verifyhost (optional)
    // cainfofile: certificate authority file, e.g. '$pathToPemFiles/rootca.pem'
    // sslcertfile: SSL certificate file, e.g. '$pathToPemFiles/mycert.pem'
    // sslkeyfile: SSL key file, e.g. '$pathToPemFiles/mykey.pem'
    // passphrase: SSL key password/passphrase
    // certpassword: SSL certificate password
    // verifypeer: default is 1
    // verifyhost: default is 1
    
    /**
     * constructor
     *
     * @param string $url The URL to which to connect
     * @param array $curl_options User-specified cURL options
     * @param boolean $use_curl Whether to try to force cURL use
     * @access public
     */
    function soap_transport_http($url, $curl_options = NULL, $use_curl = false){
        parent::nusoap_base();
        $this->debug("ctor url=$url use_curl=$use_curl curl_options:");
        $this->appendDebug($this->varDump($curl_options));
        $this->setURL($url);
        if (is_array($curl_options)) {
            $this->ch_options = $curl_options;
        }
        $this->use_curl = $use_curl;
        preg_match('/\$Revisio' . 'n: ([^ ]+)/', $this->revision, $rev);
        $this->setHeader('User-Agent', $this->title.'/'.$this->version.' ('.$rev[1].')');
    }
    
    /**
     * sets a cURL option
     *
     * @param	mixed $option The cURL option (always integer?)
     * @param	mixed $value The cURL option value
     * @access   private
     */
    function setCurlOption($option, $value) {
        $this->debug("setCurlOption option=$option, value=");
        $this->appendDebug($this->varDump($value));
        curl_setopt($this->ch, $option, $value);
    }
    
    /**
     * sets an HTTP header
     *
     * @param string $name The name of the header
     * @param string $value The value of the header
     * @access private
     */
    function setHeader($name, $value) {
        $this->outgoing_headers[$name] = $value;
        $this->debug("set header $name: $value");
    }
    
    /**
     * unsets an HTTP header
     *
     * @param string $name The name of the header
     * @access private
     */
    function unsetHeader($name) {
        if (isset($this->outgoing_headers[$name])) {
            $this->debug("unset header $name");
            unset($this->outgoing_headers[$name]);
        }
    }
    
    /**
     * sets the URL to which to connect
     *
     * @param string $url The URL to which to connect
     * @access private
     */
    function setURL($url) {
        $this->url = $url;
        
        $u = parse_url($url);
        foreach($u as $k => $v){
            $this->debug("parsed URL $k = $v");
            $this->$k = $v;
        }
        
        // add any GET params to path
        if(isset($u['query']) && $u['query'] != ''){
            $this->path .= '?' . $u['query'];
        }
        
        // set default port
        if(!isset($u['port'])){
            if($u['scheme'] == 'https'){
                $this->port = 443;
            } else {
                $this->port = 80;
            }
        }
        
        $this->uri = $this->path;
        $this->digest_uri = $this->uri;
        
        // build headers
        if (!isset($u['port'])) {
            $this->setHeader('Host', $this->host);
        } else {
            $this->setHeader('Host', $this->host.':'.$this->port);
        }
        
        if (isset($u['user']) && $u['user'] != '') {
            $this->setCredentials(urldecode($u['user']), isset($u['pass']) ? urldecode($u['pass']) : '');
        }
    }
    
    /**
     * gets the I/O method to use
     *
     * @return	string	I/O method to use (socket|curl|unknown)
     * @access	private
     */
    function io_method() {
        if ($this->use_curl || ($this->scheme == 'https') || ($this->scheme == 'http' && $this->authtype == 'ntlm') || ($this->scheme == 'http' && is_array($this->proxy) && $this->proxy['authtype'] == 'ntlm'))
            return 'curl';
            if (($this->scheme == 'http' || $this->scheme == 'ssl') && $this->authtype != 'ntlm' && (!is_array($this->proxy) || $this->proxy['authtype'] != 'ntlm'))
                return 'socket';
                return 'unknown';
    }
    
    /**
     * establish an HTTP connection
     *
     * @param    integer $timeout set connection timeout in seconds
     * @param	integer $response_timeout set response timeout in seconds
     * @return	boolean true if connected, false if not
     * @access   private
     */
    function connect($connection_timeout=0,$response_timeout=30){
        // For PHP 4.3 with OpenSSL, change https scheme to ssl, then treat like
        // "regular" socket.
        // TODO: disabled for now because OpenSSL must be *compiled* in (not just
        //       loaded), and until PHP5 stream_get_wrappers is not available.
        //	  	if ($this->scheme == 'https') {
            //		  	if (version_compare(phpversion(), '4.3.0') >= 0) {
            //		  		if (extension_loaded('openssl')) {
            //		  			$this->scheme = 'ssl';
            //		  			$this->debug('Using SSL over OpenSSL');
            //		  		}
            //		  	}
            //		}
            $this->debug("connect connection_timeout $connection_timeout, response_timeout $response_timeout, scheme $this->scheme, host $this->host, port $this->port");
            if ($this->io_method() == 'socket') {
                if (!is_array($this->proxy)) {
                    $host = $this->host;
                    $port = $this->port;
                } else {
                    $host = $this->proxy['host'];
                    $port = $this->proxy['port'];
                }
                
                // use persistent connection
                if($this->persistentConnection && isset($this->fp) && is_resource($this->fp)){
                    if (!feof($this->fp)) {
                        $this->debug('Re-use persistent connection');
                        return true;
                    }
                    fclose($this->fp);
                    $this->debug('Closed persistent connection at EOF');
                }
                
                // munge host if using OpenSSL
                if ($this->scheme == 'ssl') {
                    $host = 'ssl://' . $host;
                }
                $this->debug('calling fsockopen with host ' . $host . ' connection_timeout ' . $connection_timeout);
                
                // open socket
                if($connection_timeout > 0){
                    $this->fp = @fsockopen( $host, $this->port, $this->errno, $this->error_str, $connection_timeout);
                } else {
                    $this->fp = @fsockopen( $host, $this->port, $this->errno, $this->error_str);
                }
                
                // test pointer
                if(!$this->fp) {
                    $msg = 'Couldn\'t open socket connection to server ' . $this->url;
                    if ($this->errno) {
                        $msg .= ', Error ('.$this->errno.'): '.$this->error_str;
                    } else {
                        $msg .= ' prior to connect().  This is often a problem looking up the host name.';
                    }
                    $this->debug($msg);
                    $this->setError($msg);
                    return false;
                }
                
                // set response timeout
                $this->debug('set response timeout to ' . $response_timeout);
                socket_set_timeout( $this->fp, $response_timeout);
                
                $this->debug('socket connected');
                return true;
            } else if ($this->io_method() == 'curl') {
                if (!extension_loaded('curl')) {
                    //			$this->setError('cURL Extension, or OpenSSL extension w/ PHP version >= 4.3 is required for HTTPS');
                    $this->setError('The PHP cURL Extension is required for HTTPS or NLTM.  You will need to re-build or update your PHP to include cURL or change php.ini to load the PHP cURL extension.');
                    return false;
                }
                // Avoid warnings when PHP does not have these options
                if (defined('CURLOPT_CONNECTIONTIMEOUT'))
                    $CURLOPT_CONNECTIONTIMEOUT = CURLOPT_CONNECTIONTIMEOUT;
                    else
                        $CURLOPT_CONNECTIONTIMEOUT = 78;
                        if (defined('CURLOPT_HTTPAUTH'))
                            $CURLOPT_HTTPAUTH = CURLOPT_HTTPAUTH;
                            else
                                $CURLOPT_HTTPAUTH = 107;
                                if (defined('CURLOPT_PROXYAUTH'))
                                    $CURLOPT_PROXYAUTH = CURLOPT_PROXYAUTH;
                                    else
                                        $CURLOPT_PROXYAUTH = 111;
                                        if (defined('CURLAUTH_BASIC'))
                                            $CURLAUTH_BASIC = CURLAUTH_BASIC;
                                            else
                                                $CURLAUTH_BASIC = 1;
                                                if (defined('CURLAUTH_DIGEST'))
                                                    $CURLAUTH_DIGEST = CURLAUTH_DIGEST;
                                                    else
                                                        $CURLAUTH_DIGEST = 2;
                                                        if (defined('CURLAUTH_NTLM'))
                                                            $CURLAUTH_NTLM = CURLAUTH_NTLM;
                                                            else
                                                                $CURLAUTH_NTLM = 8;
                                                                
                                                                $this->debug('connect using cURL');
                                                                // init CURL
                                                                $this->ch = curl_init();
                                                                // set url
                                                                $hostURL = ($this->port != '') ? "$this->scheme://$this->host:$this->port" : "$this->scheme://$this->host";
                                                                // add path
                                                                $hostURL .= $this->path;
                                                                $this->setCurlOption(CURLOPT_URL, $hostURL);
                                                                // follow location headers (re-directs)
                                                                if (ini_get('safe_mode') || ini_get('open_basedir')) {
                                                                    $this->debug('safe_mode or open_basedir set, so do not set CURLOPT_FOLLOWLOCATION');
                                                                    $this->debug('safe_mode = ');
                                                                    $this->appendDebug($this->varDump(ini_get('safe_mode')));
                                                                    $this->debug('open_basedir = ');
                                                                    $this->appendDebug($this->varDump(ini_get('open_basedir')));
                                                                } else {
                                                                    $this->setCurlOption(CURLOPT_FOLLOWLOCATION, 1);
                                                                }
                                                                // ask for headers in the response output
                                                                $this->setCurlOption(CURLOPT_HEADER, 1);
                                                                // ask for the response output as the return value
                                                                $this->setCurlOption(CURLOPT_RETURNTRANSFER, 1);
                                                                // encode
                                                                // We manage this ourselves through headers and encoding
                                                                //		if(function_exists('gzuncompress')){
                                                                //			$this->setCurlOption(CURLOPT_ENCODING, 'deflate');
                                                                //		}
                                                                // persistent connection
                                                                if ($this->persistentConnection) {
                                                                    // I believe the following comment is now bogus, having applied to
                                                                    // the code when it used CURLOPT_CUSTOMREQUEST to send the request.
                                                                    // The way we send data, we cannot use persistent connections, since
                                                                    // there will be some "junk" at the end of our request.
                                                                    //$this->setCurlOption(CURL_HTTP_VERSION_1_1, true);
                                                                    $this->persistentConnection = false;
                                                                    $this->setHeader('Connection', 'close');
                                                                }
                                                                // set timeouts
                                                                if ($connection_timeout != 0) {
                                                                    $this->setCurlOption($CURLOPT_CONNECTIONTIMEOUT, $connection_timeout);
                                                                }
                                                                if ($response_timeout != 0) {
                                                                    $this->setCurlOption(CURLOPT_TIMEOUT, $response_timeout);
                                                                }
                                                                
                                                                if ($this->scheme == 'https') {
                                                                    $this->debug('set cURL SSL verify options');
                                                                    // recent versions of cURL turn on peer/host checking by default,
                                                                    // while PHP binaries are not compiled with a default location for the
                                                                    // CA cert bundle, so disable peer/host checking.
                                                                    //$this->setCurlOption(CURLOPT_CAINFO, 'f:\php-4.3.2-win32\extensions\curl-ca-bundle.crt');
                                                                    $this->setCurlOption(CURLOPT_SSL_VERIFYPEER, 0);
                                                                    $this->setCurlOption(CURLOPT_SSL_VERIFYHOST, 0);
                                                                    
                                                                    // support client certificates (thanks Tobias Boes, Doug Anarino, Eryan Ariobowo)
                                                                    if ($this->authtype == 'certificate') {
                                                                        $this->debug('set cURL certificate options');
                                                                        if (isset($this->certRequest['cainfofile'])) {
                                                                            $this->setCurlOption(CURLOPT_CAINFO, $this->certRequest['cainfofile']);
                                                                        }
                                                                        if (isset($this->certRequest['verifypeer'])) {
                                                                            $this->setCurlOption(CURLOPT_SSL_VERIFYPEER, $this->certRequest['verifypeer']);
                                                                        } else {
                                                                            $this->setCurlOption(CURLOPT_SSL_VERIFYPEER, 1);
                                                                        }
                                                                        if (isset($this->certRequest['verifyhost'])) {
                                                                            $this->setCurlOption(CURLOPT_SSL_VERIFYHOST, $this->certRequest['verifyhost']);
                                                                        } else {
                                                                            $this->setCurlOption(CURLOPT_SSL_VERIFYHOST, 1);
                                                                        }
                                                                        if (isset($this->certRequest['sslcertfile'])) {
                                                                            $this->setCurlOption(CURLOPT_SSLCERT, $this->certRequest['sslcertfile']);
                                                                        }
                                                                        if (isset($this->certRequest['sslkeyfile'])) {
                                                                            $this->setCurlOption(CURLOPT_SSLKEY, $this->certRequest['sslkeyfile']);
                                                                        }
                                                                        if (isset($this->certRequest['passphrase'])) {
                                                                            $this->setCurlOption(CURLOPT_SSLKEYPASSWD, $this->certRequest['passphrase']);
                                                                        }
                                                                        if (isset($this->certRequest['certpassword'])) {
                                                                            $this->setCurlOption(CURLOPT_SSLCERTPASSWD, $this->certRequest['certpassword']);
                                                                        }
                                                                    }
                                                                }
                                                                if ($this->authtype && ($this->authtype != 'certificate')) {
                                                                    if ($this->username) {
                                                                        $this->debug('set cURL username/password');
                                                                        $this->setCurlOption(CURLOPT_USERPWD, "$this->username:$this->password");
                                                                    }
                                                                    if ($this->authtype == 'basic') {
                                                                        $this->debug('set cURL for Basic authentication');
                                                                        $this->setCurlOption($CURLOPT_HTTPAUTH, $CURLAUTH_BASIC);
                                                                    }
                                                                    if ($this->authtype == 'digest') {
                                                                        $this->debug('set cURL for digest authentication');
                                                                        $this->setCurlOption($CURLOPT_HTTPAUTH, $CURLAUTH_DIGEST);
                                                                    }
                                                                    if ($this->authtype == 'ntlm') {
                                                                        $this->debug('set cURL for NTLM authentication');
                                                                        $this->setCurlOption($CURLOPT_HTTPAUTH, $CURLAUTH_NTLM);
                                                                    }
                                                                }
                                                                if (is_array($this->proxy)) {
                                                                    $this->debug('set cURL proxy options');
                                                                    if ($this->proxy['port'] != '') {
                                                                        $this->setCurlOption(CURLOPT_PROXY, $this->proxy['host'].':'.$this->proxy['port']);
                                                                    } else {
                                                                        $this->setCurlOption(CURLOPT_PROXY, $this->proxy['host']);
                                                                    }
                                                                    if ($this->proxy['username'] || $this->proxy['password']) {
                                                                        $this->debug('set cURL proxy authentication options');
                                                                        $this->setCurlOption(CURLOPT_PROXYUSERPWD, $this->proxy['username'].':'.$this->proxy['password']);
                                                                        if ($this->proxy['authtype'] == 'basic') {
                                                                            $this->setCurlOption($CURLOPT_PROXYAUTH, $CURLAUTH_BASIC);
                                                                        }
                                                                        if ($this->proxy['authtype'] == 'ntlm') {
                                                                            $this->setCurlOption($CURLOPT_PROXYAUTH, $CURLAUTH_NTLM);
                                                                        }
                                                                    }
                                                                }
                                                                $this->debug('cURL connection set up');
                                                                return true;
            } else {
                $this->setError('Unknown scheme ' . $this->scheme);
                $this->debug('Unknown scheme ' . $this->scheme);
                return false;
            }
}

/**
 * sends the SOAP request and gets the SOAP response via HTTP[S]
 *
 * @param    string $data message data
 * @param    integer $timeout set connection timeout in seconds
 * @param	integer $response_timeout set response timeout in seconds
 * @param	array $cookies cookies to send
 * @return	string data
 * @access   public
 */
function send($data, $timeout=0, $response_timeout=30, $cookies=NULL) {
    
    $this->debug('entered send() with data of length: '.strlen($data));
    
    $this->tryagain = true;
    $tries = 0;
    while ($this->tryagain) {
        $this->tryagain = false;
        if ($tries++ < 2) {
            // make connnection
            if (!$this->connect($timeout, $response_timeout)){
                return false;
            }
            
            // send request
            if (!$this->sendRequest($data, $cookies)){
                return false;
            }
            
            // get response
            $respdata = $this->getResponse();
        } else {
            $this->setError("Too many tries to get an OK response ($this->response_status_line)");
        }
    }
    $this->debug('end of send()');
    return $respdata;
}


/**
 * sends the SOAP request and gets the SOAP response via HTTPS using CURL
 *
 * @param    string $data message data
 * @param    integer $timeout set connection timeout in seconds
 * @param	integer $response_timeout set response timeout in seconds
 * @param	array $cookies cookies to send
 * @return	string data
 * @access   public
 * @deprecated
 */
function sendHTTPS($data, $timeout=0, $response_timeout=30, $cookies) {
    return $this->send($data, $timeout, $response_timeout, $cookies);
}

/**
 * if authenticating, set user credentials here
 *
 * @param    string $username
 * @param    string $password
 * @param	string $authtype (basic|digest|certificate|ntlm)
 * @param	array $digestRequest (keys must be nonce, nc, realm, qop)
 * @param	array $certRequest (keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, certpassword (optional), verifypeer (optional), verifyhost (optional): see corresponding options in cURL docs)
 * @access   public
 */
function setCredentials($username, $password, $authtype = 'basic', $digestRequest = array(), $certRequest = array()) {
    $this->debug("setCredentials username=$username authtype=$authtype digestRequest=");
    $this->appendDebug($this->varDump($digestRequest));
    $this->debug("certRequest=");
    $this->appendDebug($this->varDump($certRequest));
    // cf. RFC 2617
    if ($authtype == 'basic') {
        $this->setHeader('Authorization', 'Basic '.base64_encode(str_replace(':','',$username).':'.$password));
    } elseif ($authtype == 'digest') {
        if (isset($digestRequest['nonce'])) {
            $digestRequest['nc'] = isset($digestRequest['nc']) ? $digestRequest['nc']++ : 1;
            
            // calculate the Digest hashes (calculate code based on digest implementation found at: http://www.rassoc.com/gregr/weblog/stories/2002/07/09/webServicesSecurityHttpDigestAuthenticationWithoutActiveDirectory.html)
            
            // A1 = unq(username-value) ":" unq(realm-value) ":" passwd
            $A1 = $username. ':' . (isset($digestRequest['realm']) ? $digestRequest['realm'] : '') . ':' . $password;
            
            // H(A1) = MD5(A1)
            $HA1 = md5($A1);
            
            // A2 = Method ":" digest-uri-value
            $A2 = $this->request_method . ':' . $this->digest_uri;
            
            // H(A2)
            $HA2 =  md5($A2);
            
            // KD(secret, data) = H(concat(secret, ":", data))
            // if qop == auth:
            // request-digest  = <"> < KD ( H(A1),     unq(nonce-value)
            //                              ":" nc-value
            //                              ":" unq(cnonce-value)
            //                              ":" unq(qop-value)
            //                              ":" H(A2)
            //                            ) <">
            // if qop is missing,
            // request-digest  = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2) ) > <">
            
            $unhashedDigest = '';
            $nonce = isset($digestRequest['nonce']) ? $digestRequest['nonce'] : '';
            $cnonce = $nonce;
            if ($digestRequest['qop'] != '') {
                $unhashedDigest = $HA1 . ':' . $nonce . ':' . sprintf("%08d", $digestRequest['nc']) . ':' . $cnonce . ':' . $digestRequest['qop'] . ':' . $HA2;
            } else {
                $unhashedDigest = $HA1 . ':' . $nonce . ':' . $HA2;
            }
            
            $hashedDigest = md5($unhashedDigest);
            
            $opaque = '';
            if (isset($digestRequest['opaque'])) {
                $opaque = ', opaque="' . $digestRequest['opaque'] . '"';
            }
            
            $this->setHeader('Authorization', 'Digest username="' . $username . '", realm="' . $digestRequest['realm'] . '", nonce="' . $nonce . '", uri="' . $this->digest_uri . $opaque . '", cnonce="' . $cnonce . '", nc=' . sprintf("%08x", $digestRequest['nc']) . ', qop="' . $digestRequest['qop'] . '", response="' . $hashedDigest . '"');
        }
    } elseif ($authtype == 'certificate') {
        $this->certRequest = $certRequest;
        $this->debug('Authorization header not set for certificate');
    } elseif ($authtype == 'ntlm') {
        // do nothing
        $this->debug('Authorization header not set for ntlm');
    }
    $this->username = $username;
    $this->password = $password;
    $this->authtype = $authtype;
    $this->digestRequest = $digestRequest;
}

/**
 * set the soapaction value
 *
 * @param    string $soapaction
 * @access   public
 */
function setSOAPAction($soapaction) {
    $this->setHeader('SOAPAction', '"' . $soapaction . '"');
}

/**
 * use http encoding
 *
 * @param    string $enc encoding style. supported values: gzip, deflate, or both
 * @access   public
 */
function setEncoding($enc='gzip, deflate') {
    if (function_exists('gzdeflate')) {
        $this->protocol_version = '1.1';
        $this->setHeader('Accept-Encoding', $enc);
        if (!isset($this->outgoing_headers['Connection'])) {
            $this->setHeader('Connection', 'close');
            $this->persistentConnection = false;
        }
        // deprecated as of PHP 5.3.0
        //set_magic_quotes_runtime(0);
        $this->encoding = $enc;
    }
}

/**
 * set proxy info here
 *
 * @param    string $proxyhost use an empty string to remove proxy
 * @param    string $proxyport
 * @param	string $proxyusername
 * @param	string $proxypassword
 * @param	string $proxyauthtype (basic|ntlm)
 * @access   public
 */
function setProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '', $proxyauthtype = 'basic') {
    if ($proxyhost) {
        $this->proxy = array(
            'host' => $proxyhost,
            'port' => $proxyport,
            'username' => $proxyusername,
            'password' => $proxypassword,
            'authtype' => $proxyauthtype
        );
        if ($proxyusername != '' && $proxypassword != '' && $proxyauthtype = 'basic') {
            $this->setHeader('Proxy-Authorization', ' Basic '.base64_encode($proxyusername.':'.$proxypassword));
        }
    } else {
        $this->debug('remove proxy');
        $proxy = null;
        unsetHeader('Proxy-Authorization');
    }
}


/**
 * Test if the given string starts with a header that is to be skipped.
 * Skippable headers result from chunked transfer and proxy requests.
 *
 * @param	string $data The string to check.
 * @returns	boolean	Whether a skippable header was found.
 * @access	private
 */
function isSkippableCurlHeader(&$data) {
    $skipHeaders = array(	'HTTP/1.1 100',
        'HTTP/1.0 301',
        'HTTP/1.1 301',
        'HTTP/1.0 302',
        'HTTP/1.1 302',
        'HTTP/1.0 401',
        'HTTP/1.1 401',
        'HTTP/1.0 200 Connection established');
    foreach ($skipHeaders as $hd) {
        $prefix = substr($data, 0, strlen($hd));
        if ($prefix == $hd) return true;
    }
    
    return false;
}

/**
 * decode a string that is encoded w/ "chunked' transfer encoding
 * as defined in RFC2068 19.4.6
 *
 * @param    string $buffer
 * @param    string $lb
 * @returns	string
 * @access   public
 * @deprecated
 */
function decodeChunked($buffer, $lb){
    // length := 0
    $length = 0;
    $new = '';
    
    // read chunk-size, chunk-extension (if any) and CRLF
    // get the position of the linebreak
    $chunkend = strpos($buffer, $lb);
    if ($chunkend == FALSE) {
        $this->debug('no linebreak found in decodeChunked');
        return $new;
    }
    $temp = substr($buffer,0,$chunkend);
    $chunk_size = hexdec( trim($temp) );
    $chunkstart = $chunkend + strlen($lb);
    // while (chunk-size > 0) {
    while ($chunk_size > 0) {
        $this->debug("chunkstart: $chunkstart chunk_size: $chunk_size");
        $chunkend = strpos( $buffer, $lb, $chunkstart + $chunk_size);
        
        // Just in case we got a broken connection
        if ($chunkend == FALSE) {
            $chunk = substr($buffer,$chunkstart);
            // append chunk-data to entity-body
            $new .= $chunk;
            $length += strlen($chunk);
            break;
        }
        
        // read chunk-data and CRLF
        $chunk = substr($buffer,$chunkstart,$chunkend-$chunkstart);
        // append chunk-data to entity-body
        $new .= $chunk;
        // length := length + chunk-size
        $length += strlen($chunk);
        // read chunk-size and CRLF
        $chunkstart = $chunkend + strlen($lb);
        
        $chunkend = strpos($buffer, $lb, $chunkstart) + strlen($lb);
        if ($chunkend == FALSE) {
            break; //Just in case we got a broken connection
        }
        $temp = substr($buffer,$chunkstart,$chunkend-$chunkstart);
        $chunk_size = hexdec( trim($temp) );
        $chunkstart = $chunkend;
    }
    return $new;
}

/**
 * Writes the payload, including HTTP headers, to $this->outgoing_payload.
 *
 * @param	string $data HTTP body
 * @param	string $cookie_str data for HTTP Cookie header
 * @return	void
 * @access	private
 */
function buildPayload($data, $cookie_str = '') {
    // Note: for cURL connections, $this->outgoing_payload is ignored,
    // as is the Content-Length header, but these are still created as
    // debugging guides.
    
    // add content-length header
    if ($this->request_method != 'GET') {
        $this->setHeader('Content-Length', strlen($data));
    }
    
    // start building outgoing payload:
    if ($this->proxy) {
        $uri = $this->url;
    } else {
        $uri = $this->uri;
    }
    $req = "$this->request_method $uri HTTP/$this->protocol_version";
    $this->debug("HTTP request: $req");
    $this->outgoing_payload = "$req\r\n";
    
    // loop thru headers, serializing
    foreach($this->outgoing_headers as $k => $v){
        $hdr = $k.': '.$v;
        $this->debug("HTTP header: $hdr");
        $this->outgoing_payload .= "$hdr\r\n";
    }
    
    // add any cookies
    if ($cookie_str != '') {
        $hdr = 'Cookie: '.$cookie_str;
        $this->debug("HTTP header: $hdr");
        $this->outgoing_payload .= "$hdr\r\n";
    }
    
    // header/body separator
    $this->outgoing_payload .= "\r\n";
    
    // add data
    $this->outgoing_payload .= $data;
}

/**
 * sends the SOAP request via HTTP[S]
 *
 * @param    string $data message data
 * @param	array $cookies cookies to send
 * @return	boolean	true if OK, false if problem
 * @access   private
 */
function sendRequest($data, $cookies = NULL) {
    // build cookie string
    $cookie_str = $this->getCookiesForRequest($cookies, (($this->scheme == 'ssl') || ($this->scheme == 'https')));
    
    // build payload
    $this->buildPayload($data, $cookie_str);
    
    if ($this->io_method() == 'socket') {
        // send payload
        if(!fputs($this->fp, $this->outgoing_payload, strlen($this->outgoing_payload))) {
            $this->setError('couldn\'t write message data to socket');
            $this->debug('couldn\'t write message data to socket');
            return false;
        }
        $this->debug('wrote data to socket, length = ' . strlen($this->outgoing_payload));
        return true;
    } else if ($this->io_method() == 'curl') {
        // set payload
        // cURL does say this should only be the verb, and in fact it
        // turns out that the URI and HTTP version are appended to this, which
        // some servers refuse to work with (so we no longer use this method!)
        //$this->setCurlOption(CURLOPT_CUSTOMREQUEST, $this->outgoing_payload);
        $curl_headers = array();
        foreach($this->outgoing_headers as $k => $v){
            if ($k == 'Connection' || $k == 'Content-Length' || $k == 'Host' || $k == 'Authorization' || $k == 'Proxy-Authorization') {
                $this->debug("Skip cURL header $k: $v");
            } else {
                $curl_headers[] = "$k: $v";
            }
        }
        if ($cookie_str != '') {
            $curl_headers[] = 'Cookie: ' . $cookie_str;
        }
        $this->setCurlOption(CURLOPT_HTTPHEADER, $curl_headers);
        $this->debug('set cURL HTTP headers');
        if ($this->request_method == "POST") {
            $this->setCurlOption(CURLOPT_POST, 1);
            $this->setCurlOption(CURLOPT_POSTFIELDS, $data);
            $this->debug('set cURL POST data');
        } else {
        }
        // insert custom user-set cURL options
        foreach ($this->ch_options as $key => $val) {
            $this->setCurlOption($key, $val);
        }
        
        $this->debug('set cURL payload');
        return true;
    }
}

/**
 * gets the SOAP response via HTTP[S]
 *
 * @return	string the response (also sets member variables like incoming_payload)
 * @access   private
 */
function getResponse(){
    $this->incoming_payload = '';
    
    if ($this->io_method() == 'socket') {
        // loop until headers have been retrieved
        $data = '';
        while (!isset($lb)){
            
            // We might EOF during header read.
            if(feof($this->fp)) {
                $this->incoming_payload = $data;
                $this->debug('found no headers before EOF after length ' . strlen($data));
                $this->debug("received before EOF:\n" . $data);
                $this->setError('server failed to send headers');
                return false;
            }
            
            $tmp = fgets($this->fp, 256);
            $tmplen = strlen($tmp);
            $this->debug("read line of $tmplen bytes: " . trim($tmp));
            
            if ($tmplen == 0) {
                $this->incoming_payload = $data;
                $this->debug('socket read of headers timed out after length ' . strlen($data));
                $this->debug("read before timeout: " . $data);
                $this->setError('socket read of headers timed out');
                return false;
            }
            
            $data .= $tmp;
            $pos = strpos($data,"\r\n\r\n");
            if($pos > 1){
                $lb = "\r\n";
            } else {
                $pos = strpos($data,"\n\n");
                if($pos > 1){
                    $lb = "\n";
                }
            }
            // remove 100 headers
            if (isset($lb) && preg_match('/^HTTP\/1.1 100/',$data)) {
                unset($lb);
                $data = '';
            }//
        }
        // store header data
        $this->incoming_payload .= $data;
        $this->debug('found end of headers after length ' . strlen($data));
        // process headers
        $header_data = trim(substr($data,0,$pos));
        $header_array = explode($lb,$header_data);
        $this->incoming_headers = array();
        $this->incoming_cookies = array();
        foreach($header_array as $header_line){
            $arr = explode(':',$header_line, 2);
            if(count($arr) > 1){
                $header_name = strtolower(trim($arr[0]));
                $this->incoming_headers[$header_name] = trim($arr[1]);
                if ($header_name == 'set-cookie') {
                    // TODO: allow multiple cookies from parseCookie
                    $cookie = $this->parseCookie(trim($arr[1]));
                    if ($cookie) {
                        $this->incoming_cookies[] = $cookie;
                        $this->debug('found cookie: ' . $cookie['name'] . ' = ' . $cookie['value']);
                    } else {
                        $this->debug('did not find cookie in ' . trim($arr[1]));
                    }
                }
            } else if (isset($header_name)) {
                // append continuation line to previous header
                $this->incoming_headers[$header_name] .= $lb . ' ' . $header_line;
            }
        }
        
        // loop until msg has been received
        if (isset($this->incoming_headers['transfer-encoding']) && strtolower($this->incoming_headers['transfer-encoding']) == 'chunked') {
            $content_length =  2147483647;	// ignore any content-length header
            $chunked = true;
            $this->debug("want to read chunked content");
        } elseif (isset($this->incoming_headers['content-length'])) {
            $content_length = $this->incoming_headers['content-length'];
            $chunked = false;
            $this->debug("want to read content of length $content_length");
        } else {
            $content_length =  2147483647;
            $chunked = false;
            $this->debug("want to read content to EOF");
        }
        $data = '';
        do {
            if ($chunked) {
                $tmp = fgets($this->fp, 256);
                $tmplen = strlen($tmp);
                $this->debug("read chunk line of $tmplen bytes");
                if ($tmplen == 0) {
                    $this->incoming_payload = $data;
                    $this->debug('socket read of chunk length timed out after length ' . strlen($data));
                    $this->debug("read before timeout:\n" . $data);
                    $this->setError('socket read of chunk length timed out');
                    return false;
                }
                $content_length = hexdec(trim($tmp));
                $this->debug("chunk length $content_length");
            }
            $strlen = 0;
            while (($strlen < $content_length) && (!feof($this->fp))) {
                $readlen = min(8192, $content_length - $strlen);
                $tmp = fread($this->fp, $readlen);
                $tmplen = strlen($tmp);
                $this->debug("read buffer of $tmplen bytes");
                if (($tmplen == 0) && (!feof($this->fp))) {
                    $this->incoming_payload = $data;
                    $this->debug('socket read of body timed out after length ' . strlen($data));
                    $this->debug("read before timeout:\n" . $data);
                    $this->setError('socket read of body timed out');
                    return false;
                }
                $strlen += $tmplen;
                $data .= $tmp;
            }
            if ($chunked && ($content_length > 0)) {
                $tmp = fgets($this->fp, 256);
                $tmplen = strlen($tmp);
                $this->debug("read chunk terminator of $tmplen bytes");
                if ($tmplen == 0) {
                    $this->incoming_payload = $data;
                    $this->debug('socket read of chunk terminator timed out after length ' . strlen($data));
                    $this->debug("read before timeout:\n" . $data);
                    $this->setError('socket read of chunk terminator timed out');
                    return false;
                }
            }
        } while ($chunked && ($content_length > 0) && (!feof($this->fp)));
        if (feof($this->fp)) {
            $this->debug('read to EOF');
        }
        $this->debug('read body of length ' . strlen($data));
        $this->incoming_payload .= $data;
        $this->debug('received a total of '.strlen($this->incoming_payload).' bytes of data from server');
        
        // close filepointer
        if(
            (isset($this->incoming_headers['connection']) && strtolower($this->incoming_headers['connection']) == 'close') ||
            (! $this->persistentConnection) || feof($this->fp)){
                fclose($this->fp);
                $this->fp = false;
                $this->debug('closed socket');
        }
        
        // connection was closed unexpectedly
        if($this->incoming_payload == ''){
            $this->setError('no response from server');
            return false;
        }
        
        // decode transfer-encoding
        //		if(isset($this->incoming_headers['transfer-encoding']) && strtolower($this->incoming_headers['transfer-encoding']) == 'chunked'){
        //			if(!$data = $this->decodeChunked($data, $lb)){
        //				$this->setError('Decoding of chunked data failed');
        //				return false;
        //			}
        //print "<pre>\nde-chunked:\n---------------\n$data\n\n---------------\n</pre>";
        // set decoded payload
        //			$this->incoming_payload = $header_data.$lb.$lb.$data;
        //		}
        
} else if ($this->io_method() == 'curl') {
    // send and receive
    $this->debug('send and receive with cURL');
    $this->incoming_payload = curl_exec($this->ch);
    $data = $this->incoming_payload;
    
    $cErr = curl_error($this->ch);
    if ($cErr != '') {
        $err = 'cURL ERROR: '.curl_errno($this->ch).': '.$cErr.'<br>';
        // TODO: there is a PHP bug that can cause this to SEGV for CURLINFO_CONTENT_TYPE
        foreach(curl_getinfo($this->ch) as $k => $v){
            $err .= "$k: $v<br>";
        }
        $this->debug($err);
        $this->setError($err);
        curl_close($this->ch);
        return false;
    } else {
        //echo '<pre>';
        //var_dump(curl_getinfo($this->ch));
        //echo '</pre>';
    }
    // close curl
    $this->debug('No cURL error, closing cURL');
    curl_close($this->ch);
    
    // try removing skippable headers
    $savedata = $data;
    while ($this->isSkippableCurlHeader($data)) {
        $this->debug("Found HTTP header to skip");
        if ($pos = strpos($data,"\r\n\r\n")) {
            $data = ltrim(substr($data,$pos));
        } elseif($pos = strpos($data,"\n\n") ) {
            $data = ltrim(substr($data,$pos));
        }
    }
    
    if ($data == '') {
        // have nothing left; just remove 100 header(s)
        $data = $savedata;
        while (preg_match('/^HTTP\/1.1 100/',$data)) {
            if ($pos = strpos($data,"\r\n\r\n")) {
                $data = ltrim(substr($data,$pos));
            } elseif($pos = strpos($data,"\n\n") ) {
                $data = ltrim(substr($data,$pos));
            }
        }
    }
    
    // separate content from HTTP headers
    if ($pos = strpos($data,"\r\n\r\n")) {
        $lb = "\r\n";
    } elseif( $pos = strpos($data,"\n\n")) {
        $lb = "\n";
    } else {
        $this->debug('no proper separation of headers and document');
        $this->setError('no proper separation of headers and document');
        return false;
    }
    $header_data = trim(substr($data,0,$pos));
    $header_array = explode($lb,$header_data);
    $data = ltrim(substr($data,$pos));
    $this->debug('found proper separation of headers and document');
    $this->debug('cleaned data, stringlen: '.strlen($data));
    // clean headers
    foreach ($header_array as $header_line) {
        $arr = explode(':',$header_line,2);
        if(count($arr) > 1){
            $header_name = strtolower(trim($arr[0]));
            $this->incoming_headers[$header_name] = trim($arr[1]);
            if ($header_name == 'set-cookie') {
                // TODO: allow multiple cookies from parseCookie
                $cookie = $this->parseCookie(trim($arr[1]));
                if ($cookie) {
                    $this->incoming_cookies[] = $cookie;
                    $this->debug('found cookie: ' . $cookie['name'] . ' = ' . $cookie['value']);
                } else {
                    $this->debug('did not find cookie in ' . trim($arr[1]));
                }
            }
        } else if (isset($header_name)) {
            // append continuation line to previous header
            $this->incoming_headers[$header_name] .= $lb . ' ' . $header_line;
        }
    }
}

$this->response_status_line = $header_array[0];
$arr = explode(' ', $this->response_status_line, 3);
$http_version = $arr[0];
$http_status = intval($arr[1]);
$http_reason = count($arr) > 2 ? $arr[2] : '';

// see if we need to resend the request with http digest authentication
if (isset($this->incoming_headers['location']) && ($http_status == 301 || $http_status == 302)) {
    $this->debug("Got $http_status $http_reason with Location: " . $this->incoming_headers['location']);
    $this->setURL($this->incoming_headers['location']);
    $this->tryagain = true;
    return false;
}

// see if we need to resend the request with http digest authentication
if (isset($this->incoming_headers['www-authenticate']) && $http_status == 401) {
    $this->debug("Got 401 $http_reason with WWW-Authenticate: " . $this->incoming_headers['www-authenticate']);
    if (strstr($this->incoming_headers['www-authenticate'], "Digest ")) {
        $this->debug('Server wants digest authentication');
        // remove "Digest " from our elements
        $digestString = str_replace('Digest ', '', $this->incoming_headers['www-authenticate']);
        
        // parse elements into array
        $digestElements = explode(',', $digestString);
        foreach ($digestElements as $val) {
            $tempElement = explode('=', trim($val), 2);
            $digestRequest[$tempElement[0]] = str_replace("\"", '', $tempElement[1]);
        }
        
        // should have (at least) qop, realm, nonce
        if (isset($digestRequest['nonce'])) {
            $this->setCredentials($this->username, $this->password, 'digest', $digestRequest);
            $this->tryagain = true;
            return false;
        }
    }
    $this->debug('HTTP authentication failed');
    $this->setError('HTTP authentication failed');
    return false;
}

if (
    ($http_status >= 300 && $http_status <= 307) ||
    ($http_status >= 400 && $http_status <= 417) ||
    ($http_status >= 501 && $http_status <= 505)
    ) {
        $this->setError("Unsupported HTTP response status $http_status $http_reason (soapclient->response has contents of the response)");
        return false;
    }
    
    // decode content-encoding
    if(isset($this->incoming_headers['content-encoding']) && $this->incoming_headers['content-encoding'] != ''){
        if(strtolower($this->incoming_headers['content-encoding']) == 'deflate' || strtolower($this->incoming_headers['content-encoding']) == 'gzip'){
            // if decoding works, use it. else assume data wasn't gzencoded
            if(function_exists('gzinflate')){
                //$timer->setMarker('starting decoding of gzip/deflated content');
                // IIS 5 requires gzinflate instead of gzuncompress (similar to IE 5 and gzdeflate v. gzcompress)
                // this means there are no Zlib headers, although there should be
                $this->debug('The gzinflate function exists');
                $datalen = strlen($data);
                if ($this->incoming_headers['content-encoding'] == 'deflate') {
                    if ($degzdata = @gzinflate($data)) {
                        $data = $degzdata;
                        $this->debug('The payload has been inflated to ' . strlen($data) . ' bytes');
                        if (strlen($data) < $datalen) {
                            // test for the case that the payload has been compressed twice
                            $this->debug('The inflated payload is smaller than the gzipped one; try again');
                            if ($degzdata = @gzinflate($data)) {
                                $data = $degzdata;
                                $this->debug('The payload has been inflated again to ' . strlen($data) . ' bytes');
                            }
                        }
                    } else {
                        $this->debug('Error using gzinflate to inflate the payload');
                        $this->setError('Error using gzinflate to inflate the payload');
                    }
                } elseif ($this->incoming_headers['content-encoding'] == 'gzip') {
                    if ($degzdata = @gzinflate(substr($data, 10))) {	// do our best
                        $data = $degzdata;
                        $this->debug('The payload has been un-gzipped to ' . strlen($data) . ' bytes');
                        if (strlen($data) < $datalen) {
                            // test for the case that the payload has been compressed twice
                            $this->debug('The un-gzipped payload is smaller than the gzipped one; try again');
                            if ($degzdata = @gzinflate(substr($data, 10))) {
                                $data = $degzdata;
                                $this->debug('The payload has been un-gzipped again to ' . strlen($data) . ' bytes');
                            }
                        }
                    } else {
                        $this->debug('Error using gzinflate to un-gzip the payload');
                        $this->setError('Error using gzinflate to un-gzip the payload');
                    }
                }
                //$timer->setMarker('finished decoding of gzip/deflated content');
                //print "<xmp>\nde-inflated:\n---------------\n$data\n-------------\n</xmp>";
                // set decoded payload
                $this->incoming_payload = $header_data.$lb.$lb.$data;
            } else {
                $this->debug('The server sent compressed data. Your php install must have the Zlib extension compiled in to support this.');
                $this->setError('The server sent compressed data. Your php install must have the Zlib extension compiled in to support this.');
            }
        } else {
            $this->debug('Unsupported Content-Encoding ' . $this->incoming_headers['content-encoding']);
            $this->setError('Unsupported Content-Encoding ' . $this->incoming_headers['content-encoding']);
        }
    } else {
        $this->debug('No Content-Encoding header');
    }
    
    if(strlen($data) == 0){
        $this->debug('no data after headers!');
        $this->setError('no data present after HTTP headers');
        return false;
    }
    
    return $data;
}

/**
 * sets the content-type for the SOAP message to be sent
 *
 * @param	string $type the content type, MIME style
 * @param	mixed $charset character set used for encoding (or false)
 * @access	public
 */
function setContentType($type, $charset = false) {
    $this->setHeader('Content-Type', $type . ($charset ? '; charset=' . $charset : ''));
}

/**
 * specifies that an HTTP persistent connection should be used
 *
 * @return	boolean whether the request was honored by this method.
 * @access	public
 */
function usePersistentConnection(){
    if (isset($this->outgoing_headers['Accept-Encoding'])) {
        return false;
    }
    $this->protocol_version = '1.1';
    $this->persistentConnection = true;
    $this->setHeader('Connection', 'Keep-Alive');
    return true;
}

/**
 * parse an incoming Cookie into it's parts
 *
 * @param	string $cookie_str content of cookie
 * @return	array with data of that cookie
 * @access	private
 */
/*
 * TODO: allow a Set-Cookie string to be parsed into multiple cookies
 */
function parseCookie($cookie_str) {
    $cookie_str = str_replace('; ', ';', $cookie_str) . ';';
    $data = preg_split('/;/', $cookie_str);
    $value_str = $data[0];
    
    $cookie_param = 'domain=';
    $start = strpos($cookie_str, $cookie_param);
    if ($start > 0) {
        $domain = substr($cookie_str, $start + strlen($cookie_param));
        $domain = substr($domain, 0, strpos($domain, ';'));
    } else {
        $domain = '';
    }
    
    $cookie_param = 'expires=';
    $start = strpos($cookie_str, $cookie_param);
    if ($start > 0) {
        $expires = substr($cookie_str, $start + strlen($cookie_param));
        $expires = substr($expires, 0, strpos($expires, ';'));
    } else {
        $expires = '';
    }
    
    $cookie_param = 'path=';
    $start = strpos($cookie_str, $cookie_param);
    if ( $start > 0 ) {
        $path = substr($cookie_str, $start + strlen($cookie_param));
        $path = substr($path, 0, strpos($path, ';'));
    } else {
        $path = '/';
    }
    
    $cookie_param = ';secure;';
    if (strpos($cookie_str, $cookie_param) !== FALSE) {
        $secure = true;
    } else {
        $secure = false;
    }
    
    $sep_pos = strpos($value_str, '=');
    
    if ($sep_pos) {
        $name = substr($value_str, 0, $sep_pos);
        $value = substr($value_str, $sep_pos + 1);
        $cookie= array(	'name' => $name,
            'value' => $value,
            'domain' => $domain,
            'path' => $path,
            'expires' => $expires,
            'secure' => $secure
        );
        return $cookie;
    }
    return false;
}

/**
 * sort out cookies for the current request
 *
 * @param	array $cookies array with all cookies
 * @param	boolean $secure is the send-content secure or not?
 * @return	string for Cookie-HTTP-Header
 * @access	private
 */
function getCookiesForRequest($cookies, $secure=false) {
    $cookie_str = '';
    if ((! is_null($cookies)) && (is_array($cookies))) {
        foreach ($cookies as $cookie) {
            if (! is_array($cookie)) {
                continue;
            }
            $this->debug("check cookie for validity: ".$cookie['name'].'='.$cookie['value']);
            if ((isset($cookie['expires'])) && (! empty($cookie['expires']))) {
                if (strtotime($cookie['expires']) <= time()) {
                    $this->debug('cookie has expired');
                    continue;
                }
            }
            if ((isset($cookie['domain'])) && (! empty($cookie['domain']))) {
                $domain = preg_quote($cookie['domain']);
                if (! preg_match("'.*$domain$'i", $this->host)) {
                    $this->debug('cookie has different domain');
                    continue;
                }
            }
            if ((isset($cookie['path'])) && (! empty($cookie['path']))) {
                $path = preg_quote($cookie['path']);
                if (! preg_match("'^$path.*'i", $this->path)) {
                    $this->debug('cookie is for a different path');
                    continue;
                }
            }
            if ((! $secure) && (isset($cookie['secure'])) && ($cookie['secure'])) {
                $this->debug('cookie is secure, transport is not');
                continue;
            }
            $cookie_str .= $cookie['name'] . '=' . $cookie['value'] . '; ';
            $this->debug('add cookie to Cookie-String: ' . $cookie['name'] . '=' . $cookie['value']);
        }
    }
    return $cookie_str;
}
}
<?php
namespace com\linways\base\soap;


/**
 *
 * [nu]soapclient higher level class for easy usage.
 *
 * usage:
 *
 * // instantiate client with server info
 * $soapclient = new nusoap_client( string path [ ,mixed wsdl] );
 *
 * // call method, get results
 * echo $soapclient->call( string methodname [ ,array parameters] );
 *
 * // bye bye client
 * unset($soapclient);
 *
 * @author   Dietrich Ayala <dietrich@ganx4.com>
 * @author   Scott Nichol <snichol@users.sourceforge.net>
 * @version  $Id: nusoap.php,v 1.123 2010/04/26 20:15:08 snichol Exp $
 * @access   public
 */
class nusoap_client extends nusoap_base  {
    
    var $username = '';				// Username for HTTP authentication
    var $password = '';				// Password for HTTP authentication
    var $authtype = '';				// Type of HTTP authentication
    var $certRequest = array();		// Certificate for HTTP SSL authentication
    var $requestHeaders = false;	// SOAP headers in request (text)
    var $responseHeaders = '';		// SOAP headers from response (incomplete namespace resolution) (text)
    var $responseHeader = NULL;		// SOAP Header from response (parsed)
    var $document = '';				// SOAP body response portion (incomplete namespace resolution) (text)
    var $endpoint;
    var $forceEndpoint = '';		// overrides WSDL endpoint
    var $proxyhost = '';
    var $proxyport = '';
    var $proxyusername = '';
    var $proxypassword = '';
    var $portName = '';				// port name to use in WSDL
    var $xml_encoding = '';			// character set encoding of incoming (response) messages
    var $http_encoding = false;
    var $timeout = 0;				// HTTP connection timeout
    var $response_timeout = 30;		// HTTP response timeout
    var $endpointType = '';			// soap|wsdl, empty for WSDL initialization error
    var $persistentConnection = false;
    var $defaultRpcParams = false;	// This is no longer used
    var $request = '';				// HTTP request
    var $response = '';				// HTTP response
    var $responseData = '';			// SOAP payload of response
    var $cookies = array();			// Cookies from response or for request
    var $decode_utf8 = true;		// toggles whether the parser decodes element content w/ utf8_decode()
    var $operations = array();		// WSDL operations, empty for WSDL initialization error
    var $curl_options = array();	// User-specified cURL options
    var $bindingType = '';			// WSDL operation binding type
    var $use_curl = false;			// whether to always try to use cURL
    
    /*
     * fault related variables
     */
    /**
     * @var      fault
     * @access   public
     */
    var $fault;
    /**
     * @var      faultcode
     * @access   public
     */
    var $faultcode;
    /**
     * @var      faultstring
     * @access   public
     */
    var $faultstring;
    /**
     * @var      faultdetail
     * @access   public
     */
    var $faultdetail;
    
    /**
     * constructor
     *
     * @param    mixed $endpoint SOAP server or WSDL URL (string), or wsdl instance (object)
     * @param    mixed $wsdl optional, set to 'wsdl' or true if using WSDL
     * @param    string $proxyhost optional
     * @param    string $proxyport optional
     * @param	string $proxyusername optional
     * @param	string $proxypassword optional
     * @param	integer $timeout set the connection timeout
     * @param	integer $response_timeout set the response timeout
     * @param	string $portName optional portName in WSDL document
     * @access   public
     */
    function nusoap_client($endpoint,$wsdl = false,$proxyhost = false,$proxyport = false,$proxyusername = false, $proxypassword = false, $timeout = 0, $response_timeout = 30, $portName = ''){
        parent::nusoap_base();
        $this->endpoint = $endpoint;
        $this->proxyhost = $proxyhost;
        $this->proxyport = $proxyport;
        $this->proxyusername = $proxyusername;
        $this->proxypassword = $proxypassword;
        $this->timeout = $timeout;
        $this->response_timeout = $response_timeout;
        $this->portName = $portName;
        
        $this->debug("ctor wsdl=$wsdl timeout=$timeout response_timeout=$response_timeout");
        $this->appendDebug('endpoint=' . $this->varDump($endpoint));
        
        // make values
        if($wsdl){
            if (is_object($endpoint) && (get_class($endpoint) == 'wsdl')) {
                $this->wsdl = $endpoint;
                $this->endpoint = $this->wsdl->wsdl;
                $this->wsdlFile = $this->endpoint;
                $this->debug('existing wsdl instance created from ' . $this->endpoint);
                $this->checkWSDL();
            } else {
                $this->wsdlFile = $this->endpoint;
                $this->wsdl = null;
                $this->debug('will use lazy evaluation of wsdl from ' . $this->endpoint);
            }
            $this->endpointType = 'wsdl';
        } else {
            $this->debug("instantiate SOAP with endpoint at $endpoint");
            $this->endpointType = 'soap';
        }
    }
    
    /**
     * calls method, returns PHP native type
     *
     * @param    string $operation SOAP server URL or path
     * @param    mixed $params An array, associative or simple, of the parameters
     *			              for the method call, or a string that is the XML
     *			              for the call.  For rpc style, this call will
     *			              wrap the XML in a tag named after the method, as
     *			              well as the SOAP Envelope and Body.  For document
     *			              style, this will only wrap with the Envelope and Body.
     *			              IMPORTANT: when using an array with document style,
     *			              in which case there
     *                         is really one parameter, the root of the fragment
     *                         used in the call, which encloses what programmers
     *                         normally think of parameters.  A parameter array
     *                         *must* include the wrapper.
     * @param	string $namespace optional method namespace (WSDL can override)
     * @param	string $soapAction optional SOAPAction value (WSDL can override)
     * @param	mixed $headers optional string of XML with SOAP header content, or array of soapval objects for SOAP headers, or associative array
     * @param	boolean $rpcParams optional (no longer used)
     * @param	string	$style optional (rpc|document) the style to use when serializing parameters (WSDL can override)
     * @param	string	$use optional (encoded|literal) the use when serializing parameters (WSDL can override)
     * @return	mixed	response from SOAP call, normally an associative array mirroring the structure of the XML response, false for certain fatal errors
     * @access   public
     */
    function call($operation,$params=array(),$namespace='http://tempuri.org',$soapAction='',$headers=false,$rpcParams=null,$style='rpc',$use='encoded'){
        $this->operation = $operation;
        $this->fault = false;
        $this->setError('');
        $this->request = '';
        $this->response = '';
        $this->responseData = '';
        $this->faultstring = '';
        $this->faultcode = '';
        $this->opData = array();
        
        $this->debug("call: operation=$operation, namespace=$namespace, soapAction=$soapAction, rpcParams=$rpcParams, style=$style, use=$use, endpointType=$this->endpointType");
        $this->appendDebug('params=' . $this->varDump($params));
        $this->appendDebug('headers=' . $this->varDump($headers));
        if ($headers) {
            $this->requestHeaders = $headers;
        }
        if ($this->endpointType == 'wsdl' && is_null($this->wsdl)) {
            $this->loadWSDL();
            if ($this->getError())
                return false;
        }
        // serialize parameters
        if($this->endpointType == 'wsdl' && $opData = $this->getOperationData($operation)){
            // use WSDL for operation
            $this->opData = $opData;
            $this->debug("found operation");
            $this->appendDebug('opData=' . $this->varDump($opData));
            if (isset($opData['soapAction'])) {
                $soapAction = $opData['soapAction'];
            }
            if (! $this->forceEndpoint) {
                $this->endpoint = $opData['endpoint'];
            } else {
                $this->endpoint = $this->forceEndpoint;
            }
            $namespace = isset($opData['input']['namespace']) ? $opData['input']['namespace'] :	$namespace;
            $style = $opData['style'];
            $use = $opData['input']['use'];
            // add ns to ns array
            if($namespace != '' && !isset($this->wsdl->namespaces[$namespace])){
                $nsPrefix = 'ns' . rand(1000, 9999);
                $this->wsdl->namespaces[$nsPrefix] = $namespace;
            }
            $nsPrefix = $this->wsdl->getPrefixFromNamespace($namespace);
            // serialize payload
            if (is_string($params)) {
                $this->debug("serializing param string for WSDL operation $operation");
                $payload = $params;
            } elseif (is_array($params)) {
                $this->debug("serializing param array for WSDL operation $operation");
                $payload = $this->wsdl->serializeRPCParameters($operation,'input',$params,$this->bindingType);
            } else {
                $this->debug('params must be array or string');
                $this->setError('params must be array or string');
                return false;
            }
            $usedNamespaces = $this->wsdl->usedNamespaces;
            if (isset($opData['input']['encodingStyle'])) {
                $encodingStyle = $opData['input']['encodingStyle'];
            } else {
                $encodingStyle = '';
            }
            $this->appendDebug($this->wsdl->getDebug());
            $this->wsdl->clearDebug();
            if ($errstr = $this->wsdl->getError()) {
                $this->debug('got wsdl error: '.$errstr);
                $this->setError('wsdl error: '.$errstr);
                return false;
            }
        } elseif($this->endpointType == 'wsdl') {
            // operation not in WSDL
            $this->appendDebug($this->wsdl->getDebug());
            $this->wsdl->clearDebug();
            $this->setError('operation '.$operation.' not present in WSDL.');
            $this->debug("operation '$operation' not present in WSDL.");
            return false;
        } else {
            // no WSDL
            //$this->namespaces['ns1'] = $namespace;
            $nsPrefix = 'ns' . rand(1000, 9999);
            // serialize
            $payload = '';
            if (is_string($params)) {
                $this->debug("serializing param string for operation $operation");
                $payload = $params;
            } elseif (is_array($params)) {
                $this->debug("serializing param array for operation $operation");
                foreach($params as $k => $v){
                    $payload .= $this->serialize_val($v,$k,false,false,false,false,$use);
                }
            } else {
                $this->debug('params must be array or string');
                $this->setError('params must be array or string');
                return false;
            }
            $usedNamespaces = array();
            if ($use == 'encoded') {
                $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
            } else {
                $encodingStyle = '';
            }
        }
        // wrap RPC calls with method element
        if ($style == 'rpc') {
            if ($use == 'literal') {
                $this->debug("wrapping RPC request with literal method element");
                if ($namespace) {
                    // http://www.ws-i.org/Profiles/BasicProfile-1.1-2004-08-24.html R2735 says rpc/literal accessor elements should not be in a namespace
                    $payload = "<$nsPrefix:$operation xmlns:$nsPrefix=\"$namespace\">" .
                    $payload .
                    "</$nsPrefix:$operation>";
                } else {
                    $payload = "<$operation>" . $payload . "</$operation>";
                }
            } else {
                $this->debug("wrapping RPC request with encoded method element");
                if ($namespace) {
                    $payload = "<$nsPrefix:$operation xmlns:$nsPrefix=\"$namespace\">" .
                    $payload .
                    "</$nsPrefix:$operation>";
                } else {
                    $payload = "<$operation>" .
                    $payload .
                    "</$operation>";
                }
            }
        }
        // serialize envelope
        $soapmsg = $this->serializeEnvelope($payload,$this->requestHeaders,$usedNamespaces,$style,$use,$encodingStyle);
        $this->debug("endpoint=$this->endpoint, soapAction=$soapAction, namespace=$namespace, style=$style, use=$use, encodingStyle=$encodingStyle");
        $this->debug('SOAP message length=' . strlen($soapmsg) . ' contents (max 1000 bytes)=' . substr($soapmsg, 0, 1000));
        // send
        $return = $this->send($this->getHTTPBody($soapmsg),$soapAction,$this->timeout,$this->response_timeout);
        if($errstr = $this->getError()){
            $this->debug('Error: '.$errstr);
            return false;
        } else {
            $this->return = $return;
            $this->debug('sent message successfully and got a(n) '.gettype($return));
            $this->appendDebug('return=' . $this->varDump($return));
            
            // fault?
            if(is_array($return) && isset($return['faultcode'])){
                $this->debug('got fault');
                $this->setError($return['faultcode'].': '.$return['faultstring']);
                $this->fault = true;
                foreach($return as $k => $v){
                    $this->$k = $v;
                    $this->debug("$k = $v<br>");
                }
                return $return;
            } elseif ($style == 'document') {
                // NOTE: if the response is defined to have multiple parts (i.e. unwrapped),
                // we are only going to return the first part here...sorry about that
                return $return;
            } else {
                // array of return values
                if(is_array($return)){
                    // multiple 'out' parameters, which we return wrapped up
                    // in the array
                    if(sizeof($return) > 1){
                        return $return;
                    }
                    // single 'out' parameter (normally the return value)
                    $return = array_shift($return);
                    $this->debug('return shifted value: ');
                    $this->appendDebug($this->varDump($return));
                    return $return;
                    // nothing returned (ie, echoVoid)
                } else {
                    return "";
                }
            }
        }
    }
    
    /**
     * check WSDL passed as an instance or pulled from an endpoint
     *
     * @access   private
     */
    function checkWSDL() {
        $this->appendDebug($this->wsdl->getDebug());
        $this->wsdl->clearDebug();
        $this->debug('checkWSDL');
        // catch errors
        if ($errstr = $this->wsdl->getError()) {
            $this->appendDebug($this->wsdl->getDebug());
            $this->wsdl->clearDebug();
            $this->debug('got wsdl error: '.$errstr);
            $this->setError('wsdl error: '.$errstr);
        } elseif ($this->operations = $this->wsdl->getOperations($this->portName, 'soap')) {
            $this->appendDebug($this->wsdl->getDebug());
            $this->wsdl->clearDebug();
            $this->bindingType = 'soap';
            $this->debug('got '.count($this->operations).' operations from wsdl '.$this->wsdlFile.' for binding type '.$this->bindingType);
        } elseif ($this->operations = $this->wsdl->getOperations($this->portName, 'soap12')) {
            $this->appendDebug($this->wsdl->getDebug());
            $this->wsdl->clearDebug();
            $this->bindingType = 'soap12';
            $this->debug('got '.count($this->operations).' operations from wsdl '.$this->wsdlFile.' for binding type '.$this->bindingType);
            $this->debug('**************** WARNING: SOAP 1.2 BINDING *****************');
        } else {
            $this->appendDebug($this->wsdl->getDebug());
            $this->wsdl->clearDebug();
            $this->debug('getOperations returned false');
            $this->setError('no operations defined in the WSDL document!');
        }
    }
    
    /**
     * instantiate wsdl object and parse wsdl file
     *
     * @access	public
     */
    function loadWSDL() {
        $this->debug('instantiating wsdl class with doc: '.$this->wsdlFile);
        $this->wsdl = new wsdl('',$this->proxyhost,$this->proxyport,$this->proxyusername,$this->proxypassword,$this->timeout,$this->response_timeout,$this->curl_options,$this->use_curl);
        $this->wsdl->setCredentials($this->username, $this->password, $this->authtype, $this->certRequest);
        $this->wsdl->fetchWSDL($this->wsdlFile);
        $this->checkWSDL();
    }
    
    /**
     * get available data pertaining to an operation
     *
     * @param    string $operation operation name
     * @return	array array of data pertaining to the operation
     * @access   public
     */
    function getOperationData($operation){
        if ($this->endpointType == 'wsdl' && is_null($this->wsdl)) {
            $this->loadWSDL();
            if ($this->getError())
                return false;
        }
        if(isset($this->operations[$operation])){
            return $this->operations[$operation];
        }
        $this->debug("No data for operation: $operation");
    }
    
    /**
     * send the SOAP message
     *
     * Note: if the operation has multiple return values
     * the return value of this method will be an array
     * of those values.
     *
     * @param    string $msg a SOAPx4 soapmsg object
     * @param    string $soapaction SOAPAction value
     * @param    integer $timeout set connection timeout in seconds
     * @param	integer $response_timeout set response timeout in seconds
     * @return	mixed native PHP types.
     * @access   private
     */
    function send($msg, $soapaction = '', $timeout=0, $response_timeout=30) {
        $this->checkCookies();
        // detect transport
        switch(true){
            // http(s)
            case preg_match('/^http/',$this->endpoint):
                $this->debug('transporting via HTTP');
                if($this->persistentConnection == true && is_object($this->persistentConnection)){
                    $http =& $this->persistentConnection;
                } else {
                    $http = new soap_transport_http($this->endpoint, $this->curl_options, $this->use_curl);
                    if ($this->persistentConnection) {
                        $http->usePersistentConnection();
                    }
                }
                $http->setContentType($this->getHTTPContentType(), $this->getHTTPContentTypeCharset());
                $http->setSOAPAction($soapaction);
                if($this->proxyhost && $this->proxyport){
                    $http->setProxy($this->proxyhost,$this->proxyport,$this->proxyusername,$this->proxypassword);
                }
                if($this->authtype != '') {
                    $http->setCredentials($this->username, $this->password, $this->authtype, array(), $this->certRequest);
                }
                if($this->http_encoding != ''){
                    $http->setEncoding($this->http_encoding);
                }
                $this->debug('sending message, length='.strlen($msg));
                if(preg_match('/^http:/',$this->endpoint)){
                    //if(strpos($this->endpoint,'http:')){
                    $this->responseData = $http->send($msg,$timeout,$response_timeout,$this->cookies);
                } elseif(preg_match('/^https/',$this->endpoint)){
                    //} elseif(strpos($this->endpoint,'https:')){
                    //if(phpversion() == '4.3.0-dev'){
                    //$response = $http->send($msg,$timeout,$response_timeout);
                        //$this->request = $http->outgoing_payload;
                        //$this->response = $http->incoming_payload;
                        //} else
                        $this->responseData = $http->sendHTTPS($msg,$timeout,$response_timeout,$this->cookies);
                } else {
                    $this->setError('no http/s in endpoint url');
                }
                $this->request = $http->outgoing_payload;
                $this->response = $http->incoming_payload;
                $this->appendDebug($http->getDebug());
                $this->UpdateCookies($http->incoming_cookies);
                
                // save transport object if using persistent connections
                if ($this->persistentConnection) {
                    $http->clearDebug();
                    if (!is_object($this->persistentConnection)) {
                        $this->persistentConnection = $http;
                    }
                }
                
                if($err = $http->getError()){
                    $this->setError('HTTP Error: '.$err);
                    return false;
                } elseif($this->getError()){
                    return false;
                } else {
                    $this->debug('got response, length='. strlen($this->responseData).' type='.$http->incoming_headers['content-type']);
                    return $this->parseResponse($http->incoming_headers, $this->responseData);
                }
                break;
            default:
                $this->setError('no transport found, or selected transport is not yet supported!');
                return false;
                break;
        }
    }
    
    /**
     * processes SOAP message returned from server
     *
     * @param	array	$headers	The HTTP headers
     * @param	string	$data		unprocessed response data from server
     * @return	mixed	value of the message, decoded into a PHP type
     * @access   private
     */
    function parseResponse($headers, $data) {
        $this->debug('Entering parseResponse() for data of length ' . strlen($data) . ' headers:');
        $this->appendDebug($this->varDump($headers));
        if (!isset($headers['content-type'])) {
            $this->setError('Response not of type text/xml (no content-type header)');
            return false;
        }
        if (!strstr($headers['content-type'], 'text/xml')) {
            $this->setError('Response not of type text/xml: ' . $headers['content-type']);
            return false;
        }
        if (strpos($headers['content-type'], '=')) {
            $enc = str_replace('"', '', substr(strstr($headers["content-type"], '='), 1));
            $this->debug('Got response encoding: ' . $enc);
            if(preg_match('/^(ISO-8859-1|US-ASCII|UTF-8)$/i',$enc)){
                $this->xml_encoding = strtoupper($enc);
            } else {
                $this->xml_encoding = 'US-ASCII';
            }
        } else {
            // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
            $this->xml_encoding = 'ISO-8859-1';
        }
        $this->debug('Use encoding: ' . $this->xml_encoding . ' when creating nusoap_parser');
        $parser = new nusoap_parser($data,$this->xml_encoding,$this->operation,$this->decode_utf8);
        // add parser debug data to our debug
        $this->appendDebug($parser->getDebug());
        // if parse errors
        if($errstr = $parser->getError()){
            $this->setError( $errstr);
            // destroy the parser object
            unset($parser);
            return false;
        } else {
            // get SOAP headers
            $this->responseHeaders = $parser->getHeaders();
            // get SOAP headers
            $this->responseHeader = $parser->get_soapheader();
            // get decoded message
            $return = $parser->get_soapbody();
            // add document for doclit support
            $this->document = $parser->document;
            // destroy the parser object
            unset($parser);
            // return decode message
            return $return;
        }
    }
    
    /**
     * sets user-specified cURL options
     *
     * @param	mixed $option The cURL option (always integer?)
     * @param	mixed $value The cURL option value
     * @access   public
     */
    function setCurlOption($option, $value) {
        $this->debug("setCurlOption option=$option, value=");
        $this->appendDebug($this->varDump($value));
        $this->curl_options[$option] = $value;
    }
    
    /**
     * sets the SOAP endpoint, which can override WSDL
     *
     * @param	string $endpoint The endpoint URL to use, or empty string or false to prevent override
     * @access   public
     */
    function setEndpoint($endpoint) {
        $this->debug("setEndpoint(\"$endpoint\")");
        $this->forceEndpoint = $endpoint;
    }
    
    /**
     * set the SOAP headers
     *
     * @param	mixed $headers String of XML with SOAP header content, or array of soapval objects for SOAP headers
     * @access   public
     */
    function setHeaders($headers){
        $this->debug("setHeaders headers=");
        $this->appendDebug($this->varDump($headers));
        $this->requestHeaders = $headers;
    }
    
    /**
     * get the SOAP response headers (namespace resolution incomplete)
     *
     * @return	string
     * @access   public
     */
    function getHeaders(){
        return $this->responseHeaders;
    }
    
    /**
     * get the SOAP response Header (parsed)
     *
     * @return	mixed
     * @access   public
     */
    function getHeader(){
        return $this->responseHeader;
    }
    
    /**
     * set proxy info here
     *
     * @param    string $proxyhost
     * @param    string $proxyport
     * @param	string $proxyusername
     * @param	string $proxypassword
     * @access   public
     */
    function setHTTPProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '') {
        $this->proxyhost = $proxyhost;
        $this->proxyport = $proxyport;
        $this->proxyusername = $proxyusername;
        $this->proxypassword = $proxypassword;
    }
    
    /**
     * if authenticating, set user credentials here
     *
     * @param    string $username
     * @param    string $password
     * @param	string $authtype (basic|digest|certificate|ntlm)
     * @param	array $certRequest (keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, verifypeer (optional), verifyhost (optional): see corresponding options in cURL docs)
     * @access   public
     */
    function setCredentials($username, $password, $authtype = 'basic', $certRequest = array()) {
        $this->debug("setCredentials username=$username authtype=$authtype certRequest=");
        $this->appendDebug($this->varDump($certRequest));
        $this->username = $username;
        $this->password = $password;
        $this->authtype = $authtype;
        $this->certRequest = $certRequest;
    }
    
    /**
     * use HTTP encoding
     *
     * @param    string $enc HTTP encoding
     * @access   public
     */
    function setHTTPEncoding($enc='gzip, deflate'){
        $this->debug("setHTTPEncoding(\"$enc\")");
        $this->http_encoding = $enc;
    }
    
    /**
     * Set whether to try to use cURL connections if possible
     *
     * @param	boolean $use Whether to try to use cURL
     * @access   public
     */
    function setUseCURL($use) {
        $this->debug("setUseCURL($use)");
        $this->use_curl = $use;
    }
    
    /**
     * use HTTP persistent connections if possible
     *
     * @access   public
     */
    function useHTTPPersistentConnection(){
        $this->debug("useHTTPPersistentConnection");
        $this->persistentConnection = true;
    }
    
    /**
     * gets the default RPC parameter setting.
     * If true, default is that call params are like RPC even for document style.
     * Each call() can override this value.
     *
     * This is no longer used.
     *
     * @return boolean
     * @access public
     * @deprecated
     */
    function getDefaultRpcParams() {
        return $this->defaultRpcParams;
    }
    
    /**
     * sets the default RPC parameter setting.
     * If true, default is that call params are like RPC even for document style
     * Each call() can override this value.
     *
     * This is no longer used.
     *
     * @param    boolean $rpcParams
     * @access public
     * @deprecated
     */
    function setDefaultRpcParams($rpcParams) {
        $this->defaultRpcParams = $rpcParams;
    }
    
    /**
     * dynamically creates an instance of a proxy class,
     * allowing user to directly call methods from wsdl
     *
     * @return   object soap_proxy object
     * @access   public
     */
    function getProxy() {
        $r = rand();
        $evalStr = $this->_getProxyClassCode($r);
        //$this->debug("proxy class: $evalStr");
        if ($this->getError()) {
            $this->debug("Error from _getProxyClassCode, so return NULL");
            return null;
        }
        // eval the class
        eval($evalStr);
        // instantiate proxy object
        eval("\$proxy = new nusoap_proxy_$r('');");
        // transfer current wsdl data to the proxy thereby avoiding parsing the wsdl twice
        $proxy->endpointType = 'wsdl';
        $proxy->wsdlFile = $this->wsdlFile;
        $proxy->wsdl = $this->wsdl;
        $proxy->operations = $this->operations;
        $proxy->defaultRpcParams = $this->defaultRpcParams;
        // transfer other state
        $proxy->soap_defencoding = $this->soap_defencoding;
        $proxy->username = $this->username;
        $proxy->password = $this->password;
        $proxy->authtype = $this->authtype;
        $proxy->certRequest = $this->certRequest;
        $proxy->requestHeaders = $this->requestHeaders;
        $proxy->endpoint = $this->endpoint;
        $proxy->forceEndpoint = $this->forceEndpoint;
        $proxy->proxyhost = $this->proxyhost;
        $proxy->proxyport = $this->proxyport;
        $proxy->proxyusername = $this->proxyusername;
        $proxy->proxypassword = $this->proxypassword;
        $proxy->http_encoding = $this->http_encoding;
        $proxy->timeout = $this->timeout;
        $proxy->response_timeout = $this->response_timeout;
        $proxy->persistentConnection = &$this->persistentConnection;
        $proxy->decode_utf8 = $this->decode_utf8;
        $proxy->curl_options = $this->curl_options;
        $proxy->bindingType = $this->bindingType;
        $proxy->use_curl = $this->use_curl;
        return $proxy;
    }
    
    /**
     * dynamically creates proxy class code
     *
     * @return   string PHP/NuSOAP code for the proxy class
     * @access   private
     */
    function _getProxyClassCode($r) {
        $this->debug("in getProxy endpointType=$this->endpointType");
        $this->appendDebug("wsdl=" . $this->varDump($this->wsdl));
        if ($this->endpointType != 'wsdl') {
            $evalStr = 'A proxy can only be created for a WSDL client';
            $this->setError($evalStr);
            $evalStr = "echo \"$evalStr\";";
            return $evalStr;
        }
        if ($this->endpointType == 'wsdl' && is_null($this->wsdl)) {
            $this->loadWSDL();
            if ($this->getError()) {
                return "echo \"" . $this->getError() . "\";";
            }
        }
        $evalStr = '';
        foreach ($this->operations as $operation => $opData) {
            if ($operation != '') {
                // create param string and param comment string
                if (sizeof($opData['input']['parts']) > 0) {
                    $paramStr = '';
                    $paramArrayStr = '';
                    $paramCommentStr = '';
                    foreach ($opData['input']['parts'] as $name => $type) {
                        $paramStr .= "\$$name, ";
                        $paramArrayStr .= "'$name' => \$$name, ";
                        $paramCommentStr .= "$type \$$name, ";
                    }
                    $paramStr = substr($paramStr, 0, strlen($paramStr)-2);
                    $paramArrayStr = substr($paramArrayStr, 0, strlen($paramArrayStr)-2);
                    $paramCommentStr = substr($paramCommentStr, 0, strlen($paramCommentStr)-2);
                } else {
                    $paramStr = '';
                    $paramArrayStr = '';
                    $paramCommentStr = 'void';
                }
                $opData['namespace'] = !isset($opData['namespace']) ? 'http://testuri.com' : $opData['namespace'];
                $evalStr .= "// $paramCommentStr
	function " . str_replace('.', '__', $operation) . "($paramStr) {
		\$params = array($paramArrayStr);
		return \$this->call('$operation', \$params, '".$opData['namespace']."', '".(isset($opData['soapAction']) ? $opData['soapAction'] : '')."');
	}
	";
                unset($paramStr);
                unset($paramCommentStr);
            }
        }
        $evalStr = 'class nusoap_proxy_'.$r.' extends nusoap_client {
	'.$evalStr.'
}';
        return $evalStr;
    }
    
    /**
     * dynamically creates proxy class code
     *
     * @return   string PHP/NuSOAP code for the proxy class
     * @access   public
     */
    function getProxyClassCode() {
        $r = rand();
        return $this->_getProxyClassCode($r);
    }
    
    /**
     * gets the HTTP body for the current request.
     *
     * @param string $soapmsg The SOAP payload
     * @return string The HTTP body, which includes the SOAP payload
     * @access private
     */
    function getHTTPBody($soapmsg) {
        return $soapmsg;
    }
    
    /**
     * gets the HTTP content type for the current request.
     *
     * Note: getHTTPBody must be called before this.
     *
     * @return string the HTTP content type for the current request.
     * @access private
     */
    function getHTTPContentType() {
        return 'text/xml';
    }
    
    /**
     * gets the HTTP content type charset for the current request.
     * returns false for non-text content types.
     *
     * Note: getHTTPBody must be called before this.
     *
     * @return string the HTTP content type charset for the current request.
     * @access private
     */
    function getHTTPContentTypeCharset() {
        return $this->soap_defencoding;
    }
    
    /*
     * whether or not parser should decode utf8 element content
     *
     * @return   always returns true
     * @access   public
     */
    function decodeUTF8($bool){
        $this->decode_utf8 = $bool;
        return true;
    }
    
    /**
     * adds a new Cookie into $this->cookies array
     *
     * @param	string $name Cookie Name
     * @param	string $value Cookie Value
     * @return	boolean if cookie-set was successful returns true, else false
     * @access	public
     */
    function setCookie($name, $value) {
        if (strlen($name) == 0) {
            return false;
        }
        $this->cookies[] = array('name' => $name, 'value' => $value);
        return true;
    }
    
    /**
     * gets all Cookies
     *
     * @return   array with all internal cookies
     * @access   public
     */
    function getCookies() {
        return $this->cookies;
    }
    
    /**
     * checks all Cookies and delete those which are expired
     *
     * @return   boolean always return true
     * @access   private
     */
    function checkCookies() {
        if (sizeof($this->cookies) == 0) {
            return true;
        }
        $this->debug('checkCookie: check ' . sizeof($this->cookies) . ' cookies');
        $curr_cookies = $this->cookies;
        $this->cookies = array();
        foreach ($curr_cookies as $cookie) {
            if (! is_array($cookie)) {
                $this->debug('Remove cookie that is not an array');
                continue;
            }
            if ((isset($cookie['expires'])) && (! empty($cookie['expires']))) {
                if (strtotime($cookie['expires']) > time()) {
                    $this->cookies[] = $cookie;
                } else {
                    $this->debug('Remove expired cookie ' . $cookie['name']);
                }
            } else {
                $this->cookies[] = $cookie;
            }
        }
        $this->debug('checkCookie: '.sizeof($this->cookies).' cookies left in array');
        return true;
    }
    
    /**
     * updates the current cookies with a new set
     *
     * @param	array $cookies new cookies with which to update current ones
     * @return	boolean always return true
     * @access	private
     */
    function UpdateCookies($cookies) {
        if (sizeof($this->cookies) == 0) {
            // no existing cookies: take whatever is new
            if (sizeof($cookies) > 0) {
                $this->debug('Setting new cookie(s)');
                $this->cookies = $cookies;
            }
            return true;
        }
        if (sizeof($cookies) == 0) {
            // no new cookies: keep what we've got
            return true;
        }
        // merge
        foreach ($cookies as $newCookie) {
            if (!is_array($newCookie)) {
                continue;
            }
            if ((!isset($newCookie['name'])) || (!isset($newCookie['value']))) {
                continue;
            }
            $newName = $newCookie['name'];
            
            $found = false;
            for ($i = 0; $i < count($this->cookies); $i++) {
                $cookie = $this->cookies[$i];
                if (!is_array($cookie)) {
                    continue;
                }
                if (!isset($cookie['name'])) {
                    continue;
                }
                if ($newName != $cookie['name']) {
                    continue;
                }
                $newDomain = isset($newCookie['domain']) ? $newCookie['domain'] : 'NODOMAIN';
                $domain = isset($cookie['domain']) ? $cookie['domain'] : 'NODOMAIN';
                if ($newDomain != $domain) {
                    continue;
                }
                $newPath = isset($newCookie['path']) ? $newCookie['path'] : 'NOPATH';
                $path = isset($cookie['path']) ? $cookie['path'] : 'NOPATH';
                if ($newPath != $path) {
                    continue;
                }
                $this->cookies[$i] = $newCookie;
                $found = true;
                $this->debug('Update cookie ' . $newName . '=' . $newCookie['value']);
                break;
            }
            if (! $found) {
                $this->debug('Add cookie ' . $newName . '=' . $newCookie['value']);
                $this->cookies[] = $newCookie;
            }
        }
        return true;
    }
}

<?php
namespace com\linways\base\soap;
/**
* parses an XML Schema, allows access to it's data, other utility methods.
* imperfect, no validation... yet, but quite functional.
*
* @author   Dietrich Ayala <dietrich@ganx4.com>
* @author   Scott Nichol <snichol@users.sourceforge.net>
* @version  $Id: nusoap.php,v 1.123 2010/04/26 20:15:08 snichol Exp $
* @access   public
*/
class nusoap_xmlschema extends nusoap_base  {
	
	// files
	var $schema = '';
	var $xml = '';
	// namespaces
	var $enclosingNamespaces;
	// schema info
	var $schemaInfo = array();
	var $schemaTargetNamespace = '';
	// types, elements, attributes defined by the schema
	var $attributes = array();
	var $complexTypes = array();
	var $complexTypeStack = array();
	var $currentComplexType = null;
	var $elements = array();
	var $elementStack = array();
	var $currentElement = null;
	var $simpleTypes = array();
	var $simpleTypeStack = array();
	var $currentSimpleType = null;
	// imports
	var $imports = array();
	// parser vars
	var $parser;
	var $position = 0;
	var $depth = 0;
	var $depth_array = array();
	var $message = array();
	var $defaultNamespace = array();
    
	/**
	* constructor
	*
	* @param    string $schema schema document URI
	* @param    string $xml xml document URI
	* @param	string $namespaces namespaces defined in enclosing XML
	* @access   public
	*/
	function nusoap_xmlschema($schema='',$xml='',$namespaces=array()){
		parent::nusoap_base();
		$this->debug('nusoap_xmlschema class instantiated, inside constructor');
		// files
		$this->schema = $schema;
		$this->xml = $xml;

		// namespaces
		$this->enclosingNamespaces = $namespaces;
		$this->namespaces = array_merge($this->namespaces, $namespaces);

		// parse schema file
		if($schema != ''){
			$this->debug('initial schema file: '.$schema);
			$this->parseFile($schema, 'schema');
		}

		// parse xml file
		if($xml != ''){
			$this->debug('initial xml file: '.$xml);
			$this->parseFile($xml, 'xml');
		}

	}

    /**
    * parse an XML file
    *
    * @param string $xml path/URL to XML file
    * @param string $type (schema | xml)
	* @return boolean
    * @access public
    */
	function parseFile($xml,$type){
		// parse xml file
		if($xml != ""){
			$xmlStr = @join("",@file($xml));
			if($xmlStr == ""){
				$msg = 'Error reading XML from '.$xml;
				$this->setError($msg);
				$this->debug($msg);
			return false;
			} else {
				$this->debug("parsing $xml");
				$this->parseString($xmlStr,$type);
				$this->debug("done parsing $xml");
			return true;
			}
		}
		return false;
	}

	/**
	* parse an XML string
	*
	* @param    string $xml path or URL
    * @param	string $type (schema|xml)
	* @access   private
	*/
	function parseString($xml,$type){
		// parse xml string
		if($xml != ""){

	    	// Create an XML parser.
	    	$this->parser = xml_parser_create();
	    	// Set the options for parsing the XML data.
	    	xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);

	    	// Set the object for the parser.
	    	xml_set_object($this->parser, $this);

	    	// Set the element handlers for the parser.
			if($type == "schema"){
		    	xml_set_element_handler($this->parser, 'schemaStartElement','schemaEndElement');
		    	xml_set_character_data_handler($this->parser,'schemaCharacterData');
			} elseif($type == "xml"){
				xml_set_element_handler($this->parser, 'xmlStartElement','xmlEndElement');
		    	xml_set_character_data_handler($this->parser,'xmlCharacterData');
			}

		    // Parse the XML file.
		    if(!xml_parse($this->parser,$xml,true)){
			// Display an error message.
				$errstr = sprintf('XML error parsing XML schema on line %d: %s',
				xml_get_current_line_number($this->parser),
				xml_error_string(xml_get_error_code($this->parser))
				);
				$this->debug($errstr);
				$this->debug("XML payload:\n" . $xml);
				$this->setError($errstr);
	    	}
            
			xml_parser_free($this->parser);
		} else{
			$this->debug('no xml passed to parseString()!!');
			$this->setError('no xml passed to parseString()!!');
		}
	}

	/**
	 * gets a type name for an unnamed type
	 *
	 * @param	string	Element name
	 * @return	string	A type name for an unnamed type
	 * @access	private
	 */
	function CreateTypeName($ename) {
		$scope = '';
		for ($i = 0; $i < count($this->complexTypeStack); $i++) {
			$scope .= $this->complexTypeStack[$i] . '_';
		}
		return $scope . $ename . '_ContainedType';
	}
	
	/**
	* start-element handler
	*
	* @param    string $parser XML parser object
	* @param    string $name element name
	* @param    string $attrs associative array of attributes
	* @access   private
	*/
	function schemaStartElement($parser, $name, $attrs) {
		
		// position in the total number of elements, starting from 0
		$pos = $this->position++;
		$depth = $this->depth++;
		// set self as current value for this depth
		$this->depth_array[$depth] = $pos;
		$this->message[$pos] = array('cdata' => ''); 
		if ($depth > 0) {
			$this->defaultNamespace[$pos] = $this->defaultNamespace[$this->depth_array[$depth - 1]];
		} else {
			$this->defaultNamespace[$pos] = false;
		}

		// get element prefix
		if($prefix = $this->getPrefix($name)){
			// get unqualified name
			$name = $this->getLocalPart($name);
		} else {
        	$prefix = '';
        }
		
        // loop thru attributes, expanding, and registering namespace declarations
        if(count($attrs) > 0){
        	foreach($attrs as $k => $v){
                // if ns declarations, add to class level array of valid namespaces
				if(preg_match('/^xmlns/',$k)){
                	//$this->xdebug("$k: $v");
                	//$this->xdebug('ns_prefix: '.$this->getPrefix($k));
                	if($ns_prefix = substr(strrchr($k,':'),1)){
                		//$this->xdebug("Add namespace[$ns_prefix] = $v");
						$this->namespaces[$ns_prefix] = $v;
					} else {
						$this->defaultNamespace[$pos] = $v;
						if (! $this->getPrefixFromNamespace($v)) {
							$this->namespaces['ns'.(count($this->namespaces)+1)] = $v;
						}
					}
					if($v == 'http://www.w3.org/2001/XMLSchema' || $v == 'http://www.w3.org/1999/XMLSchema' || $v == 'http://www.w3.org/2000/10/XMLSchema'){
						$this->XMLSchemaVersion = $v;
						$this->namespaces['xsi'] = $v.'-instance';
					}
				}
        	}
        	foreach($attrs as $k => $v){
                // expand each attribute
                $k = strpos($k,':') ? $this->expandQname($k) : $k;
                $v = strpos($v,':') ? $this->expandQname($v) : $v;
        		$eAttrs[$k] = $v;
        	}
        	$attrs = $eAttrs;
        } else {
        	$attrs = array();
        }
		// find status, register data
		switch($name){
			case 'all':			// (optional) compositor content for a complexType
			case 'choice':
			case 'group':
			case 'sequence':
				//$this->xdebug("compositor $name for currentComplexType: $this->currentComplexType and currentElement: $this->currentElement");
				$this->complexTypes[$this->currentComplexType]['compositor'] = $name;
				//if($name == 'all' || $name == 'sequence'){
				//	$this->complexTypes[$this->currentComplexType]['phpType'] = 'struct';
				//}
			break;
			case 'attribute':	// complexType attribute
            	//$this->xdebug("parsing attribute $attrs[name] $attrs[ref] of value: ".$attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']);
            	$this->xdebug("parsing attribute:");
            	$this->appendDebug($this->varDump($attrs));
				if (!isset($attrs['form'])) {
					// TODO: handle globals
					$attrs['form'] = $this->schemaInfo['attributeFormDefault'];
				}
            	if (isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])) {
					$v = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
					if (!strpos($v, ':')) {
						// no namespace in arrayType attribute value...
						if ($this->defaultNamespace[$pos]) {
							// ...so use the default
							$attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'] = $this->defaultNamespace[$pos] . ':' . $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
						}
					}
            	}
                if(isset($attrs['name'])){
					$this->attributes[$attrs['name']] = $attrs;
					$aname = $attrs['name'];
				} elseif(isset($attrs['ref']) && $attrs['ref'] == 'http://schemas.xmlsoap.org/soap/encoding/:arrayType'){
					if (isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])) {
	                	$aname = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
	                } else {
	                	$aname = '';
	                }
				} elseif(isset($attrs['ref'])){
					$aname = $attrs['ref'];
                    $this->attributes[$attrs['ref']] = $attrs;
				}
                
				if($this->currentComplexType){	// This should *always* be
					$this->complexTypes[$this->currentComplexType]['attrs'][$aname] = $attrs;
				}
				// arrayType attribute
				if(isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']) || $this->getLocalPart($aname) == 'arrayType'){
					$this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
                	$prefix = $this->getPrefix($aname);
					if(isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])){
						$v = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'];
					} else {
						$v = '';
					}
                    if(strpos($v,'[,]')){
                        $this->complexTypes[$this->currentComplexType]['multidimensional'] = true;
                    }
                    $v = substr($v,0,strpos($v,'[')); // clip the []
                    if(!strpos($v,':') && isset($this->typemap[$this->XMLSchemaVersion][$v])){
                        $v = $this->XMLSchemaVersion.':'.$v;
                    }
                    $this->complexTypes[$this->currentComplexType]['arrayType'] = $v;
				}
			break;
			case 'complexContent':	// (optional) content for a complexType
				$this->xdebug("do nothing for element $name");
			break;
			case 'complexType':
				array_push($this->complexTypeStack, $this->currentComplexType);
				if(isset($attrs['name'])){
					// TODO: what is the scope of named complexTypes that appear
					//       nested within other c complexTypes?
					$this->xdebug('processing named complexType '.$attrs['name']);
					//$this->currentElement = false;
					$this->currentComplexType = $attrs['name'];
					$this->complexTypes[$this->currentComplexType] = $attrs;
					$this->complexTypes[$this->currentComplexType]['typeClass'] = 'complexType';
					// This is for constructs like
					//           <complexType name="ListOfString" base="soap:Array">
					//                <sequence>
					//                    <element name="string" type="xsd:string"
					//                        minOccurs="0" maxOccurs="unbounded" />
					//                </sequence>
					//            </complexType>
					if(isset($attrs['base']) && preg_match('/:Array$/',$attrs['base'])){
						$this->xdebug('complexType is unusual array');
						$this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
					} else {
						$this->complexTypes[$this->currentComplexType]['phpType'] = 'struct';
					}
				} else {
					$name = $this->CreateTypeName($this->currentElement);
					$this->xdebug('processing unnamed complexType for element ' . $this->currentElement . ' named ' . $name);
					$this->currentComplexType = $name;
					//$this->currentElement = false;
					$this->complexTypes[$this->currentComplexType] = $attrs;
					$this->complexTypes[$this->currentComplexType]['typeClass'] = 'complexType';
					// This is for constructs like
					//           <complexType name="ListOfString" base="soap:Array">
					//                <sequence>
					//                    <element name="string" type="xsd:string"
					//                        minOccurs="0" maxOccurs="unbounded" />
					//                </sequence>
					//            </complexType>
					if(isset($attrs['base']) && preg_match('/:Array$/',$attrs['base'])){
						$this->xdebug('complexType is unusual array');
						$this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
					} else {
						$this->complexTypes[$this->currentComplexType]['phpType'] = 'struct';
					}
				}
				$this->complexTypes[$this->currentComplexType]['simpleContent'] = 'false';
			break;
			case 'element':
				array_push($this->elementStack, $this->currentElement);
				if (!isset($attrs['form'])) {
					if ($this->currentComplexType) {
						$attrs['form'] = $this->schemaInfo['elementFormDefault'];
					} else {
						// global
						$attrs['form'] = 'qualified';
					}
				}
				if(isset($attrs['type'])){
					$this->xdebug("processing typed element ".$attrs['name']." of type ".$attrs['type']);
					if (! $this->getPrefix($attrs['type'])) {
						if ($this->defaultNamespace[$pos]) {
							$attrs['type'] = $this->defaultNamespace[$pos] . ':' . $attrs['type'];
							$this->xdebug('used default namespace to make type ' . $attrs['type']);
						}
					}
					// This is for constructs like
					//           <complexType name="ListOfString" base="soap:Array">
					//                <sequence>
					//                    <element name="string" type="xsd:string"
					//                        minOccurs="0" maxOccurs="unbounded" />
					//                </sequence>
					//            </complexType>
					if ($this->currentComplexType && $this->complexTypes[$this->currentComplexType]['phpType'] == 'array') {
						$this->xdebug('arrayType for unusual array is ' . $attrs['type']);
						$this->complexTypes[$this->currentComplexType]['arrayType'] = $attrs['type'];
					}
					$this->currentElement = $attrs['name'];
					$ename = $attrs['name'];
				} elseif(isset($attrs['ref'])){
					$this->xdebug("processing element as ref to ".$attrs['ref']);
					$this->currentElement = "ref to ".$attrs['ref'];
					$ename = $this->getLocalPart($attrs['ref']);
				} else {
					$type = $this->CreateTypeName($this->currentComplexType . '_' . $attrs['name']);
					$this->xdebug("processing untyped element " . $attrs['name'] . ' type ' . $type);
					$this->currentElement = $attrs['name'];
					$attrs['type'] = $this->schemaTargetNamespace . ':' . $type;
					$ename = $attrs['name'];
				}
				if (isset($ename) && $this->currentComplexType) {
					$this->xdebug("add element $ename to complexType $this->currentComplexType");
					$this->complexTypes[$this->currentComplexType]['elements'][$ename] = $attrs;
				} elseif (!isset($attrs['ref'])) {
					$this->xdebug("add element $ename to elements array");
					$this->elements[ $attrs['name'] ] = $attrs;
					$this->elements[ $attrs['name'] ]['typeClass'] = 'element';
				}
			break;
			case 'enumeration':	//	restriction value list member
				$this->xdebug('enumeration ' . $attrs['value']);
				if ($this->currentSimpleType) {
					$this->simpleTypes[$this->currentSimpleType]['enumeration'][] = $attrs['value'];
				} elseif ($this->currentComplexType) {
					$this->complexTypes[$this->currentComplexType]['enumeration'][] = $attrs['value'];
				}
			break;
			case 'extension':	// simpleContent or complexContent type extension
				$this->xdebug('extension ' . $attrs['base']);
				if ($this->currentComplexType) {
					$ns = $this->getPrefix($attrs['base']);
					if ($ns == '') {
						$this->complexTypes[$this->currentComplexType]['extensionBase'] = $this->schemaTargetNamespace . ':' . $attrs['base'];
					} else {
						$this->complexTypes[$this->currentComplexType]['extensionBase'] = $attrs['base'];
					}
				} else {
					$this->xdebug('no current complexType to set extensionBase');
				}
			break;
			case 'import':
			    if (isset($attrs['schemaLocation'])) {
					$this->xdebug('import namespace ' . $attrs['namespace'] . ' from ' . $attrs['schemaLocation']);
                    $this->imports[$attrs['namespace']][] = array('location' => $attrs['schemaLocation'], 'loaded' => false);
				} else {
					$this->xdebug('import namespace ' . $attrs['namespace']);
                    $this->imports[$attrs['namespace']][] = array('location' => '', 'loaded' => true);
					if (! $this->getPrefixFromNamespace($attrs['namespace'])) {
						$this->namespaces['ns'.(count($this->namespaces)+1)] = $attrs['namespace'];
					}
				}
			break;
			case 'include':
			    if (isset($attrs['schemaLocation'])) {
					$this->xdebug('include into namespace ' . $this->schemaTargetNamespace . ' from ' . $attrs['schemaLocation']);
                    $this->imports[$this->schemaTargetNamespace][] = array('location' => $attrs['schemaLocation'], 'loaded' => false);
				} else {
					$this->xdebug('ignoring invalid XML Schema construct: include without schemaLocation attribute');
				}
			break;
			case 'list':	// simpleType value list
				$this->xdebug("do nothing for element $name");
			break;
			case 'restriction':	// simpleType, simpleContent or complexContent value restriction
				$this->xdebug('restriction ' . $attrs['base']);
				if($this->currentSimpleType){
					$this->simpleTypes[$this->currentSimpleType]['type'] = $attrs['base'];
				} elseif($this->currentComplexType){
					$this->complexTypes[$this->currentComplexType]['restrictionBase'] = $attrs['base'];
					if(strstr($attrs['base'],':') == ':Array'){
						$this->complexTypes[$this->currentComplexType]['phpType'] = 'array';
					}
				}
			break;
			case 'schema':
				$this->schemaInfo = $attrs;
				$this->schemaInfo['schemaVersion'] = $this->getNamespaceFromPrefix($prefix);
				if (isset($attrs['targetNamespace'])) {
					$this->schemaTargetNamespace = $attrs['targetNamespace'];
				}
				if (!isset($attrs['elementFormDefault'])) {
					$this->schemaInfo['elementFormDefault'] = 'unqualified';
				}
				if (!isset($attrs['attributeFormDefault'])) {
					$this->schemaInfo['attributeFormDefault'] = 'unqualified';
				}
			break;
			case 'simpleContent':	// (optional) content for a complexType
				if ($this->currentComplexType) {	// This should *always* be
					$this->complexTypes[$this->currentComplexType]['simpleContent'] = 'true';
				} else {
					$this->xdebug("do nothing for element $name because there is no current complexType");
				}
			break;
			case 'simpleType':
				array_push($this->simpleTypeStack, $this->currentSimpleType);
				if(isset($attrs['name'])){
					$this->xdebug("processing simpleType for name " . $attrs['name']);
					$this->currentSimpleType = $attrs['name'];
					$this->simpleTypes[ $attrs['name'] ] = $attrs;
					$this->simpleTypes[ $attrs['name'] ]['typeClass'] = 'simpleType';
					$this->simpleTypes[ $attrs['name'] ]['phpType'] = 'scalar';
				} else {
					$name = $this->CreateTypeName($this->currentComplexType . '_' . $this->currentElement);
					$this->xdebug('processing unnamed simpleType for element ' . $this->currentElement . ' named ' . $name);
					$this->currentSimpleType = $name;
					//$this->currentElement = false;
					$this->simpleTypes[$this->currentSimpleType] = $attrs;
					$this->simpleTypes[$this->currentSimpleType]['phpType'] = 'scalar';
				}
			break;
			case 'union':	// simpleType type list
				$this->xdebug("do nothing for element $name");
			break;
			default:
				$this->xdebug("do not have any logic to process element $name");
		}
	}

	/**
	* end-element handler
	*
	* @param    string $parser XML parser object
	* @param    string $name element name
	* @access   private
	*/
	function schemaEndElement($parser, $name) {
		// bring depth down a notch
		$this->depth--;
		// position of current element is equal to the last value left in depth_array for my depth
		if(isset($this->depth_array[$this->depth])){
        	$pos = $this->depth_array[$this->depth];
        }
		// get element prefix
		if ($prefix = $this->getPrefix($name)){
			// get unqualified name
			$name = $this->getLocalPart($name);
		} else {
        	$prefix = '';
        }
		// move on...
		if($name == 'complexType'){
			$this->xdebug('done processing complexType ' . ($this->currentComplexType ? $this->currentComplexType : '(unknown)'));
			$this->xdebug($this->varDump($this->complexTypes[$this->currentComplexType]));
			$this->currentComplexType = array_pop($this->complexTypeStack);
			//$this->currentElement = false;
		}
		if($name == 'element'){
			$this->xdebug('done processing element ' . ($this->currentElement ? $this->currentElement : '(unknown)'));
			$this->currentElement = array_pop($this->elementStack);
		}
		if($name == 'simpleType'){
			$this->xdebug('done processing simpleType ' . ($this->currentSimpleType ? $this->currentSimpleType : '(unknown)'));
			$this->xdebug($this->varDump($this->simpleTypes[$this->currentSimpleType]));
			$this->currentSimpleType = array_pop($this->simpleTypeStack);
		}
	}

	/**
	* element content handler
	*
	* @param    string $parser XML parser object
	* @param    string $data element content
	* @access   private
	*/
	function schemaCharacterData($parser, $data){
		$pos = $this->depth_array[$this->depth - 1];
		$this->message[$pos]['cdata'] .= $data;
	}

	/**
	* serialize the schema
	*
	* @access   public
	*/
	function serializeSchema(){

		$schemaPrefix = $this->getPrefixFromNamespace($this->XMLSchemaVersion);
		$xml = '';
		// imports
		if (sizeof($this->imports) > 0) {
			foreach($this->imports as $ns => $list) {
				foreach ($list as $ii) {
					if ($ii['location'] != '') {
						$xml .= " <$schemaPrefix:import location=\"" . $ii['location'] . '" namespace="' . $ns . "\" />\n";
					} else {
						$xml .= " <$schemaPrefix:import namespace=\"" . $ns . "\" />\n";
					}
				}
			} 
		} 
		// complex types
		foreach($this->complexTypes as $typeName => $attrs){
			$contentStr = '';
			// serialize child elements
			if(isset($attrs['elements']) && (count($attrs['elements']) > 0)){
				foreach($attrs['elements'] as $element => $eParts){
					if(isset($eParts['ref'])){
						$contentStr .= "   <$schemaPrefix:element ref=\"$element\"/>\n";
					} else {
						$contentStr .= "   <$schemaPrefix:element name=\"$element\" type=\"" . $this->contractQName($eParts['type']) . "\"";
						foreach ($eParts as $aName => $aValue) {
							// handle, e.g., abstract, default, form, minOccurs, maxOccurs, nillable
							if ($aName != 'name' && $aName != 'type') {
								$contentStr .= " $aName=\"$aValue\"";
							}
						}
						$contentStr .= "/>\n";
					}
				}
				// compositor wraps elements
				if (isset($attrs['compositor']) && ($attrs['compositor'] != '')) {
					$contentStr = "  <$schemaPrefix:$attrs[compositor]>\n".$contentStr."  </$schemaPrefix:$attrs[compositor]>\n";
				}
			}
			// attributes
			if(isset($attrs['attrs']) && (count($attrs['attrs']) >= 1)){
				foreach($attrs['attrs'] as $attr => $aParts){
					$contentStr .= "    <$schemaPrefix:attribute";
					foreach ($aParts as $a => $v) {
						if ($a == 'ref' || $a == 'type') {
							$contentStr .= " $a=\"".$this->contractQName($v).'"';
						} elseif ($a == 'http://schemas.xmlsoap.org/wsdl/:arrayType') {
							$this->usedNamespaces['wsdl'] = $this->namespaces['wsdl'];
							$contentStr .= ' wsdl:arrayType="'.$this->contractQName($v).'"';
						} else {
							$contentStr .= " $a=\"$v\"";
						}
					}
					$contentStr .= "/>\n";
				}
			}
			// if restriction
			if (isset($attrs['restrictionBase']) && $attrs['restrictionBase'] != ''){
				$contentStr = "   <$schemaPrefix:restriction base=\"".$this->contractQName($attrs['restrictionBase'])."\">\n".$contentStr."   </$schemaPrefix:restriction>\n";
				// complex or simple content
				if ((isset($attrs['elements']) && count($attrs['elements']) > 0) || (isset($attrs['attrs']) && count($attrs['attrs']) > 0)){
					$contentStr = "  <$schemaPrefix:complexContent>\n".$contentStr."  </$schemaPrefix:complexContent>\n";
				}
			}
			// finalize complex type
			if($contentStr != ''){
				$contentStr = " <$schemaPrefix:complexType name=\"$typeName\">\n".$contentStr." </$schemaPrefix:complexType>\n";
			} else {
				$contentStr = " <$schemaPrefix:complexType name=\"$typeName\"/>\n";
			}
			$xml .= $contentStr;
		}
		// simple types
		if(isset($this->simpleTypes) && count($this->simpleTypes) > 0){
			foreach($this->simpleTypes as $typeName => $eParts){
				$xml .= " <$schemaPrefix:simpleType name=\"$typeName\">\n  <$schemaPrefix:restriction base=\"".$this->contractQName($eParts['type'])."\">\n";
				if (isset($eParts['enumeration'])) {
					foreach ($eParts['enumeration'] as $e) {
						$xml .= "  <$schemaPrefix:enumeration value=\"$e\"/>\n";
					}
				}
				$xml .= "  </$schemaPrefix:restriction>\n </$schemaPrefix:simpleType>";
			}
		}
		// elements
		if(isset($this->elements) && count($this->elements) > 0){
			foreach($this->elements as $element => $eParts){
				$xml .= " <$schemaPrefix:element name=\"$element\" type=\"".$this->contractQName($eParts['type'])."\"/>\n";
			}
		}
		// attributes
		if(isset($this->attributes) && count($this->attributes) > 0){
			foreach($this->attributes as $attr => $aParts){
				$xml .= " <$schemaPrefix:attribute name=\"$attr\" type=\"".$this->contractQName($aParts['type'])."\"\n/>";
			}
		}
		// finish 'er up
		$attr = '';
		foreach ($this->schemaInfo as $k => $v) {
			if ($k == 'elementFormDefault' || $k == 'attributeFormDefault') {
				$attr .= " $k=\"$v\"";
			}
		}
		$el = "<$schemaPrefix:schema$attr targetNamespace=\"$this->schemaTargetNamespace\"\n";
		foreach (array_diff($this->usedNamespaces, $this->enclosingNamespaces) as $nsp => $ns) {
			$el .= " xmlns:$nsp=\"$ns\"";
		}
		$xml = $el . ">\n".$xml."</$schemaPrefix:schema>\n";
		return $xml;
	}

	/**
	* adds debug data to the clas level debug string
	*
	* @param    string $string debug data
	* @access   private
	*/
	function xdebug($string){
		$this->debug('<' . $this->schemaTargetNamespace . '> '.$string);
	}

    /**
    * get the PHP type of a user defined type in the schema
    * PHP type is kind of a misnomer since it actually returns 'struct' for assoc. arrays
    * returns false if no type exists, or not w/ the given namespace
    * else returns a string that is either a native php type, or 'struct'
    *
    * @param string $type name of defined type
    * @param string $ns namespace of type
    * @return mixed
    * @access public
    * @deprecated
    */
	function getPHPType($type,$ns){
		if(isset($this->typemap[$ns][$type])){
			//print "found type '$type' and ns $ns in typemap<br>";
			return $this->typemap[$ns][$type];
		} elseif(isset($this->complexTypes[$type])){
			//print "getting type '$type' and ns $ns from complexTypes array<br>";
			return $this->complexTypes[$type]['phpType'];
		}
		return false;
	}

	/**
    * returns an associative array of information about a given type
    * returns false if no type exists by the given name
    *
	*	For a complexType typeDef = array(
	*	'restrictionBase' => '',
	*	'phpType' => '',
	*	'compositor' => '(sequence|all)',
	*	'elements' => array(), // refs to elements array
	*	'attrs' => array() // refs to attributes array
	*	... and so on (see addComplexType)
	*	)
	*
	*   For simpleType or element, the array has different keys.
    *
    * @param string $type
    * @return mixed
    * @access public
    * @see addComplexType
    * @see addSimpleType
    * @see addElement
    */
	function getTypeDef($type){
		//$this->debug("in getTypeDef for type $type");
		if (substr($type, -1) == '^') {
			$is_element = 1;
			$type = substr($type, 0, -1);
		} else {
			$is_element = 0;
		}

		if((! $is_element) && isset($this->complexTypes[$type])){
			$this->xdebug("in getTypeDef, found complexType $type");
			return $this->complexTypes[$type];
		} elseif((! $is_element) && isset($this->simpleTypes[$type])){
			$this->xdebug("in getTypeDef, found simpleType $type");
			if (!isset($this->simpleTypes[$type]['phpType'])) {
				// get info for type to tack onto the simple type
				// TODO: can this ever really apply (i.e. what is a simpleType really?)
				$uqType = substr($this->simpleTypes[$type]['type'], strrpos($this->simpleTypes[$type]['type'], ':') + 1);
				$ns = substr($this->simpleTypes[$type]['type'], 0, strrpos($this->simpleTypes[$type]['type'], ':'));
				$etype = $this->getTypeDef($uqType);
				if ($etype) {
					$this->xdebug("in getTypeDef, found type for simpleType $type:");
					$this->xdebug($this->varDump($etype));
					if (isset($etype['phpType'])) {
						$this->simpleTypes[$type]['phpType'] = $etype['phpType'];
					}
					if (isset($etype['elements'])) {
						$this->simpleTypes[$type]['elements'] = $etype['elements'];
					}
				}
			}
			return $this->simpleTypes[$type];
		} elseif(isset($this->elements[$type])){
			$this->xdebug("in getTypeDef, found element $type");
			if (!isset($this->elements[$type]['phpType'])) {
				// get info for type to tack onto the element
				$uqType = substr($this->elements[$type]['type'], strrpos($this->elements[$type]['type'], ':') + 1);
				$ns = substr($this->elements[$type]['type'], 0, strrpos($this->elements[$type]['type'], ':'));
				$etype = $this->getTypeDef($uqType);
				if ($etype) {
					$this->xdebug("in getTypeDef, found type for element $type:");
					$this->xdebug($this->varDump($etype));
					if (isset($etype['phpType'])) {
						$this->elements[$type]['phpType'] = $etype['phpType'];
					}
					if (isset($etype['elements'])) {
						$this->elements[$type]['elements'] = $etype['elements'];
					}
					if (isset($etype['extensionBase'])) {
						$this->elements[$type]['extensionBase'] = $etype['extensionBase'];
					}
				} elseif ($ns == 'http://www.w3.org/2001/XMLSchema') {
					$this->xdebug("in getTypeDef, element $type is an XSD type");
					$this->elements[$type]['phpType'] = 'scalar';
				}
			}
			return $this->elements[$type];
		} elseif(isset($this->attributes[$type])){
			$this->xdebug("in getTypeDef, found attribute $type");
			return $this->attributes[$type];
		} elseif (preg_match('/_ContainedType$/', $type)) {
			$this->xdebug("in getTypeDef, have an untyped element $type");
			$typeDef['typeClass'] = 'simpleType';
			$typeDef['phpType'] = 'scalar';
			$typeDef['type'] = 'http://www.w3.org/2001/XMLSchema:string';
			return $typeDef;
		}
		$this->xdebug("in getTypeDef, did not find $type");
		return false;
	}

	/**
    * returns a sample serialization of a given type, or false if no type by the given name
    *
    * @param string $type name of type
    * @return mixed
    * @access public
    * @deprecated
    */
    function serializeTypeDef($type){
    	//print "in sTD() for type $type<br>";
	if($typeDef = $this->getTypeDef($type)){
		$str .= '<'.$type;
	    if(is_array($typeDef['attrs'])){
		foreach($typeDef['attrs'] as $attName => $data){
		    $str .= " $attName=\"{type = ".$data['type']."}\"";
		}
	    }
	    $str .= " xmlns=\"".$this->schema['targetNamespace']."\"";
	    if(count($typeDef['elements']) > 0){
		$str .= ">";
		foreach($typeDef['elements'] as $element => $eData){
		    $str .= $this->serializeTypeDef($element);
		}
		$str .= "</$type>";
	    } elseif($typeDef['typeClass'] == 'element') {
		$str .= "></$type>";
	    } else {
		$str .= "/>";
	    }
			return $str;
	}
    	return false;
    }

    /**
    * returns HTML form elements that allow a user
    * to enter values for creating an instance of the given type.
    *
    * @param string $name name for type instance
    * @param string $type name of type
    * @return string
    * @access public
    * @deprecated
	*/
	function typeToForm($name,$type){
		// get typedef
		if($typeDef = $this->getTypeDef($type)){
			// if struct
			if($typeDef['phpType'] == 'struct'){
				$buffer .= '<table>';
				foreach($typeDef['elements'] as $child => $childDef){
					$buffer .= "
					<tr><td align='right'>$childDef[name] (type: ".$this->getLocalPart($childDef['type'])."):</td>
					<td><input type='text' name='parameters[".$name."][$childDef[name]]'></td></tr>";
				}
				$buffer .= '</table>';
			// if array
			} elseif($typeDef['phpType'] == 'array'){
				$buffer .= '<table>';
				for($i=0;$i < 3; $i++){
					$buffer .= "
					<tr><td align='right'>array item (type: $typeDef[arrayType]):</td>
					<td><input type='text' name='parameters[".$name."][]'></td></tr>";
				}
				$buffer .= '</table>';
			// if scalar
			} else {
				$buffer .= "<input type='text' name='parameters[$name]'>";
			}
		} else {
			$buffer .= "<input type='text' name='parameters[$name]'>";
		}
		return $buffer;
	}
	
	/**
	* adds a complex type to the schema
	* 
	* example: array
	* 
	* addType(
	* 	'ArrayOfstring',
	* 	'complexType',
	* 	'array',
	* 	'',
	* 	'SOAP-ENC:Array',
	* 	array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'string[]'),
	* 	'xsd:string'
	* );
	* 
	* example: PHP associative array ( SOAP Struct )
	* 
	* addType(
	* 	'SOAPStruct',
	* 	'complexType',
	* 	'struct',
	* 	'all',
	* 	array('myVar'=> array('name'=>'myVar','type'=>'string')
	* );
	* 
	* @param name
	* @param typeClass (complexType|simpleType|attribute)
	* @param phpType: currently supported are array and struct (php assoc array)
	* @param compositor (all|sequence|choice)
	* @param restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
	* @param elements = array ( name = array(name=>'',type=>'') )
	* @param attrs = array(
	* 	array(
	*		'ref' => "http://schemas.xmlsoap.org/soap/encoding/:arrayType",
	*		"http://schemas.xmlsoap.org/wsdl/:arrayType" => "string[]"
	* 	)
	* )
	* @param arrayType: namespace:name (http://www.w3.org/2001/XMLSchema:string)
	* @access public
	* @see getTypeDef
	*/
	function addComplexType($name,$typeClass='complexType',$phpType='array',$compositor='',$restrictionBase='',$elements=array(),$attrs=array(),$arrayType=''){
		$this->complexTypes[$name] = array(
	    'name'		=> $name,
	    'typeClass'	=> $typeClass,
	    'phpType'	=> $phpType,
		'compositor'=> $compositor,
	    'restrictionBase' => $restrictionBase,
		'elements'	=> $elements,
	    'attrs'		=> $attrs,
	    'arrayType'	=> $arrayType
		);
		
		$this->xdebug("addComplexType $name:");
		$this->appendDebug($this->varDump($this->complexTypes[$name]));
	}
	
	/**
	* adds a simple type to the schema
	*
	* @param string $name
	* @param string $restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
	* @param string $typeClass (should always be simpleType)
	* @param string $phpType (should always be scalar)
	* @param array $enumeration array of values
	* @access public
	* @see nusoap_xmlschema
	* @see getTypeDef
	*/
	function addSimpleType($name, $restrictionBase='', $typeClass='simpleType', $phpType='scalar', $enumeration=array()) {
		$this->simpleTypes[$name] = array(
	    'name'			=> $name,
	    'typeClass'		=> $typeClass,
	    'phpType'		=> $phpType,
	    'type'			=> $restrictionBase,
	    'enumeration'	=> $enumeration
		);
		
		$this->xdebug("addSimpleType $name:");
		$this->appendDebug($this->varDump($this->simpleTypes[$name]));
	}

	/**
	* adds an element to the schema
	*
	* @param array $attrs attributes that must include name and type
	* @see nusoap_xmlschema
	* @access public
	*/
	function addElement($attrs) {
		if (! $this->getPrefix($attrs['type'])) {
			$attrs['type'] = $this->schemaTargetNamespace . ':' . $attrs['type'];
		}
		$this->elements[ $attrs['name'] ] = $attrs;
		$this->elements[ $attrs['name'] ]['typeClass'] = 'element';
		
		$this->xdebug("addElement " . $attrs['name']);
		$this->appendDebug($this->varDump($this->elements[ $attrs['name'] ]));
	}
}
?>
<?php
namespace com\linways\base\soap;

/**
 * Backward compatibility
 */
class soap_server extends nusoap_server {
}<?php
namespace com\linways\base\soap;
/*
 $Id: nusoap.php,v 1.123 2010/04/26 20:15:08 snichol Exp $
 
 NuSOAP - Web Services Toolkit for PHP
 
 Copyright (c) 2002 NuSphere Corporation
 
 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation; either
 version 2.1 of the License, or (at your option) any later version.
 
 This library is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 Lesser General Public License for more details.
 
 You should have received a copy of the GNU Lesser General Public
 License along with this library; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 The NuSOAP project home is:
 http://sourceforge.net/projects/nusoap/
 
 The primary support for NuSOAP is the Help forum on the project home page.
 
 If you have any questions or comments, please email:
 
 Dietrich Ayala
 dietrich@ganx4.com
 http://dietrich.ganx4.com/nusoap
 
 NuSphere Corporation
 http://www.nusphere.com
 
 */

/*
 *	Some of the standards implmented in whole or part by NuSOAP:
 *
 *	SOAP 1.1 (http://www.w3.org/TR/2000/NOTE-SOAP-20000508/)
 *	WSDL 1.1 (http://www.w3.org/TR/2001/NOTE-wsdl-20010315)
 *	SOAP Messages With Attachments (http://www.w3.org/TR/SOAP-attachments)
 *	XML 1.0 (http://www.w3.org/TR/2006/REC-xml-20060816/)
 *	Namespaces in XML 1.0 (http://www.w3.org/TR/2006/REC-xml-names-20060816/)
 *	XML Schema 1.0 (http://www.w3.org/TR/xmlschema-0/)
 *	RFC 2045 Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies
 *	RFC 2068 Hypertext Transfer Protocol -- HTTP/1.1
 *	RFC 2617 HTTP Authentication: Basic and Digest Access Authentication
 */

/* load classes

// necessary classes
require_once('class.soapclient.php');
require_once('class.soap_val.php');
require_once('class.soap_parser.php');
require_once('class.soap_fault.php');

// transport classes
require_once('class.soap_transport_http.php');

// optional add-on classes
require_once('class.xmlschema.php');
require_once('class.wsdl.php');

// server class
require_once('class.soap_server.php');*/

// class variable emulation
// cf. http://www.webkreator.com/php/techniques/php-static-class-variables.html
$GLOBALS['_transient']['static']['nusoap_base']['globalDebugLevel'] = 9;

/**
 *
 * nusoap_base
 *
 * @author   Dietrich Ayala <dietrich@ganx4.com>
 * @author   Scott Nichol <snichol@users.sourceforge.net>
 * @version  $Id: nusoap.php,v 1.123 2010/04/26 20:15:08 snichol Exp $
 * @access   public
 */
class nusoap_base {
    /**
     * Identification for HTTP headers.
     *
     * @var string
     * @access private
     */
    var $title = 'NuSOAP';
    /**
     * Version for HTTP headers.
     *
     * @var string
     * @access private
     */
    var $version = '0.9.5';
    /**
     * CVS revision for HTTP headers.
     *
     * @var string
     * @access private
     */
    var $revision = '$Revision: 1.123 $';
    /**
     * Current error string (manipulated by getError/setError)
     *
     * @var string
     * @access private
     */
    var $error_str = '';
    /**
     * Current debug string (manipulated by debug/appendDebug/clearDebug/getDebug/getDebugAsXMLComment)
     *
     * @var string
     * @access private
     */
    var $debug_str = '';
    /**
     * toggles automatic encoding of special characters as entities
     * (should always be true, I think)
     *
     * @var boolean
     * @access private
     */
    var $charencoding = true;
    /**
     * the debug level for this instance
     *
     * @var	integer
     * @access private
     */
    var $debugLevel;
    
    /**
     * set schema version
     *
     * @var      string
     * @access   public
     */
    var $XMLSchemaVersion = 'http://www.w3.org/2001/XMLSchema';
    
    /**
     * charset encoding for outgoing messages
     *
     * @var      string
     * @access   public
     */
    var $soap_defencoding = 'ISO-8859-1';
    //var $soap_defencoding = 'UTF-8';
    
    /**
     * namespaces in an array of prefix => uri
     *
     * this is "seeded" by a set of constants, but it may be altered by code
     *
     * @var      array
     * @access   public
     */
    var $namespaces = array(
        'SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/',
        'xsd' => 'http://www.w3.org/2001/XMLSchema',
        'xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
        'SOAP-ENC' => 'http://schemas.xmlsoap.org/soap/encoding/'
    );
    
    /**
     * namespaces used in the current context, e.g. during serialization
     *
     * @var      array
     * @access   private
     */
    var $usedNamespaces = array();
    
    /**
     * XML Schema types in an array of uri => (array of xml type => php type)
     * is this legacy yet?
     * no, this is used by the nusoap_xmlschema class to verify type => namespace mappings.
     * @var      array
     * @access   public
     */
    var $typemap = array(
        'http://www.w3.org/2001/XMLSchema' => array(
            'string'=>'string','boolean'=>'boolean','float'=>'double','double'=>'double','decimal'=>'double',
            'duration'=>'','dateTime'=>'string','time'=>'string','date'=>'string','gYearMonth'=>'',
            'gYear'=>'','gMonthDay'=>'','gDay'=>'','gMonth'=>'','hexBinary'=>'string','base64Binary'=>'string',
            // abstract "any" types
            'anyType'=>'string','anySimpleType'=>'string',
            // derived datatypes
            'normalizedString'=>'string','token'=>'string','language'=>'','NMTOKEN'=>'','NMTOKENS'=>'','Name'=>'','NCName'=>'','ID'=>'',
            'IDREF'=>'','IDREFS'=>'','ENTITY'=>'','ENTITIES'=>'','integer'=>'integer','nonPositiveInteger'=>'integer',
            'negativeInteger'=>'integer','long'=>'integer','int'=>'integer','short'=>'integer','byte'=>'integer','nonNegativeInteger'=>'integer',
            'unsignedLong'=>'','unsignedInt'=>'','unsignedShort'=>'','unsignedByte'=>'','positiveInteger'=>''),
        'http://www.w3.org/2000/10/XMLSchema' => array(
            'i4'=>'','int'=>'integer','boolean'=>'boolean','string'=>'string','double'=>'double',
            'float'=>'double','dateTime'=>'string',
            'timeInstant'=>'string','base64Binary'=>'string','base64'=>'string','ur-type'=>'array'),
        'http://www.w3.org/1999/XMLSchema' => array(
            'i4'=>'','int'=>'integer','boolean'=>'boolean','string'=>'string','double'=>'double',
            'float'=>'double','dateTime'=>'string',
            'timeInstant'=>'string','base64Binary'=>'string','base64'=>'string','ur-type'=>'array'),
        'http://soapinterop.org/xsd' => array('SOAPStruct'=>'struct'),
        'http://schemas.xmlsoap.org/soap/encoding/' => array('base64'=>'string','array'=>'array','Array'=>'array'),
        'http://xml.apache.org/xml-soap' => array('Map')
    );
    
    /**
     * XML entities to convert
     *
     * @var      array
     * @access   public
     * @deprecated
     * @see	expandEntities
     */
    var $xmlEntities = array('quot' => '"','amp' => '&',
        'lt' => '<','gt' => '>','apos' => "'");
    
    /**
     * constructor
     *
     * @access	public
     */
    function nusoap_base() {
        $this->debugLevel = $GLOBALS['_transient']['static']['nusoap_base']['globalDebugLevel'];
    }
    
    /**
     * gets the global debug level, which applies to future instances
     *
     * @return	integer	Debug level 0-9, where 0 turns off
     * @access	public
     */
    function getGlobalDebugLevel() {
        return $GLOBALS['_transient']['static']['nusoap_base']['globalDebugLevel'];
    }
    
    /**
     * sets the global debug level, which applies to future instances
     *
     * @param	int	$level	Debug level 0-9, where 0 turns off
     * @access	public
     */
    function setGlobalDebugLevel($level) {
        $GLOBALS['_transient']['static']['nusoap_base']['globalDebugLevel'] = $level;
    }
    
    /**
     * gets the debug level for this instance
     *
     * @return	int	Debug level 0-9, where 0 turns off
     * @access	public
     */
    function getDebugLevel() {
        return $this->debugLevel;
    }
    
    /**
     * sets the debug level for this instance
     *
     * @param	int	$level	Debug level 0-9, where 0 turns off
     * @access	public
     */
    function setDebugLevel($level) {
        $this->debugLevel = $level;
    }
    
    /**
     * adds debug data to the instance debug string with formatting
     *
     * @param    string $string debug data
     * @access   private
     */
    function debug($string){
        if ($this->debugLevel > 0) {
            $this->appendDebug($this->getmicrotime().' '.get_class($this).": $string\n");
        }
    }
    
    /**
     * adds debug data to the instance debug string without formatting
     *
     * @param    string $string debug data
     * @access   public
     */
    function appendDebug($string){
        if ($this->debugLevel > 0) {
            // it would be nice to use a memory stream here to use
            // memory more efficiently
            $this->debug_str .= $string;
        }
    }
    
    /**
     * clears the current debug data for this instance
     *
     * @access   public
     */
    function clearDebug() {
        // it would be nice to use a memory stream here to use
        // memory more efficiently
        $this->debug_str = '';
    }
    
    /**
     * gets the current debug data for this instance
     *
     * @return   debug data
     * @access   public
     */
    function &getDebug() {
        // it would be nice to use a memory stream here to use
        // memory more efficiently
        return $this->debug_str;
    }
    
    /**
     * gets the current debug data for this instance as an XML comment
     * this may change the contents of the debug data
     *
     * @return   debug data as an XML comment
     * @access   public
     */
    function &getDebugAsXMLComment() {
        // it would be nice to use a memory stream here to use
        // memory more efficiently
        while (strpos($this->debug_str, '--')) {
            $this->debug_str = str_replace('--', '- -', $this->debug_str);
        }
        $ret = "<!--\n" . $this->debug_str . "\n-->";
        return $ret;
    }
    
    /**
     * expands entities, e.g. changes '<' to '&lt;'.
     *
     * @param	string	$val	The string in which to expand entities.
     * @access	private
     */
    function expandEntities($val) {
        if ($this->charencoding) {
            $val = str_replace('&', '&amp;', $val);
            $val = str_replace("'", '&apos;', $val);
            $val = str_replace('"', '&quot;', $val);
            $val = str_replace('<', '&lt;', $val);
            $val = str_replace('>', '&gt;', $val);
        }
        return $val;
    }
    
    /**
     * returns error string if present
     *
     * @return   mixed error string or false
     * @access   public
     */
    function getError(){
        if($this->error_str != ''){
            return $this->error_str;
        }
        return false;
    }
    
    /**
     * sets error string
     *
     * @return   boolean $string error string
     * @access   private
     */
    function setError($str){
        $this->error_str = $str;
    }
    
    /**
     * detect if array is a simple array or a struct (associative array)
     *
     * @param	mixed	$val	The PHP array
     * @return	string	(arraySimple|arrayStruct)
     * @access	private
     */
    function isArraySimpleOrStruct($val) {
        $keyList = array_keys($val);
        foreach ($keyList as $keyListValue) {
            if (!is_int($keyListValue)) {
                return 'arrayStruct';
            }
        }
        return 'arraySimple';
    }
    
    /**
     * serializes PHP values in accordance w/ section 5. Type information is
     * not serialized if $use == 'literal'.
     *
     * @param	mixed	$val	The value to serialize
     * @param	string	$name	The name (local part) of the XML element
     * @param	string	$type	The XML schema type (local part) for the element
     * @param	string	$name_ns	The namespace for the name of the XML element
     * @param	string	$type_ns	The namespace for the type of the element
     * @param	array	$attributes	The attributes to serialize as name=>value pairs
     * @param	string	$use	The WSDL "use" (encoded|literal)
     * @param	boolean	$soapval	Whether this is called from soapval.
     * @return	string	The serialized element, possibly with child elements
     * @access	public
     */
    function serialize_val($val,$name=false,$type=false,$name_ns=false,$type_ns=false,$attributes=false,$use='encoded',$soapval=false) {
        $this->debug("in serialize_val: name=$name, type=$type, name_ns=$name_ns, type_ns=$type_ns, use=$use, soapval=$soapval");
        $this->appendDebug('value=' . $this->varDump($val));
        $this->appendDebug('attributes=' . $this->varDump($attributes));
        
        if (is_object($val) && get_class($val) == 'soapval' && (! $soapval)) {
            $this->debug("serialize_val: serialize soapval");
            $xml = $val->serialize($use);
            $this->appendDebug($val->getDebug());
            $val->clearDebug();
            $this->debug("serialize_val of soapval returning $xml");
            return $xml;
        }
        // force valid name if necessary
        if (is_numeric($name)) {
            $name = '__numeric_' . $name;
        } elseif (! $name) {
            $name = 'noname';
        }
        // if name has ns, add ns prefix to name
        $xmlns = '';
        if($name_ns){
            $prefix = 'nu'.rand(1000,9999);
            $name = $prefix.':'.$name;
            $xmlns .= " xmlns:$prefix=\"$name_ns\"";
        }
        // if type is prefixed, create type prefix
        if($type_ns != '' && $type_ns == $this->namespaces['xsd']){
            // need to fix this. shouldn't default to xsd if no ns specified
            // w/o checking against typemap
            $type_prefix = 'xsd';
        } elseif($type_ns){
            $type_prefix = 'ns'.rand(1000,9999);
            $xmlns .= " xmlns:$type_prefix=\"$type_ns\"";
        }
        // serialize attributes if present
        $atts = '';
        if($attributes){
            foreach($attributes as $k => $v){
                $atts .= " $k=\"".$this->expandEntities($v).'"';
            }
        }
        // serialize null value
        if (is_null($val)) {
            $this->debug("serialize_val: serialize null");
            if ($use == 'literal') {
                // TODO: depends on minOccurs
                $xml = "<$name$xmlns$atts/>";
                $this->debug("serialize_val returning $xml");
                return $xml;
            } else {
                if (isset($type) && isset($type_prefix)) {
                    $type_str = " xsi:type=\"$type_prefix:$type\"";
                } else {
                    $type_str = '';
                }
                $xml = "<$name$xmlns$type_str$atts xsi:nil=\"true\"/>";
                $this->debug("serialize_val returning $xml");
                return $xml;
            }
        }
        // serialize if an xsd built-in primitive type
        if($type != '' && isset($this->typemap[$this->XMLSchemaVersion][$type])){
            $this->debug("serialize_val: serialize xsd built-in primitive type");
            if (is_bool($val)) {
                if ($type == 'boolean') {
                    $val = $val ? 'true' : 'false';
                } elseif (! $val) {
                    $val = 0;
                }
            } else if (is_string($val)) {
                $val = $this->expandEntities($val);
            }
            if ($use == 'literal') {
                $xml = "<$name$xmlns$atts>$val</$name>";
                $this->debug("serialize_val returning $xml");
                return $xml;
            } else {
                $xml = "<$name$xmlns xsi:type=\"xsd:$type\"$atts>$val</$name>";
                $this->debug("serialize_val returning $xml");
                return $xml;
            }
        }
        // detect type and serialize
        $xml = '';
        switch(true) {
            case (is_bool($val) || $type == 'boolean'):
                $this->debug("serialize_val: serialize boolean");
                if ($type == 'boolean') {
                    $val = $val ? 'true' : 'false';
                } elseif (! $val) {
                    $val = 0;
                }
                if ($use == 'literal') {
                    $xml .= "<$name$xmlns$atts>$val</$name>";
                } else {
                    $xml .= "<$name$xmlns xsi:type=\"xsd:boolean\"$atts>$val</$name>";
                }
                break;
            case (is_int($val) || is_long($val) || $type == 'int'):
                $this->debug("serialize_val: serialize int");
                if ($use == 'literal') {
                    $xml .= "<$name$xmlns$atts>$val</$name>";
                } else {
                    $xml .= "<$name$xmlns xsi:type=\"xsd:int\"$atts>$val</$name>";
                }
                break;
            case (is_float($val)|| is_double($val) || $type == 'float'):
                $this->debug("serialize_val: serialize float");
                if ($use == 'literal') {
                    $xml .= "<$name$xmlns$atts>$val</$name>";
                } else {
                    $xml .= "<$name$xmlns xsi:type=\"xsd:float\"$atts>$val</$name>";
                }
                break;
            case (is_string($val) || $type == 'string'):
                $this->debug("serialize_val: serialize string");
                $val = $this->expandEntities($val);
                if ($use == 'literal') {
                    $xml .= "<$name$xmlns$atts>$val</$name>";
                } else {
                    $xml .= "<$name$xmlns xsi:type=\"xsd:string\"$atts>$val</$name>";
                }
                break;
            case is_object($val):
                $this->debug("serialize_val: serialize object");
                if (get_class($val) == 'soapval') {
                    $this->debug("serialize_val: serialize soapval object");
                    $pXml = $val->serialize($use);
                    $this->appendDebug($val->getDebug());
                    $val->clearDebug();
                } else {
                    if (! $name) {
                        $name = get_class($val);
                        $this->debug("In serialize_val, used class name $name as element name");
                    } else {
                        $this->debug("In serialize_val, do not override name $name for element name for class " . get_class($val));
                    }
                    foreach(get_object_vars($val) as $k => $v){
                        $pXml = isset($pXml) ? $pXml.$this->serialize_val($v,$k,false,false,false,false,$use) : $this->serialize_val($v,$k,false,false,false,false,$use);
                    }
                }
                if(isset($type) && isset($type_prefix)){
                    $type_str = " xsi:type=\"$type_prefix:$type\"";
                } else {
                    $type_str = '';
                }
                if ($use == 'literal') {
                    $xml .= "<$name$xmlns$atts>$pXml</$name>";
                } else {
                    $xml .= "<$name$xmlns$type_str$atts>$pXml</$name>";
                }
                break;
                break;
            case (is_array($val) || $type):
                // detect if struct or array
                $valueType = $this->isArraySimpleOrStruct($val);
                if($valueType=='arraySimple' || preg_match('/^ArrayOf/',$type)){
                    $this->debug("serialize_val: serialize array");
                    $i = 0;
                    if(is_array($val) && count($val)> 0){
                        foreach($val as $v){
                            if(is_object($v) && get_class($v) ==  'soapval'){
                                $tt_ns = $v->type_ns;
                                $tt = $v->type;
                            } elseif (is_array($v)) {
                                $tt = $this->isArraySimpleOrStruct($v);
                            } else {
                                $tt = gettype($v);
                            }
                            $array_types[$tt] = 1;
                            // TODO: for literal, the name should be $name
                            $xml .= $this->serialize_val($v,'item',false,false,false,false,$use);
                            ++$i;
                        }
                        if(count($array_types) > 1){
                            $array_typename = 'xsd:anyType';
                        } elseif(isset($tt) && isset($this->typemap[$this->XMLSchemaVersion][$tt])) {
                            if ($tt == 'integer') {
                                $tt = 'int';
                            }
                            $array_typename = 'xsd:'.$tt;
                        } elseif(isset($tt) && $tt == 'arraySimple'){
                            $array_typename = 'SOAP-ENC:Array';
                        } elseif(isset($tt) && $tt == 'arrayStruct'){
                            $array_typename = 'unnamed_struct_use_soapval';
                        } else {
                            // if type is prefixed, create type prefix
                            if ($tt_ns != '' && $tt_ns == $this->namespaces['xsd']){
                                $array_typename = 'xsd:' . $tt;
                            } elseif ($tt_ns) {
                                $tt_prefix = 'ns' . rand(1000, 9999);
                                $array_typename = "$tt_prefix:$tt";
                                $xmlns .= " xmlns:$tt_prefix=\"$tt_ns\"";
                            } else {
                                $array_typename = $tt;
                            }
                        }
                        $array_type = $i;
                        if ($use == 'literal') {
                            $type_str = '';
                        } else if (isset($type) && isset($type_prefix)) {
                            $type_str = " xsi:type=\"$type_prefix:$type\"";
                        } else {
                            $type_str = " xsi:type=\"SOAP-ENC:Array\" SOAP-ENC:arrayType=\"".$array_typename."[$array_type]\"";
                        }
                        // empty array
                    } else {
                        if ($use == 'literal') {
                            $type_str = '';
                        } else if (isset($type) && isset($type_prefix)) {
                            $type_str = " xsi:type=\"$type_prefix:$type\"";
                        } else {
                            $type_str = " xsi:type=\"SOAP-ENC:Array\" SOAP-ENC:arrayType=\"xsd:anyType[0]\"";
                        }
                    }
                    // TODO: for array in literal, there is no wrapper here
                    $xml = "<$name$xmlns$type_str$atts>".$xml."</$name>";
                } else {
                    // got a struct
                    $this->debug("serialize_val: serialize struct");
                    if(isset($type) && isset($type_prefix)){
                        $type_str = " xsi:type=\"$type_prefix:$type\"";
                    } else {
                        $type_str = '';
                    }
                    if ($use == 'literal') {
                        $xml .= "<$name$xmlns$atts>";
                    } else {
                        $xml .= "<$name$xmlns$type_str$atts>";
                    }
                    foreach($val as $k => $v){
                        // Apache Map
                        if ($type == 'Map' && $type_ns == 'http://xml.apache.org/xml-soap') {
                            $xml .= '<item>';
                            $xml .= $this->serialize_val($k,'key',false,false,false,false,$use);
                            $xml .= $this->serialize_val($v,'value',false,false,false,false,$use);
                            $xml .= '</item>';
                        } else {
                            $xml .= $this->serialize_val($v,$k,false,false,false,false,$use);
                        }
                    }
                    $xml .= "</$name>";
                }
                break;
            default:
                $this->debug("serialize_val: serialize unknown");
                $xml .= 'not detected, got '.gettype($val).' for '.$val;
                break;
        }
        $this->debug("serialize_val returning $xml");
        return $xml;
    }
    
    /**
     * serializes a message
     *
     * @param string $body the XML of the SOAP body
     * @param mixed $headers optional string of XML with SOAP header content, or array of soapval objects for SOAP headers, or associative array
     * @param array $namespaces optional the namespaces used in generating the body and headers
     * @param string $style optional (rpc|document)
     * @param string $use optional (encoded|literal)
     * @param string $encodingStyle optional (usually 'http://schemas.xmlsoap.org/soap/encoding/' for encoded)
     * @return string the message
     * @access public
     */
    function serializeEnvelope($body,$headers=false,$namespaces=array(),$style='rpc',$use='encoded',$encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'){
        // TODO: add an option to automatically run utf8_encode on $body and $headers
        // if $this->soap_defencoding is UTF-8.  Not doing this automatically allows
        // one to send arbitrary UTF-8 characters, not just characters that map to ISO-8859-1
        
        $this->debug("In serializeEnvelope length=" . strlen($body) . " body (max 1000 characters)=" . substr($body, 0, 1000) . " style=$style use=$use encodingStyle=$encodingStyle");
        $this->debug("headers:");
        $this->appendDebug($this->varDump($headers));
        $this->debug("namespaces:");
        $this->appendDebug($this->varDump($namespaces));
        
        // serialize namespaces
        $ns_string = '';
        foreach(array_merge($this->namespaces,$namespaces) as $k => $v){
            $ns_string .= " xmlns:$k=\"$v\"";
        }
        if($encodingStyle) {
            $ns_string = " SOAP-ENV:encodingStyle=\"$encodingStyle\"$ns_string";
        }
        
        // serialize headers
        if($headers){
            if (is_array($headers)) {
                $xml = '';
                foreach ($headers as $k => $v) {
                    if (is_object($v) && get_class($v) == 'soapval') {
                        $xml .= $this->serialize_val($v, false, false, false, false, false, $use);
                    } else {
                        $xml .= $this->serialize_val($v, $k, false, false, false, false, $use);
                    }
                }
                $headers = $xml;
                $this->debug("In serializeEnvelope, serialized array of headers to $headers");
            }
            $headers = "<SOAP-ENV:Header>".$headers."</SOAP-ENV:Header>";
        }
        // serialize envelope
        return
        '<?xml version="1.0" encoding="'.$this->soap_defencoding .'"?'.">".
        '<SOAP-ENV:Envelope'.$ns_string.">".
        $headers.
        "<SOAP-ENV:Body>".
        $body.
        "</SOAP-ENV:Body>".
        "</SOAP-ENV:Envelope>";
    }
    
    /**
     * formats a string to be inserted into an HTML stream
     *
     * @param string $str The string to format
     * @return string The formatted string
     * @access public
     * @deprecated
     */
    function formatDump($str){
        $str = htmlspecialchars($str);
        return nl2br($str);
    }
    
    /**
     * contracts (changes namespace to prefix) a qualified name
     *
     * @param    string $qname qname
     * @return	string contracted qname
     * @access   private
     */
    function contractQname($qname){
        // get element namespace
        //$this->xdebug("Contract $qname");
        if (strrpos($qname, ':')) {
            // get unqualified name
            $name = substr($qname, strrpos($qname, ':') + 1);
            // get ns
            $ns = substr($qname, 0, strrpos($qname, ':'));
            $p = $this->getPrefixFromNamespace($ns);
            if ($p) {
                return $p . ':' . $name;
            }
            return $qname;
        } else {
            return $qname;
        }
    }
    
    /**
     * expands (changes prefix to namespace) a qualified name
     *
     * @param    string $qname qname
     * @return	string expanded qname
     * @access   private
     */
    function expandQname($qname){
        // get element prefix
        if(strpos($qname,':') && !preg_match('/^http:\/\//',$qname)){
            // get unqualified name
            $name = substr(strstr($qname,':'),1);
            // get ns prefix
            $prefix = substr($qname,0,strpos($qname,':'));
            if(isset($this->namespaces[$prefix])){
                return $this->namespaces[$prefix].':'.$name;
            } else {
                return $qname;
            }
        } else {
            return $qname;
        }
    }
    
    /**
     * returns the local part of a prefixed string
     * returns the original string, if not prefixed
     *
     * @param string $str The prefixed string
     * @return string The local part
     * @access public
     */
    function getLocalPart($str){
        if($sstr = strrchr($str,':')){
            // get unqualified name
            return substr( $sstr, 1 );
        } else {
            return $str;
        }
    }
    
    /**
     * returns the prefix part of a prefixed string
     * returns false, if not prefixed
     *
     * @param string $str The prefixed string
     * @return mixed The prefix or false if there is no prefix
     * @access public
     */
    function getPrefix($str){
        if($pos = strrpos($str,':')){
            // get prefix
            return substr($str,0,$pos);
        }
        return false;
    }
    
    /**
     * pass it a prefix, it returns a namespace
     *
     * @param string $prefix The prefix
     * @return mixed The namespace, false if no namespace has the specified prefix
     * @access public
     */
    function getNamespaceFromPrefix($prefix){
        if (isset($this->namespaces[$prefix])) {
            return $this->namespaces[$prefix];
        }
        //$this->setError("No namespace registered for prefix '$prefix'");
        return false;
    }
    
    /**
     * returns the prefix for a given namespace (or prefix)
     * or false if no prefixes registered for the given namespace
     *
     * @param string $ns The namespace
     * @return mixed The prefix, false if the namespace has no prefixes
     * @access public
     */
    function getPrefixFromNamespace($ns) {
        foreach ($this->namespaces as $p => $n) {
            if ($ns == $n || $ns == $p) {
                $this->usedNamespaces[$p] = $n;
                return $p;
            }
        }
        return false;
    }
    
    /**
     * returns the time in ODBC canonical form with microseconds
     *
     * @return string The time in ODBC canonical form with microseconds
     * @access public
     */
    function getmicrotime() {
        if (function_exists('gettimeofday')) {
            $tod = gettimeofday();
            $sec = $tod['sec'];
            $usec = $tod['usec'];
        } else {
            $sec = time();
            $usec = 0;
        }
        return strftime('%Y-%m-%d %H:%M:%S', $sec) . '.' . sprintf('%06d', $usec);
    }
    
    /**
     * Returns a string with the output of var_dump
     *
     * @param mixed $data The variable to var_dump
     * @return string The output of var_dump
     * @access public
     */
    function varDump($data) {
        ob_start();
        var_dump($data);
        $ret_val = ob_get_contents();
        ob_end_clean();
        return $ret_val;
    }
    
    /**
     * represents the object as a string
     *
     * @return	string
     * @access   public
     */
    function __toString() {
        return $this->varDump($this);
    }
}

// XML Schema Datatype Helper Functions

//xsd:dateTime helpers

/**
 * convert unix timestamp to ISO 8601 compliant date string
 *
 * @param    int $timestamp Unix time stamp
 * @param	boolean $utc Whether the time stamp is UTC or local
 * @return	mixed ISO 8601 date string or false
 * @access   public
 */
function timestamp_to_iso8601($timestamp,$utc=true){
    $datestr = date('Y-m-d\TH:i:sO',$timestamp);
    $pos = strrpos($datestr, "+");
    if ($pos === FALSE) {
        $pos = strrpos($datestr, "-");
    }
    if ($pos !== FALSE) {
        if (strlen($datestr) == $pos + 5) {
            $datestr = substr($datestr, 0, $pos + 3) . ':' . substr($datestr, -2);
        }
    }
    if($utc){
        $pattern = '/'.
            '([0-9]{4})-'.	// centuries & years CCYY-
            '([0-9]{2})-'.	// months MM-
            '([0-9]{2})'.	// days DD
            'T'.			// separator T
            '([0-9]{2}):'.	// hours hh:
            '([0-9]{2}):'.	// minutes mm:
            '([0-9]{2})(\.[0-9]*)?'. // seconds ss.ss...
            '(Z|[+\-][0-9]{2}:?[0-9]{2})?'. // Z to indicate UTC, -/+HH:MM:SS.SS... for local tz's
            '/';
        
        if(preg_match($pattern,$datestr,$regs)){
            return sprintf('%04d-%02d-%02dT%02d:%02d:%02dZ',$regs[1],$regs[2],$regs[3],$regs[4],$regs[5],$regs[6]);
        }
        return false;
    } else {
        return $datestr;
    }
}

/**
 * convert ISO 8601 compliant date string to unix timestamp
 *
 * @param    string $datestr ISO 8601 compliant date string
 * @return	mixed Unix timestamp (int) or false
 * @access   public
 */
function iso8601_to_timestamp($datestr){
    $pattern = '/'.
        '([0-9]{4})-'.	// centuries & years CCYY-
        '([0-9]{2})-'.	// months MM-
        '([0-9]{2})'.	// days DD
        'T'.			// separator T
        '([0-9]{2}):'.	// hours hh:
        '([0-9]{2}):'.	// minutes mm:
        '([0-9]{2})(\.[0-9]+)?'. // seconds ss.ss...
        '(Z|[+\-][0-9]{2}:?[0-9]{2})?'. // Z to indicate UTC, -/+HH:MM:SS.SS... for local tz's
        '/';
    if(preg_match($pattern,$datestr,$regs)){
        // not utc
        if($regs[8] != 'Z'){
            $op = substr($regs[8],0,1);
            $h = substr($regs[8],1,2);
            $m = substr($regs[8],strlen($regs[8])-2,2);
            if($op == '-'){
                $regs[4] = $regs[4] + $h;
                $regs[5] = $regs[5] + $m;
            } elseif($op == '+'){
                $regs[4] = $regs[4] - $h;
                $regs[5] = $regs[5] - $m;
            }
        }
        return gmmktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
        //		return strtotime("$regs[1]-$regs[2]-$regs[3] $regs[4]:$regs[5]:$regs[6]Z");
    } else {
        return false;
    }
}

/**
 * sleeps some number of microseconds
 *
 * @param    string $usec the number of microseconds to sleep
 * @access   public
 * @deprecated
 */
function usleepWindows($usec)
{
    $start = gettimeofday();
    
    do
    {
        $stop = gettimeofday();
        $timePassed = 1000000 * ($stop['sec'] - $start['sec'])
        + $stop['usec'] - $start['usec'];
    }
    while ($timePassed < $usec);
}


?>
<?php
namespace com\linways\base\soap;
/**
 * Backward compatibility
 */
class soap_parser extends nusoap_parser {
}<?php
namespace com\linways\base\soap;


/**
 * Backward compatibility
 */
class soap_fault extends nusoap_fault {
}

<?php
namespace com\linways\base\soap;

/**
 * For creating serializable abstractions of native PHP types.  This class
 * allows element name/namespace, XSD type, and XML attributes to be
 * associated with a value.  This is extremely useful when WSDL is not
 * used, but is also useful when WSDL is used with polymorphic types, including
 * xsd:anyType and user-defined types.
 *
 * @author   Dietrich Ayala <dietrich@ganx4.com>
 * @version  $Id: nusoap.php,v 1.123 2010/04/26 20:15:08 snichol Exp $
 * @access   public
 */
class soapval extends nusoap_base {
    /**
     * The XML element name
     *
     * @var string
     * @access private
     */
    var $name;
    /**
     * The XML type name (string or false)
     *
     * @var mixed
     * @access private
     */
    var $type;
    /**
     * The PHP value
     *
     * @var mixed
     * @access private
     */
    var $value;
    /**
     * The XML element namespace (string or false)
     *
     * @var mixed
     * @access private
     */
    var $element_ns;
    /**
     * The XML type namespace (string or false)
     *
     * @var mixed
     * @access private
     */
    var $type_ns;
    /**
     * The XML element attributes (array or false)
     *
     * @var mixed
     * @access private
     */
    var $attributes;
    
    /**
     * constructor
     *
     * @param    string $name optional name
     * @param    mixed $type optional type name
     * @param	mixed $value optional value
     * @param	mixed $element_ns optional namespace of value
     * @param	mixed $type_ns optional namespace of type
     * @param	mixed $attributes associative array of attributes to add to element serialization
     * @access   public
     */
    function soapval($name='soapval',$type=false,$value=-1,$element_ns=false,$type_ns=false,$attributes=false) {
        parent::nusoap_base();
        $this->name = $name;
        $this->type = $type;
        $this->value = $value;
        $this->element_ns = $element_ns;
        $this->type_ns = $type_ns;
        $this->attributes = $attributes;
    }
    
    /**
     * return serialized value
     *
     * @param	string $use The WSDL use value (encoded|literal)
     * @return	string XML data
     * @access   public
     */
    function serialize($use='encoded') {
        return $this->serialize_val($this->value, $this->name, $this->type, $this->element_ns, $this->type_ns, $this->attributes, $use, true);
    }
    
    /**
     * decodes a soapval object into a PHP native type
     *
     * @return	mixed
     * @access   public
     */
    function decode(){
        return $this->value;
    }
}

<?php
namespace com\linways\base\soap;


/**
 * parses a WSDL file, allows access to it's data, other utility methods.
 * also builds WSDL structures programmatically.
 *
 * @author   Dietrich Ayala <dietrich@ganx4.com>
 * @author   Scott Nichol <snichol@users.sourceforge.net>
 * @version  $Id: nusoap.php,v 1.123 2010/04/26 20:15:08 snichol Exp $
 * @access public
 */
class wsdl extends nusoap_base {
    // URL or filename of the root of this WSDL
    var $wsdl;
    // define internal arrays of bindings, ports, operations, messages, etc.
    var $schemas = array();
    var $currentSchema;
    var $message = array();
    var $complexTypes = array();
    var $messages = array();
    var $currentMessage;
    var $currentOperation;
    var $portTypes = array();
    var $currentPortType;
    var $bindings = array();
    var $currentBinding;
    var $ports = array();
    var $currentPort;
    var $opData = array();
    var $status = '';
    var $documentation = false;
    var $endpoint = '';
    // array of wsdl docs to import
    var $import = array();
    // parser vars
    var $parser;
    var $position = 0;
    var $depth = 0;
    var $depth_array = array();
    // for getting wsdl
    var $proxyhost = '';
    var $proxyport = '';
    var $proxyusername = '';
    var $proxypassword = '';
    var $timeout = 0;
    var $response_timeout = 30;
    var $curl_options = array();	// User-specified cURL options
    var $use_curl = false;			// whether to always try to use cURL
    // for HTTP authentication
    var $username = '';				// Username for HTTP authentication
    var $password = '';				// Password for HTTP authentication
    var $authtype = '';				// Type of HTTP authentication
    var $certRequest = array();		// Certificate for HTTP SSL authentication
    
    /**
     * constructor
     *
     * @param string $wsdl WSDL document URL
     * @param string $proxyhost
     * @param string $proxyport
     * @param string $proxyusername
     * @param string $proxypassword
     * @param integer $timeout set the connection timeout
     * @param integer $response_timeout set the response timeout
     * @param array $curl_options user-specified cURL options
     * @param boolean $use_curl try to use cURL
     * @access public
     */
    function wsdl($wsdl = '',$proxyhost=false,$proxyport=false,$proxyusername=false,$proxypassword=false,$timeout=0,$response_timeout=30,$curl_options=null,$use_curl=false){
        parent::nusoap_base();
        $this->debug("ctor wsdl=$wsdl timeout=$timeout response_timeout=$response_timeout");
        $this->proxyhost = $proxyhost;
        $this->proxyport = $proxyport;
        $this->proxyusername = $proxyusername;
        $this->proxypassword = $proxypassword;
        $this->timeout = $timeout;
        $this->response_timeout = $response_timeout;
        if (is_array($curl_options))
            $this->curl_options = $curl_options;
            $this->use_curl = $use_curl;
            $this->fetchWSDL($wsdl);
    }
    
    /**
     * fetches the WSDL document and parses it
     *
     * @access public
     */
    function fetchWSDL($wsdl) {
        $this->debug("parse and process WSDL path=$wsdl");
        $this->wsdl = $wsdl;
        // parse wsdl file
        if ($this->wsdl != "") {
            $this->parseWSDL($this->wsdl);
        }
        // imports
        // TODO: handle imports more properly, grabbing them in-line and nesting them
        $imported_urls = array();
        $imported = 1;
        while ($imported > 0) {
            $imported = 0;
            // Schema imports
            foreach ($this->schemas as $ns => $list) {
                foreach ($list as $xs) {
                    $wsdlparts = parse_url($this->wsdl);	// this is bogusly simple!
                    foreach ($xs->imports as $ns2 => $list2) {
                        for ($ii = 0; $ii < count($list2); $ii++) {
                            if (! $list2[$ii]['loaded']) {
                                $this->schemas[$ns]->imports[$ns2][$ii]['loaded'] = true;
                                $url = $list2[$ii]['location'];
                                if ($url != '') {
                                    $urlparts = parse_url($url);
                                    if (!isset($urlparts['host'])) {
                                        $url = $wsdlparts['scheme'] . '://' . $wsdlparts['host'] . (isset($wsdlparts['port']) ? ':' .$wsdlparts['port'] : '') .
                                        substr($wsdlparts['path'],0,strrpos($wsdlparts['path'],'/') + 1) .$urlparts['path'];
                                    }
                                    if (! in_array($url, $imported_urls)) {
                                        $this->parseWSDL($url);
                                        $imported++;
                                        $imported_urls[] = $url;
                                    }
                                } else {
                                    $this->debug("Unexpected scenario: empty URL for unloaded import");
                                }
                            }
                        }
                    }
                }
            }
            // WSDL imports
            $wsdlparts = parse_url($this->wsdl);	// this is bogusly simple!
            foreach ($this->import as $ns => $list) {
                for ($ii = 0; $ii < count($list); $ii++) {
                    if (! $list[$ii]['loaded']) {
                        $this->import[$ns][$ii]['loaded'] = true;
                        $url = $list[$ii]['location'];
                        if ($url != '') {
                            $urlparts = parse_url($url);
                            if (!isset($urlparts['host'])) {
                                $url = $wsdlparts['scheme'] . '://' . $wsdlparts['host'] . (isset($wsdlparts['port']) ? ':' . $wsdlparts['port'] : '') .
                                substr($wsdlparts['path'],0,strrpos($wsdlparts['path'],'/') + 1) .$urlparts['path'];
                            }
                            if (! in_array($url, $imported_urls)) {
                                $this->parseWSDL($url);
                                $imported++;
                                $imported_urls[] = $url;
                            }
                        } else {
                            $this->debug("Unexpected scenario: empty URL for unloaded import");
                        }
                    }
                }
            }
        }
        // add new data to operation data
        foreach($this->bindings as $binding => $bindingData) {
            if (isset($bindingData['operations']) && is_array($bindingData['operations'])) {
                foreach($bindingData['operations'] as $operation => $data) {
                    $this->debug('post-parse data gathering for ' . $operation);
                    $this->bindings[$binding]['operations'][$operation]['input'] =
                    isset($this->bindings[$binding]['operations'][$operation]['input']) ?
                    array_merge($this->bindings[$binding]['operations'][$operation]['input'], $this->portTypes[ $bindingData['portType'] ][$operation]['input']) :
                    $this->portTypes[ $bindingData['portType'] ][$operation]['input'];
                    $this->bindings[$binding]['operations'][$operation]['output'] =
                    isset($this->bindings[$binding]['operations'][$operation]['output']) ?
                    array_merge($this->bindings[$binding]['operations'][$operation]['output'], $this->portTypes[ $bindingData['portType'] ][$operation]['output']) :
                    $this->portTypes[ $bindingData['portType'] ][$operation]['output'];
                    if(isset($this->messages[ $this->bindings[$binding]['operations'][$operation]['input']['message'] ])){
                        $this->bindings[$binding]['operations'][$operation]['input']['parts'] = $this->messages[ $this->bindings[$binding]['operations'][$operation]['input']['message'] ];
                    }
                    if(isset($this->messages[ $this->bindings[$binding]['operations'][$operation]['output']['message'] ])){
                        $this->bindings[$binding]['operations'][$operation]['output']['parts'] = $this->messages[ $this->bindings[$binding]['operations'][$operation]['output']['message'] ];
                    }
                    // Set operation style if necessary, but do not override one already provided
                    if (isset($bindingData['style']) && !isset($this->bindings[$binding]['operations'][$operation]['style'])) {
                        $this->bindings[$binding]['operations'][$operation]['style'] = $bindingData['style'];
                    }
                    $this->bindings[$binding]['operations'][$operation]['transport'] = isset($bindingData['transport']) ? $bindingData['transport'] : '';
                    $this->bindings[$binding]['operations'][$operation]['documentation'] = isset($this->portTypes[ $bindingData['portType'] ][$operation]['documentation']) ? $this->portTypes[ $bindingData['portType'] ][$operation]['documentation'] : '';
                    $this->bindings[$binding]['operations'][$operation]['endpoint'] = isset($bindingData['endpoint']) ? $bindingData['endpoint'] : '';
                }
            }
        }
    }
    
    /**
     * parses the wsdl document
     *
     * @param string $wsdl path or URL
     * @access private
     */
    function parseWSDL($wsdl = '') {
        $this->debug("parse WSDL at path=$wsdl");
        
        if ($wsdl == '') {
            $this->debug('no wsdl passed to parseWSDL()!!');
            $this->setError('no wsdl passed to parseWSDL()!!');
            return false;
        }
        
        // parse $wsdl for url format
        $wsdl_props = parse_url($wsdl);
        
        if (isset($wsdl_props['scheme']) && ($wsdl_props['scheme'] == 'http' || $wsdl_props['scheme'] == 'https')) {
            $this->debug('getting WSDL http(s) URL ' . $wsdl);
            // get wsdl
            $tr = new soap_transport_http($wsdl, $this->curl_options, $this->use_curl);
            $tr->request_method = 'GET';
            $tr->useSOAPAction = false;
            if($this->proxyhost && $this->proxyport){
                $tr->setProxy($this->proxyhost,$this->proxyport,$this->proxyusername,$this->proxypassword);
            }
            if ($this->authtype != '') {
                $tr->setCredentials($this->username, $this->password, $this->authtype, array(), $this->certRequest);
            }
            $tr->setEncoding('gzip, deflate');
            $wsdl_string = $tr->send('', $this->timeout, $this->response_timeout);
            //$this->debug("WSDL request\n" . $tr->outgoing_payload);
            //$this->debug("WSDL response\n" . $tr->incoming_payload);
            $this->appendDebug($tr->getDebug());
            // catch errors
            if($err = $tr->getError() ){
                $errstr = 'Getting ' . $wsdl . ' - HTTP ERROR: '.$err;
                $this->debug($errstr);
                $this->setError($errstr);
                unset($tr);
                return false;
            }
            unset($tr);
            $this->debug("got WSDL URL");
        } else {
            // $wsdl is not http(s), so treat it as a file URL or plain file path
            if (isset($wsdl_props['scheme']) && ($wsdl_props['scheme'] == 'file') && isset($wsdl_props['path'])) {
                $path = isset($wsdl_props['host']) ? ($wsdl_props['host'] . ':' . $wsdl_props['path']) : $wsdl_props['path'];
            } else {
                $path = $wsdl;
            }
            $this->debug('getting WSDL file ' . $path);
            if ($fp = @fopen($path, 'r')) {
                $wsdl_string = '';
                while ($data = fread($fp, 32768)) {
                    $wsdl_string .= $data;
                }
                fclose($fp);
            } else {
                $errstr = "Bad path to WSDL file $path";
                $this->debug($errstr);
                $this->setError($errstr);
                return false;
            }
        }
        $this->debug('Parse WSDL');
        // end new code added
        // Create an XML parser.
        $this->parser = xml_parser_create();
        // Set the options for parsing the XML data.
        // xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
        xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
        // Set the object for the parser.
        xml_set_object($this->parser, $this);
        // Set the element handlers for the parser.
        xml_set_element_handler($this->parser, 'start_element', 'end_element');
        xml_set_character_data_handler($this->parser, 'character_data');
        // Parse the XML file.
        if (!xml_parse($this->parser, $wsdl_string, true)) {
            // Display an error message.
            $errstr = sprintf(
                'XML error parsing WSDL from %s on line %d: %s',
                $wsdl,
                xml_get_current_line_number($this->parser),
                xml_error_string(xml_get_error_code($this->parser))
                );
            $this->debug($errstr);
            $this->debug("XML payload:\n" . $wsdl_string);
            $this->setError($errstr);
            return false;
        }
        // free the parser
        xml_parser_free($this->parser);
        $this->debug('Parsing WSDL done');
        // catch wsdl parse errors
        if($this->getError()){
            return false;
        }
        return true;
    }
    
    /**
     * start-element handler
     *
     * @param string $parser XML parser object
     * @param string $name element name
     * @param string $attrs associative array of attributes
     * @access private
     */
    function start_element($parser, $name, $attrs)
    {
        if ($this->status == 'schema') {
            $this->currentSchema->schemaStartElement($parser, $name, $attrs);
            $this->appendDebug($this->currentSchema->getDebug());
            $this->currentSchema->clearDebug();
        } elseif (preg_match('/schema$/', $name)) {
            $this->debug('Parsing WSDL schema');
            // $this->debug("startElement for $name ($attrs[name]). status = $this->status (".$this->getLocalPart($name).")");
            $this->status = 'schema';
            $this->currentSchema = new nusoap_xmlschema('', '', $this->namespaces);
            $this->currentSchema->schemaStartElement($parser, $name, $attrs);
            $this->appendDebug($this->currentSchema->getDebug());
            $this->currentSchema->clearDebug();
        } else {
            // position in the total number of elements, starting from 0
            $pos = $this->position++;
            $depth = $this->depth++;
            // set self as current value for this depth
            $this->depth_array[$depth] = $pos;
            $this->message[$pos] = array('cdata' => '');
            // process attributes
            if (count($attrs) > 0) {
                // register namespace declarations
                foreach($attrs as $k => $v) {
                    if (preg_match('/^xmlns/',$k)) {
                        if ($ns_prefix = substr(strrchr($k, ':'), 1)) {
                            $this->namespaces[$ns_prefix] = $v;
                        } else {
                            $this->namespaces['ns' . (count($this->namespaces) + 1)] = $v;
                        }
                        if ($v == 'http://www.w3.org/2001/XMLSchema' || $v == 'http://www.w3.org/1999/XMLSchema' || $v == 'http://www.w3.org/2000/10/XMLSchema') {
                            $this->XMLSchemaVersion = $v;
                            $this->namespaces['xsi'] = $v . '-instance';
                        }
                    }
                }
                // expand each attribute prefix to its namespace
                foreach($attrs as $k => $v) {
                    $k = strpos($k, ':') ? $this->expandQname($k) : $k;
                    if ($k != 'location' && $k != 'soapAction' && $k != 'namespace') {
                        $v = strpos($v, ':') ? $this->expandQname($v) : $v;
                    }
                    $eAttrs[$k] = $v;
                }
                $attrs = $eAttrs;
            } else {
                $attrs = array();
            }
            // get element prefix, namespace and name
            if (preg_match('/:/', $name)) {
                // get ns prefix
                $prefix = substr($name, 0, strpos($name, ':'));
                // get ns
                $namespace = isset($this->namespaces[$prefix]) ? $this->namespaces[$prefix] : '';
                // get unqualified name
                $name = substr(strstr($name, ':'), 1);
            }
            // process attributes, expanding any prefixes to namespaces
            // find status, register data
            switch ($this->status) {
                case 'message':
                    if ($name == 'part') {
                        if (isset($attrs['type'])) {
                            $this->debug("msg " . $this->currentMessage . ": found part (with type) $attrs[name]: " . implode(',', $attrs));
                            $this->messages[$this->currentMessage][$attrs['name']] = $attrs['type'];
                        }
                        if (isset($attrs['element'])) {
                            $this->debug("msg " . $this->currentMessage . ": found part (with element) $attrs[name]: " . implode(',', $attrs));
                            $this->messages[$this->currentMessage][$attrs['name']] = $attrs['element'] . '^';
                        }
                    }
                    break;
                case 'portType':
                    switch ($name) {
                        case 'operation':
                            $this->currentPortOperation = $attrs['name'];
                            $this->debug("portType $this->currentPortType operation: $this->currentPortOperation");
                            if (isset($attrs['parameterOrder'])) {
                                $this->portTypes[$this->currentPortType][$attrs['name']]['parameterOrder'] = $attrs['parameterOrder'];
                            }
                            break;
                        case 'documentation':
                            $this->documentation = true;
                            break;
                            // merge input/output data
                        default:
                            $m = isset($attrs['message']) ? $this->getLocalPart($attrs['message']) : '';
                            $this->portTypes[$this->currentPortType][$this->currentPortOperation][$name]['message'] = $m;
                            break;
                    }
                    break;
                case 'binding':
                    switch ($name) {
                        case 'binding':
                            // get ns prefix
                            if (isset($attrs['style'])) {
                                $this->bindings[$this->currentBinding]['prefix'] = $prefix;
                            }
                            $this->bindings[$this->currentBinding] = array_merge($this->bindings[$this->currentBinding], $attrs);
                            break;
                        case 'header':
                            $this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus]['headers'][] = $attrs;
                            break;
                        case 'operation':
                            if (isset($attrs['soapAction'])) {
                                $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['soapAction'] = $attrs['soapAction'];
                            }
                            if (isset($attrs['style'])) {
                                $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['style'] = $attrs['style'];
                            }
                            if (isset($attrs['name'])) {
                                $this->currentOperation = $attrs['name'];
                                $this->debug("current binding operation: $this->currentOperation");
                                $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['name'] = $attrs['name'];
                                $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['binding'] = $this->currentBinding;
                                $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['endpoint'] = isset($this->bindings[$this->currentBinding]['endpoint']) ? $this->bindings[$this->currentBinding]['endpoint'] : '';
                            }
                            break;
                        case 'input':
                            $this->opStatus = 'input';
                            break;
                        case 'output':
                            $this->opStatus = 'output';
                            break;
                        case 'body':
                            if (isset($this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus])) {
                                $this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus] = array_merge($this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus], $attrs);
                            } else {
                                $this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus] = $attrs;
                            }
                            break;
                    }
                    break;
                case 'service':
                    switch ($name) {
                        case 'port':
                            $this->currentPort = $attrs['name'];
                            $this->debug('current port: ' . $this->currentPort);
                            $this->ports[$this->currentPort]['binding'] = $this->getLocalPart($attrs['binding']);
                            
                            break;
                        case 'address':
                            $this->ports[$this->currentPort]['location'] = $attrs['location'];
                            $this->ports[$this->currentPort]['bindingType'] = $namespace;
                            $this->bindings[ $this->ports[$this->currentPort]['binding'] ]['bindingType'] = $namespace;
                            $this->bindings[ $this->ports[$this->currentPort]['binding'] ]['endpoint'] = $attrs['location'];
                            break;
                    }
                    break;
            }
            // set status
            switch ($name) {
                case 'import':
                    if (isset($attrs['location'])) {
                        $this->import[$attrs['namespace']][] = array('location' => $attrs['location'], 'loaded' => false);
                        $this->debug('parsing import ' . $attrs['namespace']. ' - ' . $attrs['location'] . ' (' . count($this->import[$attrs['namespace']]).')');
                    } else {
                        $this->import[$attrs['namespace']][] = array('location' => '', 'loaded' => true);
                        if (! $this->getPrefixFromNamespace($attrs['namespace'])) {
                            $this->namespaces['ns'.(count($this->namespaces)+1)] = $attrs['namespace'];
                        }
                        $this->debug('parsing import ' . $attrs['namespace']. ' - [no location] (' . count($this->import[$attrs['namespace']]).')');
                    }
                    break;
                    //wait for schema
                    //case 'types':
                    //	$this->status = 'schema';
                        //	break;
                    case 'message':
                        $this->status = 'message';
                        $this->messages[$attrs['name']] = array();
                        $this->currentMessage = $attrs['name'];
                        break;
                    case 'portType':
                        $this->status = 'portType';
                        $this->portTypes[$attrs['name']] = array();
                        $this->currentPortType = $attrs['name'];
                        break;
                    case "binding":
                        if (isset($attrs['name'])) {
                            // get binding name
                            if (strpos($attrs['name'], ':')) {
                                $this->currentBinding = $this->getLocalPart($attrs['name']);
                            } else {
                                $this->currentBinding = $attrs['name'];
                            }
                            $this->status = 'binding';
                            $this->bindings[$this->currentBinding]['portType'] = $this->getLocalPart($attrs['type']);
                            $this->debug("current binding: $this->currentBinding of portType: " . $attrs['type']);
                        }
                        break;
                    case 'service':
                        $this->serviceName = $attrs['name'];
                        $this->status = 'service';
                        $this->debug('current service: ' . $this->serviceName);
                        break;
                    case 'definitions':
                        foreach ($attrs as $name => $value) {
                            $this->wsdl_info[$name] = $value;
                        }
                        break;
            }
        }
    }
    
    /**
     * end-element handler
     *
     * @param string $parser XML parser object
     * @param string $name element name
     * @access private
     */
    function end_element($parser, $name){
        // unset schema status
        if (/*preg_match('/types$/', $name) ||*/ preg_match('/schema$/', $name)) {
            $this->status = "";
            $this->appendDebug($this->currentSchema->getDebug());
            $this->currentSchema->clearDebug();
            $this->schemas[$this->currentSchema->schemaTargetNamespace][] = $this->currentSchema;
            $this->debug('Parsing WSDL schema done');
        }
        if ($this->status == 'schema') {
            $this->currentSchema->schemaEndElement($parser, $name);
        } else {
            // bring depth down a notch
            $this->depth--;
        }
        // end documentation
        if ($this->documentation) {
            //TODO: track the node to which documentation should be assigned; it can be a part, message, etc.
            //$this->portTypes[$this->currentPortType][$this->currentPortOperation]['documentation'] = $this->documentation;
            $this->documentation = false;
        }
    }
    
    /**
     * element content handler
     *
     * @param string $parser XML parser object
     * @param string $data element content
     * @access private
     */
    function character_data($parser, $data)
    {
        $pos = isset($this->depth_array[$this->depth]) ? $this->depth_array[$this->depth] : 0;
        if (isset($this->message[$pos]['cdata'])) {
            $this->message[$pos]['cdata'] .= $data;
        }
        if ($this->documentation) {
            $this->documentation .= $data;
        }
    }
    
    /**
     * if authenticating, set user credentials here
     *
     * @param    string $username
     * @param    string $password
     * @param	string $authtype (basic|digest|certificate|ntlm)
     * @param	array $certRequest (keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, certpassword (optional), verifypeer (optional), verifyhost (optional): see corresponding options in cURL docs)
     * @access   public
     */
    function setCredentials($username, $password, $authtype = 'basic', $certRequest = array()) {
        $this->debug("setCredentials username=$username authtype=$authtype certRequest=");
        $this->appendDebug($this->varDump($certRequest));
        $this->username = $username;
        $this->password = $password;
        $this->authtype = $authtype;
        $this->certRequest = $certRequest;
    }
    
    function getBindingData($binding)
    {
        if (is_array($this->bindings[$binding])) {
            return $this->bindings[$binding];
        }
    }
    
    /**
     * returns an assoc array of operation names => operation data
     *
     * @param string $portName WSDL port name
     * @param string $bindingType eg: soap, smtp, dime (only soap and soap12 are currently supported)
     * @return array
     * @access public
     */
    function getOperations($portName = '', $bindingType = 'soap') {
        $ops = array();
        if ($bindingType == 'soap') {
            $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap/';
        } elseif ($bindingType == 'soap12') {
            $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap12/';
        } else {
            $this->debug("getOperations bindingType $bindingType may not be supported");
        }
        $this->debug("getOperations for port '$portName' bindingType $bindingType");
        // loop thru ports
        foreach($this->ports as $port => $portData) {
            $this->debug("getOperations checking port $port bindingType " . $portData['bindingType']);
            if ($portName == '' || $port == $portName) {
                // binding type of port matches parameter
                if ($portData['bindingType'] == $bindingType) {
                    $this->debug("getOperations found port $port bindingType $bindingType");
                    //$this->debug("port data: " . $this->varDump($portData));
                    //$this->debug("bindings: " . $this->varDump($this->bindings[ $portData['binding'] ]));
                    // merge bindings
                    if (isset($this->bindings[ $portData['binding'] ]['operations'])) {
                        $ops = array_merge ($ops, $this->bindings[ $portData['binding'] ]['operations']);
                    }
                }
            }
        }
        if (count($ops) == 0) {
            $this->debug("getOperations found no operations for port '$portName' bindingType $bindingType");
        }
        return $ops;
    }
    
    /**
     * returns an associative array of data necessary for calling an operation
     *
     * @param string $operation name of operation
     * @param string $bindingType type of binding eg: soap, soap12
     * @return array
     * @access public
     */
    function getOperationData($operation, $bindingType = 'soap')
    {
        if ($bindingType == 'soap') {
            $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap/';
        } elseif ($bindingType == 'soap12') {
            $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap12/';
        }
        // loop thru ports
        foreach($this->ports as $port => $portData) {
            // binding type of port matches parameter
            if ($portData['bindingType'] == $bindingType) {
                // get binding
                //foreach($this->bindings[ $portData['binding'] ]['operations'] as $bOperation => $opData) {
                foreach(array_keys($this->bindings[ $portData['binding'] ]['operations']) as $bOperation) {
                    // note that we could/should also check the namespace here
                    if ($operation == $bOperation) {
                        $opData = $this->bindings[ $portData['binding'] ]['operations'][$operation];
                        return $opData;
                    }
                }
                }
            }
        }
        
        /**
         * returns an associative array of data necessary for calling an operation
         *
         * @param string $soapAction soapAction for operation
         * @param string $bindingType type of binding eg: soap, soap12
         * @return array
         * @access public
         */
        function getOperationDataForSoapAction($soapAction, $bindingType = 'soap') {
            if ($bindingType == 'soap') {
                $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap/';
            } elseif ($bindingType == 'soap12') {
                $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap12/';
            }
            // loop thru ports
            foreach($this->ports as $port => $portData) {
                // binding type of port matches parameter
                if ($portData['bindingType'] == $bindingType) {
                    // loop through operations for the binding
                    foreach ($this->bindings[ $portData['binding'] ]['operations'] as $bOperation => $opData) {
                        if ($opData['soapAction'] == $soapAction) {
                            return $opData;
                        }
                    }
                }
            }
        }
        
        /**
         * returns an array of information about a given type
         * returns false if no type exists by the given name
         *
         *	 typeDef = array(
         *	 'elements' => array(), // refs to elements array
         *	'restrictionBase' => '',
         *	'phpType' => '',
         *	'order' => '(sequence|all)',
         *	'attrs' => array() // refs to attributes array
         *	)
         *
         * @param string $type the type
         * @param string $ns namespace (not prefix) of the type
         * @return mixed
         * @access public
         * @see nusoap_xmlschema
         */
        function getTypeDef($type, $ns) {
            $this->debug("in getTypeDef: type=$type, ns=$ns");
            if ((! $ns) && isset($this->namespaces['tns'])) {
                $ns = $this->namespaces['tns'];
                $this->debug("in getTypeDef: type namespace forced to $ns");
            }
            if (!isset($this->schemas[$ns])) {
                foreach ($this->schemas as $ns0 => $schema0) {
                    if (strcasecmp($ns, $ns0) == 0) {
                        $this->debug("in getTypeDef: replacing schema namespace $ns with $ns0");
                        $ns = $ns0;
                        break;
                    }
                }
            }
            if (isset($this->schemas[$ns])) {
                $this->debug("in getTypeDef: have schema for namespace $ns");
                for ($i = 0; $i < count($this->schemas[$ns]); $i++) {
                    $xs = &$this->schemas[$ns][$i];
                    $t = $xs->getTypeDef($type);
                    $this->appendDebug($xs->getDebug());
                    $xs->clearDebug();
                    if ($t) {
                        $this->debug("in getTypeDef: found type $type");
                        if (!isset($t['phpType'])) {
                            // get info for type to tack onto the element
                            $uqType = substr($t['type'], strrpos($t['type'], ':') + 1);
                            $ns = substr($t['type'], 0, strrpos($t['type'], ':'));
                            $etype = $this->getTypeDef($uqType, $ns);
                            if ($etype) {
                                $this->debug("found type for [element] $type:");
                                $this->debug($this->varDump($etype));
                                if (isset($etype['phpType'])) {
                                    $t['phpType'] = $etype['phpType'];
                                }
                                if (isset($etype['elements'])) {
                                    $t['elements'] = $etype['elements'];
                                }
                                if (isset($etype['attrs'])) {
                                    $t['attrs'] = $etype['attrs'];
                                }
                            } else {
                                $this->debug("did not find type for [element] $type");
                            }
                        }
                        return $t;
                    }
                }
                $this->debug("in getTypeDef: did not find type $type");
            } else {
                $this->debug("in getTypeDef: do not have schema for namespace $ns");
            }
            return false;
        }
        
        /**
         * prints html description of services
         *
         * @access private
         */
        function webDescription(){
            global $HTTP_SERVER_VARS;
            
            if (isset($_SERVER)) {
                $PHP_SELF = $_SERVER['PHP_SELF'];
            } elseif (isset($HTTP_SERVER_VARS)) {
                $PHP_SELF = $HTTP_SERVER_VARS['PHP_SELF'];
            } else {
                $this->setError("Neither _SERVER nor HTTP_SERVER_VARS is available");
            }
            
            $b = '
		<html><head><title>NuSOAP: '.$this->serviceName.'</title>
		<style type="text/css">
		    body    { font-family: arial; color: #000000; background-color: #ffffff; margin: 0px 0px 0px 0px; }
		    p       { font-family: arial; color: #000000; margin-top: 0px; margin-bottom: 12px; }
		    pre { background-color: silver; padding: 5px; font-family: Courier New; font-size: x-small; color: #000000;}
		    ul      { margin-top: 10px; margin-left: 20px; }
		    li      { list-style-type: none; margin-top: 10px; color: #000000; }
		    .content{
			margin-left: 0px; padding-bottom: 2em; }
		    .nav {
			padding-top: 10px; padding-bottom: 10px; padding-left: 15px; font-size: .70em;
			margin-top: 10px; margin-left: 0px; color: #000000;
			background-color: #ccccff; width: 20%; margin-left: 20px; margin-top: 20px; }
		    .title {
			font-family: arial; font-size: 26px; color: #ffffff;
			background-color: #999999; width: 100%;
			margin-left: 0px; margin-right: 0px;
			padding-top: 10px; padding-bottom: 10px;}
		    .hidden {
			position: absolute; visibility: hidden; z-index: 200; left: 250px; top: 100px;
			font-family: arial; overflow: hidden; width: 600;
			padding: 20px; font-size: 10px; background-color: #999999;
			layer-background-color:#FFFFFF; }
		    a,a:active  { color: charcoal; font-weight: bold; }
		    a:visited   { color: #666666; font-weight: bold; }
		    a:hover     { color: cc3300; font-weight: bold; }
		</style>
		<script language="JavaScript" type="text/javascript">
		<!--
		// POP-UP CAPTIONS...
		function lib_bwcheck(){ //Browsercheck (needed)
		    this.ver=navigator.appVersion
		    this.agent=navigator.userAgent
		    this.dom=document.getElementById?1:0
		    this.opera5=this.agent.indexOf("Opera 5")>-1
		    this.ie5=(this.ver.indexOf("MSIE 5")>-1 && this.dom && !this.opera5)?1:0;
		    this.ie6=(this.ver.indexOf("MSIE 6")>-1 && this.dom && !this.opera5)?1:0;
		    this.ie4=(document.all && !this.dom && !this.opera5)?1:0;
		    this.ie=this.ie4||this.ie5||this.ie6
		    this.mac=this.agent.indexOf("Mac")>-1
		    this.ns6=(this.dom && parseInt(this.ver) >= 5) ?1:0;
		    this.ns4=(document.layers && !this.dom)?1:0;
		    this.bw=(this.ie6 || this.ie5 || this.ie4 || this.ns4 || this.ns6 || this.opera5)
		    return this
		}
		var bw = new lib_bwcheck()
		//Makes crossbrowser object.
		function makeObj(obj){
		    this.evnt=bw.dom? document.getElementById(obj):bw.ie4?document.all[obj]:bw.ns4?document.layers[obj]:0;
		    if(!this.evnt) return false
		    this.css=bw.dom||bw.ie4?this.evnt.style:bw.ns4?this.evnt:0;
		    this.wref=bw.dom||bw.ie4?this.evnt:bw.ns4?this.css.document:0;
		    this.writeIt=b_writeIt;
		    return this
		}
		// A unit of measure that will be added when setting the position of a layer.
		//var px = bw.ns4||window.opera?"":"px";
		function b_writeIt(text){
		    if (bw.ns4){this.wref.write(text);this.wref.close()}
		    else this.wref.innerHTML = text
		}
		//Shows the messages
		var oDesc;
		function popup(divid){
		    if(oDesc = new makeObj(divid)){
			oDesc.css.visibility = "visible"
		    }
		}
		function popout(){ // Hides message
		    if(oDesc) oDesc.css.visibility = "hidden"
		}
		//-->
		</script>
		</head>
		<body>
		<div class=content>
			<br><br>
			<div class=title>'.$this->serviceName.'</div>
			<div class=nav>
				<p>View the <a href="'.$PHP_SELF.'?wsdl">WSDL</a> for the service.
				Click on an operation name to view it&apos;s details.</p>
				<ul>';
            foreach($this->getOperations() as $op => $data){
                $b .= "<li><a href='#' onclick=\"popout();popup('$op')\">$op</a></li>";
                // create hidden div
                $b .= "<div id='$op' class='hidden'>
				    <a href='#' onclick='popout()'><font color='#ffffff'>Close</font></a><br><br>";
                foreach($data as $donnie => $marie){ // loop through opdata
                    if($donnie == 'input' || $donnie == 'output'){ // show input/output data
                        $b .= "<font color='white'>".ucfirst($donnie).':</font><br>';
                        foreach($marie as $captain => $tenille){ // loop through data
                            if($captain == 'parts'){ // loop thru parts
                                $b .= "&nbsp;&nbsp;$captain:<br>";
                                //if(is_array($tenille)){
                                foreach($tenille as $joanie => $chachi){
                                    $b .= "&nbsp;&nbsp;&nbsp;&nbsp;$joanie: $chachi<br>";
                                }
                                //}
                            } else {
                                $b .= "&nbsp;&nbsp;$captain: $tenille<br>";
                            }
                        }
                    } else {
                        $b .= "<font color='white'>".ucfirst($donnie).":</font> $marie<br>";
                    }
                }
                $b .= '</div>';
            }
            $b .= '
				<ul>
			</div>
		</div></body></html>';
            return $b;
        }
        
        /**
         * serialize the parsed wsdl
         *
         * @param mixed $debug whether to put debug=1 in endpoint URL
         * @return string serialization of WSDL
         * @access public
         */
        function serialize($debug = 0)
        {
            $xml = '<?xml version="1.0" encoding="ISO-8859-1"?>';
            $xml .= "\n<definitions";
            foreach($this->namespaces as $k => $v) {
                $xml .= " xmlns:$k=\"$v\"";
            }
            // 10.9.02 - add poulter fix for wsdl and tns declarations
            if (isset($this->namespaces['wsdl'])) {
                $xml .= " xmlns=\"" . $this->namespaces['wsdl'] . "\"";
            }
            if (isset($this->namespaces['tns'])) {
                $xml .= " targetNamespace=\"" . $this->namespaces['tns'] . "\"";
            }
            $xml .= '>';
            // imports
            if (sizeof($this->import) > 0) {
                foreach($this->import as $ns => $list) {
                    foreach ($list as $ii) {
                        if ($ii['location'] != '') {
                            $xml .= '<import location="' . $ii['location'] . '" namespace="' . $ns . '" />';
                        } else {
                            $xml .= '<import namespace="' . $ns . '" />';
                        }
                    }
                }
            }
            // types
            if (count($this->schemas)>=1) {
                $xml .= "\n<types>\n";
                foreach ($this->schemas as $ns => $list) {
                    foreach ($list as $xs) {
                        $xml .= $xs->serializeSchema();
                    }
                }
                $xml .= '</types>';
            }
            // messages
            if (count($this->messages) >= 1) {
                foreach($this->messages as $msgName => $msgParts) {
                    $xml .= "\n<message name=\"" . $msgName . '">';
                    if(is_array($msgParts)){
                        foreach($msgParts as $partName => $partType) {
                            // print 'serializing '.$partType.', sv: '.$this->XMLSchemaVersion.'<br>';
                            if (strpos($partType, ':')) {
                                $typePrefix = $this->getPrefixFromNamespace($this->getPrefix($partType));
                            } elseif (isset($this->typemap[$this->namespaces['xsd']][$partType])) {
                                // print 'checking typemap: '.$this->XMLSchemaVersion.'<br>';
                                $typePrefix = 'xsd';
                            } else {
                                foreach($this->typemap as $ns => $types) {
                                    if (isset($types[$partType])) {
                                        $typePrefix = $this->getPrefixFromNamespace($ns);
                                    }
                                }
                                if (!isset($typePrefix)) {
                                    die("$partType has no namespace!");
                                }
                            }
                            $ns = $this->getNamespaceFromPrefix($typePrefix);
                            $localPart = $this->getLocalPart($partType);
                            $typeDef = $this->getTypeDef($localPart, $ns);
                            if ($typeDef['typeClass'] == 'element') {
                                $elementortype = 'element';
                                if (substr($localPart, -1) == '^') {
                                    $localPart = substr($localPart, 0, -1);
                                }
                            } else {
                                $elementortype = 'type';
                            }
                            $xml .= "\n" . '  <part name="' . $partName . '" ' . $elementortype . '="' . $typePrefix . ':' . $localPart . '" />';
                        }
                    }
                    $xml .= '</message>';
                }
            }
            // bindings & porttypes
            if (count($this->bindings) >= 1) {
                $binding_xml = '';
                $portType_xml = '';
                foreach($this->bindings as $bindingName => $attrs) {
                    $binding_xml .= "\n<binding name=\"" . $bindingName . '" type="tns:' . $attrs['portType'] . '">';
                    $binding_xml .= "\n" . '  <soap:binding style="' . $attrs['style'] . '" transport="' . $attrs['transport'] . '"/>';
                    $portType_xml .= "\n<portType name=\"" . $attrs['portType'] . '">';
                    foreach($attrs['operations'] as $opName => $opParts) {
                        $binding_xml .= "\n" . '  <operation name="' . $opName . '">';
                        $binding_xml .= "\n" . '    <soap:operation soapAction="' . $opParts['soapAction'] . '" style="'. $opParts['style'] . '"/>';
                        if (isset($opParts['input']['encodingStyle']) && $opParts['input']['encodingStyle'] != '') {
                            $enc_style = ' encodingStyle="' . $opParts['input']['encodingStyle'] . '"';
                        } else {
                            $enc_style = '';
                        }
                        $binding_xml .= "\n" . '    <input><soap:body use="' . $opParts['input']['use'] . '" namespace="' . $opParts['input']['namespace'] . '"' . $enc_style . '/></input>';
                        if (isset($opParts['output']['encodingStyle']) && $opParts['output']['encodingStyle'] != '') {
                            $enc_style = ' encodingStyle="' . $opParts['output']['encodingStyle'] . '"';
                        } else {
                            $enc_style = '';
                        }
                        $binding_xml .= "\n" . '    <output><soap:body use="' . $opParts['output']['use'] . '" namespace="' . $opParts['output']['namespace'] . '"' . $enc_style . '/></output>';
                        $binding_xml .= "\n" . '  </operation>';
                        $portType_xml .= "\n" . '  <operation name="' . $opParts['name'] . '"';
                        if (isset($opParts['parameterOrder'])) {
                            $portType_xml .= ' parameterOrder="' . $opParts['parameterOrder'] . '"';
                        }
                        $portType_xml .= '>';
                        if(isset($opParts['documentation']) && $opParts['documentation'] != '') {
                            $portType_xml .= "\n" . '    <documentation>' . htmlspecialchars($opParts['documentation']) . '</documentation>';
                        }
                        $portType_xml .= "\n" . '    <input message="tns:' . $opParts['input']['message'] . '"/>';
                        $portType_xml .= "\n" . '    <output message="tns:' . $opParts['output']['message'] . '"/>';
                        $portType_xml .= "\n" . '  </operation>';
                    }
                    $portType_xml .= "\n" . '</portType>';
                    $binding_xml .= "\n" . '</binding>';
                }
                $xml .= $portType_xml . $binding_xml;
            }
            // services
            $xml .= "\n<service name=\"" . $this->serviceName . '">';
            if (count($this->ports) >= 1) {
                foreach($this->ports as $pName => $attrs) {
                    $xml .= "\n" . '  <port name="' . $pName . '" binding="tns:' . $attrs['binding'] . '">';
                    $xml .= "\n" . '    <soap:address location="' . $attrs['location'] . ($debug ? '?debug=1' : '') . '"/>';
                    $xml .= "\n" . '  </port>';
                }
            }
            $xml .= "\n" . '</service>';
            return $xml . "\n</definitions>";
        }
        
        /**
         * determine whether a set of parameters are unwrapped
         * when they are expect to be wrapped, Microsoft-style.
         *
         * @param string $type the type (element name) of the wrapper
         * @param array $parameters the parameter values for the SOAP call
         * @return boolean whether they parameters are unwrapped (and should be wrapped)
         * @access private
         */
        function parametersMatchWrapped($type, &$parameters) {
            $this->debug("in parametersMatchWrapped type=$type, parameters=");
            $this->appendDebug($this->varDump($parameters));
            
            // split type into namespace:unqualified-type
            if (strpos($type, ':')) {
                $uqType = substr($type, strrpos($type, ':') + 1);
                $ns = substr($type, 0, strrpos($type, ':'));
                $this->debug("in parametersMatchWrapped: got a prefixed type: $uqType, $ns");
                if ($this->getNamespaceFromPrefix($ns)) {
                    $ns = $this->getNamespaceFromPrefix($ns);
                    $this->debug("in parametersMatchWrapped: expanded prefixed type: $uqType, $ns");
                }
            } else {
                // TODO: should the type be compared to types in XSD, and the namespace
                // set to XSD if the type matches?
                $this->debug("in parametersMatchWrapped: No namespace for type $type");
                $ns = '';
                $uqType = $type;
            }
            
            // get the type information
            if (!$typeDef = $this->getTypeDef($uqType, $ns)) {
                $this->debug("in parametersMatchWrapped: $type ($uqType) is not a supported type.");
                return false;
            }
            $this->debug("in parametersMatchWrapped: found typeDef=");
            $this->appendDebug($this->varDump($typeDef));
            if (substr($uqType, -1) == '^') {
                $uqType = substr($uqType, 0, -1);
            }
            $phpType = $typeDef['phpType'];
            $arrayType = (isset($typeDef['arrayType']) ? $typeDef['arrayType'] : '');
            $this->debug("in parametersMatchWrapped: uqType: $uqType, ns: $ns, phptype: $phpType, arrayType: $arrayType");
            
            // we expect a complexType or element of complexType
            if ($phpType != 'struct') {
                $this->debug("in parametersMatchWrapped: not a struct");
                return false;
            }
            
            // see whether the parameter names match the elements
            if (isset($typeDef['elements']) && is_array($typeDef['elements'])) {
                $elements = 0;
                $matches = 0;
                foreach ($typeDef['elements'] as $name => $attrs) {
                    if (isset($parameters[$name])) {
                        $this->debug("in parametersMatchWrapped: have parameter named $name");
                        $matches++;
                    } else {
                        $this->debug("in parametersMatchWrapped: do not have parameter named $name");
                    }
                    $elements++;
                }
                
                $this->debug("in parametersMatchWrapped: $matches parameter names match $elements wrapped parameter names");
                if ($matches == 0) {
                    return false;
                }
                return true;
            }
            
            // since there are no elements for the type, if the user passed no
            // parameters, the parameters match wrapped.
            $this->debug("in parametersMatchWrapped: no elements type $ns:$uqType");
            return count($parameters) == 0;
        }
        
        /**
         * serialize PHP values according to a WSDL message definition
         * contrary to the method name, this is not limited to RPC
         *
         * TODO
         * - multi-ref serialization
         * - validate PHP values against type definitions, return errors if invalid
         *
         * @param string $operation operation name
         * @param string $direction (input|output)
         * @param mixed $parameters parameter value(s)
         * @param string $bindingType (soap|soap12)
         * @return mixed parameters serialized as XML or false on error (e.g. operation not found)
         * @access public
         */
        function serializeRPCParameters($operation, $direction, $parameters, $bindingType = 'soap') {
            $this->debug("in serializeRPCParameters: operation=$operation, direction=$direction, XMLSchemaVersion=$this->XMLSchemaVersion, bindingType=$bindingType");
            $this->appendDebug('parameters=' . $this->varDump($parameters));
            
            if ($direction != 'input' && $direction != 'output') {
                $this->debug('The value of the \$direction argument needs to be either "input" or "output"');
                $this->setError('The value of the \$direction argument needs to be either "input" or "output"');
                return false;
            }
            if (!$opData = $this->getOperationData($operation, $bindingType)) {
                $this->debug('Unable to retrieve WSDL data for operation: ' . $operation . ' bindingType: ' . $bindingType);
                $this->setError('Unable to retrieve WSDL data for operation: ' . $operation . ' bindingType: ' . $bindingType);
                return false;
            }
            $this->debug('in serializeRPCParameters: opData:');
            $this->appendDebug($this->varDump($opData));
            
            // Get encoding style for output and set to current
            $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
            if(($direction == 'input') && isset($opData['output']['encodingStyle']) && ($opData['output']['encodingStyle'] != $encodingStyle)) {
                $encodingStyle = $opData['output']['encodingStyle'];
                $enc_style = $encodingStyle;
            }
            
            // set input params
            $xml = '';
            if (isset($opData[$direction]['parts']) && sizeof($opData[$direction]['parts']) > 0) {
                $parts = &$opData[$direction]['parts'];
                $part_count = sizeof($parts);
                $style = $opData['style'];
                $use = $opData[$direction]['use'];
                $this->debug("have $part_count part(s) to serialize using $style/$use");
                if (is_array($parameters)) {
                    $parametersArrayType = $this->isArraySimpleOrStruct($parameters);
                    $parameter_count = count($parameters);
                    $this->debug("have $parameter_count parameter(s) provided as $parametersArrayType to serialize");
                    // check for Microsoft-style wrapped parameters
                    if ($style == 'document' && $use == 'literal' && $part_count == 1 && isset($parts['parameters'])) {
                        $this->debug('check whether the caller has wrapped the parameters');
                        if ($direction == 'output' && $parametersArrayType == 'arraySimple' && $parameter_count == 1) {
                            // TODO: consider checking here for double-wrapping, when
                            // service function wraps, then NuSOAP wraps again
                            $this->debug("change simple array to associative with 'parameters' element");
                            $parameters['parameters'] = $parameters[0];
                            unset($parameters[0]);
                        }
                        if (($parametersArrayType == 'arrayStruct' || $parameter_count == 0) && !isset($parameters['parameters'])) {
                            $this->debug('check whether caller\'s parameters match the wrapped ones');
                            if ($this->parametersMatchWrapped($parts['parameters'], $parameters)) {
                                $this->debug('wrap the parameters for the caller');
                                $parameters = array('parameters' => $parameters);
                                $parameter_count = 1;
                            }
                        }
                    }
                    foreach ($parts as $name => $type) {
                        $this->debug("serializing part $name of type $type");
                        // Track encoding style
                        if (isset($opData[$direction]['encodingStyle']) && $encodingStyle != $opData[$direction]['encodingStyle']) {
                            $encodingStyle = $opData[$direction]['encodingStyle'];
                            $enc_style = $encodingStyle;
                        } else {
                            $enc_style = false;
                        }
                        // NOTE: add error handling here
                        // if serializeType returns false, then catch global error and fault
                        if ($parametersArrayType == 'arraySimple') {
                            $p = array_shift($parameters);
                            $this->debug('calling serializeType w/indexed param');
                            $xml .= $this->serializeType($name, $type, $p, $use, $enc_style);
                        } elseif (isset($parameters[$name])) {
                            $this->debug('calling serializeType w/named param');
                            $xml .= $this->serializeType($name, $type, $parameters[$name], $use, $enc_style);
                        } else {
                            // TODO: only send nillable
                            $this->debug('calling serializeType w/null param');
                            $xml .= $this->serializeType($name, $type, null, $use, $enc_style);
                        }
                    }
                } else {
                    $this->debug('no parameters passed.');
                }
            }
            $this->debug("serializeRPCParameters returning: $xml");
            return $xml;
        }
        
        /**
         * serialize a PHP value according to a WSDL message definition
         *
         * TODO
         * - multi-ref serialization
         * - validate PHP values against type definitions, return errors if invalid
         *
         * @param string $operation operation name
         * @param string $direction (input|output)
         * @param mixed $parameters parameter value(s)
         * @return mixed parameters serialized as XML or false on error (e.g. operation not found)
         * @access public
         * @deprecated
         */
        function serializeParameters($operation, $direction, $parameters)
        {
            $this->debug("in serializeParameters: operation=$operation, direction=$direction, XMLSchemaVersion=$this->XMLSchemaVersion");
            $this->appendDebug('parameters=' . $this->varDump($parameters));
            
            if ($direction != 'input' && $direction != 'output') {
                $this->debug('The value of the \$direction argument needs to be either "input" or "output"');
                $this->setError('The value of the \$direction argument needs to be either "input" or "output"');
                return false;
            }
            if (!$opData = $this->getOperationData($operation)) {
                $this->debug('Unable to retrieve WSDL data for operation: ' . $operation);
                $this->setError('Unable to retrieve WSDL data for operation: ' . $operation);
                return false;
            }
            $this->debug('opData:');
            $this->appendDebug($this->varDump($opData));
            
            // Get encoding style for output and set to current
            $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
            if(($direction == 'input') && isset($opData['output']['encodingStyle']) && ($opData['output']['encodingStyle'] != $encodingStyle)) {
                $encodingStyle = $opData['output']['encodingStyle'];
                $enc_style = $encodingStyle;
            }
            
            // set input params
            $xml = '';
            if (isset($opData[$direction]['parts']) && sizeof($opData[$direction]['parts']) > 0) {
                
                $use = $opData[$direction]['use'];
                $this->debug("use=$use");
                $this->debug('got ' . count($opData[$direction]['parts']) . ' part(s)');
                if (is_array($parameters)) {
                    $parametersArrayType = $this->isArraySimpleOrStruct($parameters);
                    $this->debug('have ' . $parametersArrayType . ' parameters');
                    foreach($opData[$direction]['parts'] as $name => $type) {
                        $this->debug('serializing part "'.$name.'" of type "'.$type.'"');
                        // Track encoding style
                        if(isset($opData[$direction]['encodingStyle']) && $encodingStyle != $opData[$direction]['encodingStyle']) {
                            $encodingStyle = $opData[$direction]['encodingStyle'];
                            $enc_style = $encodingStyle;
                        } else {
                            $enc_style = false;
                        }
                        // NOTE: add error handling here
                        // if serializeType returns false, then catch global error and fault
                        if ($parametersArrayType == 'arraySimple') {
                            $p = array_shift($parameters);
                            $this->debug('calling serializeType w/indexed param');
                            $xml .= $this->serializeType($name, $type, $p, $use, $enc_style);
                        } elseif (isset($parameters[$name])) {
                            $this->debug('calling serializeType w/named param');
                            $xml .= $this->serializeType($name, $type, $parameters[$name], $use, $enc_style);
                        } else {
                            // TODO: only send nillable
                            $this->debug('calling serializeType w/null param');
                            $xml .= $this->serializeType($name, $type, null, $use, $enc_style);
                        }
                    }
                } else {
                    $this->debug('no parameters passed.');
                }
            }
            $this->debug("serializeParameters returning: $xml");
            return $xml;
        }
        
        /**
         * serializes a PHP value according a given type definition
         *
         * @param string $name name of value (part or element)
         * @param string $type XML schema type of value (type or element)
         * @param mixed $value a native PHP value (parameter value)
         * @param string $use use for part (encoded|literal)
         * @param string $encodingStyle SOAP encoding style for the value (if different than the enclosing style)
         * @param boolean $unqualified a kludge for what should be XML namespace form handling
         * @return string value serialized as an XML string
         * @access private
         */
        function serializeType($name, $type, $value, $use='encoded', $encodingStyle=false, $unqualified=false)
        {
            $this->debug("in serializeType: name=$name, type=$type, use=$use, encodingStyle=$encodingStyle, unqualified=" . ($unqualified ? "unqualified" : "qualified"));
            $this->appendDebug("value=" . $this->varDump($value));
            if($use == 'encoded' && $encodingStyle) {
                $encodingStyle = ' SOAP-ENV:encodingStyle="' . $encodingStyle . '"';
            }
            
            // if a soapval has been supplied, let its type override the WSDL
            if (is_object($value) && get_class($value) == 'soapval') {
                if ($value->type_ns) {
                    $type = $value->type_ns . ':' . $value->type;
                    $forceType = true;
                    $this->debug("in serializeType: soapval overrides type to $type");
                } elseif ($value->type) {
                    $type = $value->type;
                    $forceType = true;
                    $this->debug("in serializeType: soapval overrides type to $type");
                } else {
                    $forceType = false;
                    $this->debug("in serializeType: soapval does not override type");
                }
                $attrs = $value->attributes;
                $value = $value->value;
                $this->debug("in serializeType: soapval overrides value to $value");
                if ($attrs) {
                    if (!is_array($value)) {
                        $value['!'] = $value;
                    }
                    foreach ($attrs as $n => $v) {
                        $value['!' . $n] = $v;
                    }
                    $this->debug("in serializeType: soapval provides attributes");
                }
            } else {
                $forceType = false;
            }
            
            $xml = '';
            if (strpos($type, ':')) {
                $uqType = substr($type, strrpos($type, ':') + 1);
                $ns = substr($type, 0, strrpos($type, ':'));
                $this->debug("in serializeType: got a prefixed type: $uqType, $ns");
                if ($this->getNamespaceFromPrefix($ns)) {
                    $ns = $this->getNamespaceFromPrefix($ns);
                    $this->debug("in serializeType: expanded prefixed type: $uqType, $ns");
                }
                
                if($ns == $this->XMLSchemaVersion || $ns == 'http://schemas.xmlsoap.org/soap/encoding/'){
                    $this->debug('in serializeType: type namespace indicates XML Schema or SOAP Encoding type');
                    if ($unqualified && $use == 'literal') {
                        $elementNS = " xmlns=\"\"";
                    } else {
                        $elementNS = '';
                    }
                    if (is_null($value)) {
                        if ($use == 'literal') {
                            // TODO: depends on minOccurs
                            $xml = "<$name$elementNS/>";
                        } else {
                            // TODO: depends on nillable, which should be checked before calling this method
                            $xml = "<$name$elementNS xsi:nil=\"true\" xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"/>";
                        }
                        $this->debug("in serializeType: returning: $xml");
                        return $xml;
                    }
                    if ($uqType == 'Array') {
                        // JBoss/Axis does this sometimes
                        return $this->serialize_val($value, $name, false, false, false, false, $use);
                    }
                    if ($uqType == 'boolean') {
                        if ((is_string($value) && $value == 'false') || (! $value)) {
                            $value = 'false';
                        } else {
                            $value = 'true';
                        }
                    }
                    if ($uqType == 'string' && gettype($value) == 'string') {
                        $value = $this->expandEntities($value);
                    }
                    if (($uqType == 'long' || $uqType == 'unsignedLong') && gettype($value) == 'double') {
                        $value = sprintf("%.0lf", $value);
                    }
                    // it's a scalar
                    // TODO: what about null/nil values?
                    // check type isn't a custom type extending xmlschema namespace
                    if (!$this->getTypeDef($uqType, $ns)) {
                        if ($use == 'literal') {
                            if ($forceType) {
                                $xml = "<$name$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\">$value</$name>";
                            } else {
                                $xml = "<$name$elementNS>$value</$name>";
                            }
                        } else {
                            $xml = "<$name$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"$encodingStyle>$value</$name>";
                        }
                        $this->debug("in serializeType: returning: $xml");
                        return $xml;
                    }
                    $this->debug('custom type extends XML Schema or SOAP Encoding namespace (yuck)');
                } else if ($ns == 'http://xml.apache.org/xml-soap') {
                    $this->debug('in serializeType: appears to be Apache SOAP type');
                    if ($uqType == 'Map') {
                        $tt_prefix = $this->getPrefixFromNamespace('http://xml.apache.org/xml-soap');
                        if (! $tt_prefix) {
                            $this->debug('in serializeType: Add namespace for Apache SOAP type');
                            $tt_prefix = 'ns' . rand(1000, 9999);
                            $this->namespaces[$tt_prefix] = 'http://xml.apache.org/xml-soap';
                            // force this to be added to usedNamespaces
                            $tt_prefix = $this->getPrefixFromNamespace('http://xml.apache.org/xml-soap');
                        }
                        $contents = '';
                        foreach($value as $k => $v) {
                            $this->debug("serializing map element: key $k, value $v");
                            $contents .= '<item>';
                            $contents .= $this->serialize_val($k,'key',false,false,false,false,$use);
                            $contents .= $this->serialize_val($v,'value',false,false,false,false,$use);
                            $contents .= '</item>';
                        }
                        if ($use == 'literal') {
                            if ($forceType) {
                                $xml = "<$name xsi:type=\"" . $tt_prefix . ":$uqType\">$contents</$name>";
                            } else {
                                $xml = "<$name>$contents</$name>";
                            }
                        } else {
                            $xml = "<$name xsi:type=\"" . $tt_prefix . ":$uqType\"$encodingStyle>$contents</$name>";
                        }
                        $this->debug("in serializeType: returning: $xml");
                        return $xml;
                    }
                    $this->debug('in serializeType: Apache SOAP type, but only support Map');
                }
            } else {
                // TODO: should the type be compared to types in XSD, and the namespace
                // set to XSD if the type matches?
                $this->debug("in serializeType: No namespace for type $type");
                $ns = '';
                $uqType = $type;
            }
            if(!$typeDef = $this->getTypeDef($uqType, $ns)){
                $this->setError("$type ($uqType) is not a supported type.");
                $this->debug("in serializeType: $type ($uqType) is not a supported type.");
                return false;
            } else {
                $this->debug("in serializeType: found typeDef");
                $this->appendDebug('typeDef=' . $this->varDump($typeDef));
                if (substr($uqType, -1) == '^') {
                    $uqType = substr($uqType, 0, -1);
                }
            }
            if (!isset($typeDef['phpType'])) {
                $this->setError("$type ($uqType) has no phpType.");
                $this->debug("in serializeType: $type ($uqType) has no phpType.");
                return false;
            }
            $phpType = $typeDef['phpType'];
            $this->debug("in serializeType: uqType: $uqType, ns: $ns, phptype: $phpType, arrayType: " . (isset($typeDef['arrayType']) ? $typeDef['arrayType'] : '') );
            // if php type == struct, map value to the <all> element names
            if ($phpType == 'struct') {
                if (isset($typeDef['typeClass']) && $typeDef['typeClass'] == 'element') {
                    $elementName = $uqType;
                    if (isset($typeDef['form']) && ($typeDef['form'] == 'qualified')) {
                        $elementNS = " xmlns=\"$ns\"";
                    } else {
                        $elementNS = " xmlns=\"\"";
                    }
                } else {
                    $elementName = $name;
                    if ($unqualified) {
                        $elementNS = " xmlns=\"\"";
                    } else {
                        $elementNS = '';
                    }
                }
                if (is_null($value)) {
                    if ($use == 'literal') {
                        // TODO: depends on minOccurs and nillable
                        $xml = "<$elementName$elementNS/>";
                    } else {
                        $xml = "<$elementName$elementNS xsi:nil=\"true\" xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"/>";
                    }
                    $this->debug("in serializeType: returning: $xml");
                    return $xml;
                }
                if (is_object($value)) {
                    $value = get_object_vars($value);
                }
                if (is_array($value)) {
                    $elementAttrs = $this->serializeComplexTypeAttributes($typeDef, $value, $ns, $uqType);
                    if ($use == 'literal') {
                        if ($forceType) {
                            $xml = "<$elementName$elementNS$elementAttrs xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\">";
                        } else {
                            $xml = "<$elementName$elementNS$elementAttrs>";
                        }
                    } else {
                        $xml = "<$elementName$elementNS$elementAttrs xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"$encodingStyle>";
                    }
                    
                    if (isset($typeDef['simpleContent']) && $typeDef['simpleContent'] == 'true') {
                        if (isset($value['!'])) {
                            $xml .= $value['!'];
                            $this->debug("in serializeType: serialized simpleContent for type $type");
                        } else {
                            $this->debug("in serializeType: no simpleContent to serialize for type $type");
                        }
                    } else {
                        // complexContent
                        $xml .= $this->serializeComplexTypeElements($typeDef, $value, $ns, $uqType, $use, $encodingStyle);
                    }
                    $xml .= "</$elementName>";
                } else {
                    $this->debug("in serializeType: phpType is struct, but value is not an array");
                    $this->setError("phpType is struct, but value is not an array: see debug output for details");
                    $xml = '';
                }
            } elseif ($phpType == 'array') {
                if (isset($typeDef['form']) && ($typeDef['form'] == 'qualified')) {
                    $elementNS = " xmlns=\"$ns\"";
                } else {
                    if ($unqualified) {
                        $elementNS = " xmlns=\"\"";
                    } else {
                        $elementNS = '';
                    }
                }
                if (is_null($value)) {
                    if ($use == 'literal') {
                        // TODO: depends on minOccurs
                        $xml = "<$name$elementNS/>";
                    } else {
                        $xml = "<$name$elementNS xsi:nil=\"true\" xsi:type=\"" .
                        $this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/') .
                        ":Array\" " .
                        $this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/') .
                        ':arrayType="' .
                        $this->getPrefixFromNamespace($this->getPrefix($typeDef['arrayType'])) .
                        ':' .
                        $this->getLocalPart($typeDef['arrayType'])."[0]\"/>";
                    }
                    $this->debug("in serializeType: returning: $xml");
                    return $xml;
                }
                if (isset($typeDef['multidimensional'])) {
                    $nv = array();
                    foreach($value as $v) {
                        $cols = ',' . sizeof($v);
                        $nv = array_merge($nv, $v);
                    }
                    $value = $nv;
                } else {
                    $cols = '';
                }
                if (is_array($value) && sizeof($value) >= 1) {
                    $rows = sizeof($value);
                    $contents = '';
                    foreach($value as $k => $v) {
                        $this->debug("serializing array element: $k, $v of type: $typeDef[arrayType]");
                        //if (strpos($typeDef['arrayType'], ':') ) {
                        if (!in_array($typeDef['arrayType'],$this->typemap['http://www.w3.org/2001/XMLSchema'])) {
                            $contents .= $this->serializeType('item', $typeDef['arrayType'], $v, $use);
                        } else {
                            $contents .= $this->serialize_val($v, 'item', $typeDef['arrayType'], null, $this->XMLSchemaVersion, false, $use);
                        }
                    }
                } else {
                    $rows = 0;
                    $contents = null;
                }
                // TODO: for now, an empty value will be serialized as a zero element
                // array.  Revisit this when coding the handling of null/nil values.
                if ($use == 'literal') {
                    $xml = "<$name$elementNS>"
                    .$contents
                    ."</$name>";
                } else {
                    $xml = "<$name$elementNS xsi:type=\"".$this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/').':Array" '.
                        $this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/')
                        .':arrayType="'
                            .$this->getPrefixFromNamespace($this->getPrefix($typeDef['arrayType']))
                            .":".$this->getLocalPart($typeDef['arrayType'])."[$rows$cols]\">"
                            .$contents
                            ."</$name>";
                }
            } elseif ($phpType == 'scalar') {
                if (isset($typeDef['form']) && ($typeDef['form'] == 'qualified')) {
                    $elementNS = " xmlns=\"$ns\"";
                } else {
                    if ($unqualified) {
                        $elementNS = " xmlns=\"\"";
                    } else {
                        $elementNS = '';
                    }
                }
                if ($use == 'literal') {
                    if ($forceType) {
                        $xml = "<$name$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\">$value</$name>";
                    } else {
                        $xml = "<$name$elementNS>$value</$name>";
                    }
                } else {
                    $xml = "<$name$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"$encodingStyle>$value</$name>";
                }
            }
            $this->debug("in serializeType: returning: $xml");
            return $xml;
        }
        
        /**
         * serializes the attributes for a complexType
         *
         * @param array $typeDef our internal representation of an XML schema type (or element)
         * @param mixed $value a native PHP value (parameter value)
         * @param string $ns the namespace of the type
         * @param string $uqType the local part of the type
         * @return string value serialized as an XML string
         * @access private
         */
        function serializeComplexTypeAttributes($typeDef, $value, $ns, $uqType) {
            $this->debug("serializeComplexTypeAttributes for XML Schema type $ns:$uqType");
            $xml = '';
            if (isset($typeDef['extensionBase'])) {
                $nsx = $this->getPrefix($typeDef['extensionBase']);
                $uqTypex = $this->getLocalPart($typeDef['extensionBase']);
                if ($this->getNamespaceFromPrefix($nsx)) {
                    $nsx = $this->getNamespaceFromPrefix($nsx);
                }
                if ($typeDefx = $this->getTypeDef($uqTypex, $nsx)) {
                    $this->debug("serialize attributes for extension base $nsx:$uqTypex");
                    $xml .= $this->serializeComplexTypeAttributes($typeDefx, $value, $nsx, $uqTypex);
                } else {
                    $this->debug("extension base $nsx:$uqTypex is not a supported type");
                }
            }
            if (isset($typeDef['attrs']) && is_array($typeDef['attrs'])) {
                $this->debug("serialize attributes for XML Schema type $ns:$uqType");
                if (is_array($value)) {
                    $xvalue = $value;
                } elseif (is_object($value)) {
                    $xvalue = get_object_vars($value);
                } else {
                    $this->debug("value is neither an array nor an object for XML Schema type $ns:$uqType");
                    $xvalue = array();
                }
                foreach ($typeDef['attrs'] as $aName => $attrs) {
                    if (isset($xvalue['!' . $aName])) {
                        $xname = '!' . $aName;
                        $this->debug("value provided for attribute $aName with key $xname");
                    } elseif (isset($xvalue[$aName])) {
                        $xname = $aName;
                        $this->debug("value provided for attribute $aName with key $xname");
                    } elseif (isset($attrs['default'])) {
                        $xname = '!' . $aName;
                        $xvalue[$xname] = $attrs['default'];
                        $this->debug('use default value of ' . $xvalue[$aName] . ' for attribute ' . $aName);
                    } else {
                        $xname = '';
                        $this->debug("no value provided for attribute $aName");
                    }
                    if ($xname) {
                        $xml .=  " $aName=\"" . $this->expandEntities($xvalue[$xname]) . "\"";
                    }
                }
            } else {
                $this->debug("no attributes to serialize for XML Schema type $ns:$uqType");
            }
            return $xml;
        }
        
        /**
         * serializes the elements for a complexType
         *
         * @param array $typeDef our internal representation of an XML schema type (or element)
         * @param mixed $value a native PHP value (parameter value)
         * @param string $ns the namespace of the type
         * @param string $uqType the local part of the type
         * @param string $use use for part (encoded|literal)
         * @param string $encodingStyle SOAP encoding style for the value (if different than the enclosing style)
         * @return string value serialized as an XML string
         * @access private
         */
        function serializeComplexTypeElements($typeDef, $value, $ns, $uqType, $use='encoded', $encodingStyle=false) {
            $this->debug("in serializeComplexTypeElements for XML Schema type $ns:$uqType");
            $xml = '';
            if (isset($typeDef['extensionBase'])) {
                $nsx = $this->getPrefix($typeDef['extensionBase']);
                $uqTypex = $this->getLocalPart($typeDef['extensionBase']);
                if ($this->getNamespaceFromPrefix($nsx)) {
                    $nsx = $this->getNamespaceFromPrefix($nsx);
                }
                if ($typeDefx = $this->getTypeDef($uqTypex, $nsx)) {
                    $this->debug("serialize elements for extension base $nsx:$uqTypex");
                    $xml .= $this->serializeComplexTypeElements($typeDefx, $value, $nsx, $uqTypex, $use, $encodingStyle);
                } else {
                    $this->debug("extension base $nsx:$uqTypex is not a supported type");
                }
            }
            if (isset($typeDef['elements']) && is_array($typeDef['elements'])) {
                $this->debug("in serializeComplexTypeElements, serialize elements for XML Schema type $ns:$uqType");
                if (is_array($value)) {
                    $xvalue = $value;
                } elseif (is_object($value)) {
                    $xvalue = get_object_vars($value);
                } else {
                    $this->debug("value is neither an array nor an object for XML Schema type $ns:$uqType");
                    $xvalue = array();
                }
                // toggle whether all elements are present - ideally should validate against schema
                if (count($typeDef['elements']) != count($xvalue)){
                    $optionals = true;
                }
                foreach ($typeDef['elements'] as $eName => $attrs) {
                    if (!isset($xvalue[$eName])) {
                        if (isset($attrs['default'])) {
                            $xvalue[$eName] = $attrs['default'];
                            $this->debug('use default value of ' . $xvalue[$eName] . ' for element ' . $eName);
                        }
                    }
                    // if user took advantage of a minOccurs=0, then only serialize named parameters
                    if (isset($optionals)
                        && (!isset($xvalue[$eName]))
                        && ( (!isset($attrs['nillable'])) || $attrs['nillable'] != 'true')
                        ){
                            if (isset($attrs['minOccurs']) && $attrs['minOccurs'] <> '0') {
                                $this->debug("apparent error: no value provided for element $eName with minOccurs=" . $attrs['minOccurs']);
                            }
                            // do nothing
                            $this->debug("no value provided for complexType element $eName and element is not nillable, so serialize nothing");
                    } else {
                        // get value
                        if (isset($xvalue[$eName])) {
                            $v = $xvalue[$eName];
                        } else {
                            $v = null;
                        }
                        if (isset($attrs['form'])) {
                            $unqualified = ($attrs['form'] == 'unqualified');
                        } else {
                            $unqualified = false;
                        }
                        if (isset($attrs['maxOccurs']) && ($attrs['maxOccurs'] == 'unbounded' || $attrs['maxOccurs'] > 1) && isset($v) && is_array($v) && $this->isArraySimpleOrStruct($v) == 'arraySimple') {
                            $vv = $v;
                            foreach ($vv as $k => $v) {
                                if (isset($attrs['type']) || isset($attrs['ref'])) {
                                    // serialize schema-defined type
                                    $xml .= $this->serializeType($eName, isset($attrs['type']) ? $attrs['type'] : $attrs['ref'], $v, $use, $encodingStyle, $unqualified);
                                } else {
                                    // serialize generic type (can this ever really happen?)
                                    $this->debug("calling serialize_val() for $v, $eName, false, false, false, false, $use");
                                    $xml .= $this->serialize_val($v, $eName, false, false, false, false, $use);
                                }
                            }
                        } else {
                            if (is_null($v) && isset($attrs['minOccurs']) && $attrs['minOccurs'] == '0') {
                                // do nothing
                            } elseif (is_null($v) && isset($attrs['nillable']) && $attrs['nillable'] == 'true') {
                                // TODO: serialize a nil correctly, but for now serialize schema-defined type
                                $xml .= $this->serializeType($eName, isset($attrs['type']) ? $attrs['type'] : $attrs['ref'], $v, $use, $encodingStyle, $unqualified);
                            } elseif (isset($attrs['type']) || isset($attrs['ref'])) {
                                // serialize schema-defined type
                                $xml .= $this->serializeType($eName, isset($attrs['type']) ? $attrs['type'] : $attrs['ref'], $v, $use, $encodingStyle, $unqualified);
                            } else {
                                // serialize generic type (can this ever really happen?)
                                $this->debug("calling serialize_val() for $v, $eName, false, false, false, false, $use");
                                $xml .= $this->serialize_val($v, $eName, false, false, false, false, $use);
                            }
                        }
                    }
                }
            } else {
                $this->debug("no elements to serialize for XML Schema type $ns:$uqType");
            }
            return $xml;
        }
        
        /**
         * adds an XML Schema complex type to the WSDL types
         *
         * @param string	$name
         * @param string $typeClass (complexType|simpleType|attribute)
         * @param string $phpType currently supported are array and struct (php assoc array)
         * @param string $compositor (all|sequence|choice)
         * @param string $restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
         * @param array $elements e.g. array ( name => array(name=>'',type=>'') )
         * @param array $attrs e.g. array(array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'xsd:string[]'))
         * @param string $arrayType as namespace:name (xsd:string)
         * @see nusoap_xmlschema
         * @access public
         */
        function addComplexType($name,$typeClass='complexType',$phpType='array',$compositor='',$restrictionBase='',$elements=array(),$attrs=array(),$arrayType='') {
            if (count($elements) > 0) {
                $eElements = array();
                foreach($elements as $n => $e){
                    // expand each element
                    $ee = array();
                    foreach ($e as $k => $v) {
                        $k = strpos($k,':') ? $this->expandQname($k) : $k;
                        $v = strpos($v,':') ? $this->expandQname($v) : $v;
                        $ee[$k] = $v;
                    }
                    $eElements[$n] = $ee;
                }
                $elements = $eElements;
            }
            
            if (count($attrs) > 0) {
                foreach($attrs as $n => $a){
                    // expand each attribute
                    foreach ($a as $k => $v) {
                        $k = strpos($k,':') ? $this->expandQname($k) : $k;
                        $v = strpos($v,':') ? $this->expandQname($v) : $v;
                        $aa[$k] = $v;
                    }
                    $eAttrs[$n] = $aa;
                }
                $attrs = $eAttrs;
            }
            
            $restrictionBase = strpos($restrictionBase,':') ? $this->expandQname($restrictionBase) : $restrictionBase;
            $arrayType = strpos($arrayType,':') ? $this->expandQname($arrayType) : $arrayType;
            
            $typens = isset($this->namespaces['types']) ? $this->namespaces['types'] : $this->namespaces['tns'];
            $this->schemas[$typens][0]->addComplexType($name,$typeClass,$phpType,$compositor,$restrictionBase,$elements,$attrs,$arrayType);
        }
        
        /**
         * adds an XML Schema simple type to the WSDL types
         *
         * @param string $name
         * @param string $restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array)
         * @param string $typeClass (should always be simpleType)
         * @param string $phpType (should always be scalar)
         * @param array $enumeration array of values
         * @see nusoap_xmlschema
         * @access public
         */
        function addSimpleType($name, $restrictionBase='', $typeClass='simpleType', $phpType='scalar', $enumeration=array()) {
            $restrictionBase = strpos($restrictionBase,':') ? $this->expandQname($restrictionBase) : $restrictionBase;
            
            $typens = isset($this->namespaces['types']) ? $this->namespaces['types'] : $this->namespaces['tns'];
            $this->schemas[$typens][0]->addSimpleType($name, $restrictionBase, $typeClass, $phpType, $enumeration);
        }
        
        /**
         * adds an element to the WSDL types
         *
         * @param array $attrs attributes that must include name and type
         * @see nusoap_xmlschema
         * @access public
         */
        function addElement($attrs) {
            $typens = isset($this->namespaces['types']) ? $this->namespaces['types'] : $this->namespaces['tns'];
            $this->schemas[$typens][0]->addElement($attrs);
        }
        
        /**
         * register an operation with the server
         *
         * @param string $name operation (method) name
         * @param array $in assoc array of input values: key = param name, value = param type
         * @param array $out assoc array of output values: key = param name, value = param type
         * @param string $namespace optional The namespace for the operation
         * @param string $soapaction optional The soapaction for the operation
         * @param string $style (rpc|document) optional The style for the operation Note: when 'document' is specified, parameter and return wrappers are created for you automatically
         * @param string $use (encoded|literal) optional The use for the parameters (cannot mix right now)
         * @param string $documentation optional The description to include in the WSDL
         * @param string $encodingStyle optional (usually 'http://schemas.xmlsoap.org/soap/encoding/' for encoded)
         * @access public
         */
        function addOperation($name, $in = false, $out = false, $namespace = false, $soapaction = false, $style = 'rpc', $use = 'encoded', $documentation = '', $encodingStyle = ''){
            if ($use == 'encoded' && $encodingStyle == '') {
                $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
            }
            
            if ($style == 'document') {
                $elements = array();
                foreach ($in as $n => $t) {
                    $elements[$n] = array('name' => $n, 'type' => $t, 'form' => 'unqualified');
                }
                $this->addComplexType($name . 'RequestType', 'complexType', 'struct', 'all', '', $elements);
                $this->addElement(array('name' => $name, 'type' => $name . 'RequestType'));
                $in = array('parameters' => 'tns:' . $name . '^');
                
                $elements = array();
                foreach ($out as $n => $t) {
                    $elements[$n] = array('name' => $n, 'type' => $t, 'form' => 'unqualified');
                }
                $this->addComplexType($name . 'ResponseType', 'complexType', 'struct', 'all', '', $elements);
                $this->addElement(array('name' => $name . 'Response', 'type' => $name . 'ResponseType', 'form' => 'qualified'));
                $out = array('parameters' => 'tns:' . $name . 'Response' . '^');
            }
            
            // get binding
            $this->bindings[ $this->serviceName . 'Binding' ]['operations'][$name] =
            array(
                'name' => $name,
                'binding' => $this->serviceName . 'Binding',
                'endpoint' => $this->endpoint,
                'soapAction' => $soapaction,
                'style' => $style,
                'input' => array(
                    'use' => $use,
                    'namespace' => $namespace,
                    'encodingStyle' => $encodingStyle,
                    'message' => $name . 'Request',
                    'parts' => $in),
                'output' => array(
                    'use' => $use,
                    'namespace' => $namespace,
                    'encodingStyle' => $encodingStyle,
                    'message' => $name . 'Response',
                    'parts' => $out),
                'namespace' => $namespace,
                'transport' => 'http://schemas.xmlsoap.org/soap/http',
                'documentation' => $documentation);
            // add portTypes
            // add messages
            if($in)
            {
                foreach($in as $pName => $pType)
                {
                    if(strpos($pType,':')) {
                        $pType = $this->getNamespaceFromPrefix($this->getPrefix($pType)).":".$this->getLocalPart($pType);
                    }
                    $this->messages[$name.'Request'][$pName] = $pType;
                }
            } else {
                $this->messages[$name.'Request']= '0';
            }
            if($out)
            {
                foreach($out as $pName => $pType)
                {
                    if(strpos($pType,':')) {
                        $pType = $this->getNamespaceFromPrefix($this->getPrefix($pType)).":".$this->getLocalPart($pType);
                    }
                    $this->messages[$name.'Response'][$pName] = $pType;
                }
            } else {
                $this->messages[$name.'Response']= '0';
            }
            return true;
        }
    }

<?php
namespace com\linways\base\soap;

/**
 * Contains information for a SOAP fault.
 * Mainly used for returning faults from deployed functions
 * in a server instance.
 * @author   Dietrich Ayala <dietrich@ganx4.com>
 * @version  $Id: nusoap.php,v 1.123 2010/04/26 20:15:08 snichol Exp $
 * @access public
 */
class nusoap_fault extends nusoap_base {
    /**
     * The fault code (client|server)
     * @var string
     * @access private
     */
    var $faultcode;
    /**
     * The fault actor
     * @var string
     * @access private
     */
    var $faultactor;
    /**
     * The fault string, a description of the fault
     * @var string
     * @access private
     */
    var $faultstring;
    /**
     * The fault detail, typically a string or array of string
     * @var mixed
     * @access private
     */
    var $faultdetail;
    
    /**
     * constructor
     *
     * @param string $faultcode (SOAP-ENV:Client | SOAP-ENV:Server)
     * @param string $faultactor only used when msg routed between multiple actors
     * @param string $faultstring human readable error message
     * @param mixed $faultdetail detail, typically a string or array of string
     */
    function nusoap_fault($faultcode,$faultactor='',$faultstring='',$faultdetail=''){
        parent::nusoap_base();
        $this->faultcode = $faultcode;
        $this->faultactor = $faultactor;
        $this->faultstring = $faultstring;
        $this->faultdetail = $faultdetail;
    }
    
    /**
     * serialize a fault
     *
     * @return	string	The serialization of the fault instance.
     * @access   public
     */
    function serialize(){
        $ns_string = '';
        foreach($this->namespaces as $k => $v){
            $ns_string .= "\n  xmlns:$k=\"$v\"";
        }
        $return_msg =
        '<?xml version="1.0" encoding="'.$this->soap_defencoding.'"?>'.
        '<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"'.$ns_string.">\n".
        '<SOAP-ENV:Body>'.
        '<SOAP-ENV:Fault>'.
        $this->serialize_val($this->faultcode, 'faultcode').
        $this->serialize_val($this->faultactor, 'faultactor').
        $this->serialize_val($this->faultstring, 'faultstring').
        $this->serialize_val($this->faultdetail, 'detail').
        '</SOAP-ENV:Fault>'.
        '</SOAP-ENV:Body>'.
        '</SOAP-ENV:Envelope>';
        return $return_msg;
    }
}

<?php
namespace com\linways\base\soap;


/**
 *
 * nusoap_server allows the user to create a SOAP server
 * that is capable of receiving messages and returning responses
 *
 * @author   Dietrich Ayala <dietrich@ganx4.com>
 * @author   Scott Nichol <snichol@users.sourceforge.net>
 * @version  $Id: nusoap.php,v 1.123 2010/04/26 20:15:08 snichol Exp $
 * @access   public
 */
class nusoap_server extends nusoap_base {
    /**
     * HTTP headers of request
     * @var array
     * @access private
     */
    var $headers = array();
    /**
     * HTTP request
     * @var string
     * @access private
     */
    var $request = '';
    /**
     * SOAP headers from request (incomplete namespace resolution; special characters not escaped) (text)
     * @var string
     * @access public
     */
    var $requestHeaders = '';
    /**
     * SOAP Headers from request (parsed)
     * @var mixed
     * @access public
     */
    var $requestHeader = NULL;
    /**
     * SOAP body request portion (incomplete namespace resolution; special characters not escaped) (text)
     * @var string
     * @access public
     */
    var $document = '';
    /**
     * SOAP payload for request (text)
     * @var string
     * @access public
     */
    var $requestSOAP = '';
    /**
     * requested method namespace URI
     * @var string
     * @access private
     */
    var $methodURI = '';
    /**
     * name of method requested
     * @var string
     * @access private
     */
    var $methodname = '';
    /**
     * method parameters from request
     * @var array
     * @access private
     */
    var $methodparams = array();
    /**
     * SOAP Action from request
     * @var string
     * @access private
     */
    var $SOAPAction = '';
    /**
     * character set encoding of incoming (request) messages
     * @var string
     * @access public
     */
    var $xml_encoding = '';
    /**
     * toggles whether the parser decodes element content w/ utf8_decode()
     * @var boolean
     * @access public
     */
    var $decode_utf8 = true;
    
    /**
     * HTTP headers of response
     * @var array
     * @access public
     */
    var $outgoing_headers = array();
    /**
     * HTTP response
     * @var string
     * @access private
     */
    var $response = '';
    /**
     * SOAP headers for response (text or array of soapval or associative array)
     * @var mixed
     * @access public
     */
    var $responseHeaders = '';
    /**
     * SOAP payload for response (text)
     * @var string
     * @access private
     */
    var $responseSOAP = '';
    /**
     * method return value to place in response
     * @var mixed
     * @access private
     */
    var $methodreturn = false;
    /**
     * whether $methodreturn is a string of literal XML
     * @var boolean
     * @access public
     */
    var $methodreturnisliteralxml = false;
    /**
     * SOAP fault for response (or false)
     * @var mixed
     * @access private
     */
    var $fault = false;
    /**
     * text indication of result (for debugging)
     * @var string
     * @access private
     */
    var $result = 'successful';
    
    /**
     * assoc array of operations => opData; operations are added by the register()
     * method or by parsing an external WSDL definition
     * @var array
     * @access private
     */
    var $operations = array();
    /**
     * wsdl instance (if one)
     * @var mixed
     * @access private
     */
    var $wsdl = false;
    /**
     * URL for WSDL (if one)
     * @var mixed
     * @access private
     */
    var $externalWSDLURL = false;
    /**
     * whether to append debug to response as XML comment
     * @var boolean
     * @access public
     */
    var $debug_flag = false;
    
    
    /**
     * constructor
     * the optional parameter is a path to a WSDL file that you'd like to bind the server instance to.
     *
     * @param mixed $wsdl file path or URL (string), or wsdl instance (object)
     * @access   public
     */
    function nusoap_server($wsdl=false){
        parent::nusoap_base();
        // turn on debugging?
        global $debug;
        global $HTTP_SERVER_VARS;
        
        if (isset($_SERVER)) {
            $this->debug("_SERVER is defined:");
            $this->appendDebug($this->varDump($_SERVER));
        } elseif (isset($HTTP_SERVER_VARS)) {
            $this->debug("HTTP_SERVER_VARS is defined:");
            $this->appendDebug($this->varDump($HTTP_SERVER_VARS));
        } else {
            $this->debug("Neither _SERVER nor HTTP_SERVER_VARS is defined.");
        }
        
        if (isset($debug)) {
            $this->debug("In nusoap_server, set debug_flag=$debug based on global flag");
            $this->debug_flag = $debug;
        } elseif (isset($_SERVER['QUERY_STRING'])) {
            $qs = explode('&', $_SERVER['QUERY_STRING']);
            foreach ($qs as $v) {
                if (substr($v, 0, 6) == 'debug=') {
                    $this->debug("In nusoap_server, set debug_flag=" . substr($v, 6) . " based on query string #1");
                    $this->debug_flag = substr($v, 6);
                }
            }
        } elseif (isset($HTTP_SERVER_VARS['QUERY_STRING'])) {
            $qs = explode('&', $HTTP_SERVER_VARS['QUERY_STRING']);
            foreach ($qs as $v) {
                if (substr($v, 0, 6) == 'debug=') {
                    $this->debug("In nusoap_server, set debug_flag=" . substr($v, 6) . " based on query string #2");
                    $this->debug_flag = substr($v, 6);
                }
            }
        }
        
        // wsdl
        if($wsdl){
            $this->debug("In nusoap_server, WSDL is specified");
            if (is_object($wsdl) && (get_class($wsdl) == 'wsdl')) {
                $this->wsdl = $wsdl;
                $this->externalWSDLURL = $this->wsdl->wsdl;
                $this->debug('Use existing wsdl instance from ' . $this->externalWSDLURL);
            } else {
                $this->debug('Create wsdl from ' . $wsdl);
                $this->wsdl = new wsdl($wsdl);
                $this->externalWSDLURL = $wsdl;
            }
            $this->appendDebug($this->wsdl->getDebug());
            $this->wsdl->clearDebug();
            if($err = $this->wsdl->getError()){
                die('WSDL ERROR: '.$err);
            }
        }
    }
    
    /**
     * processes request and returns response
     *
     * @param    string $data usually is the value of $HTTP_RAW_POST_DATA
     * @access   public
     */
    function service($data){
        global $HTTP_SERVER_VARS;
        
        if (isset($_SERVER['REQUEST_METHOD'])) {
            $rm = $_SERVER['REQUEST_METHOD'];
        } elseif (isset($HTTP_SERVER_VARS['REQUEST_METHOD'])) {
            $rm = $HTTP_SERVER_VARS['REQUEST_METHOD'];
        } else {
            $rm = '';
        }
        
        if (isset($_SERVER['QUERY_STRING'])) {
            $qs = $_SERVER['QUERY_STRING'];
        } elseif (isset($HTTP_SERVER_VARS['QUERY_STRING'])) {
            $qs = $HTTP_SERVER_VARS['QUERY_STRING'];
        } else {
            $qs = '';
        }
        $this->debug("In service, request method=$rm query string=$qs strlen(\$data)=" . strlen($data));
        
        if ($rm == 'POST') {
            $this->debug("In service, invoke the request");
            $this->parse_request($data);
            if (! $this->fault) {
                $this->invoke_method();
            }
            if (! $this->fault) {
                $this->serialize_return();
            }
            $this->send_response();
        } elseif (preg_match('/wsdl/', $qs) ){
            $this->debug("In service, this is a request for WSDL");
            if ($this->externalWSDLURL){
                if (strpos($this->externalWSDLURL, "http://") !== false) { // assume URL
                    $this->debug("In service, re-direct for WSDL");
                    header('Location: '.$this->externalWSDLURL);
                } else { // assume file
                    $this->debug("In service, use file passthru for WSDL");
                    header("Content-Type: text/xml\r\n");
                    $pos = strpos($this->externalWSDLURL, "file://");
                    if ($pos === false) {
                        $filename = $this->externalWSDLURL;
                    } else {
                        $filename = substr($this->externalWSDLURL, $pos + 7);
                    }
                    $fp = fopen($this->externalWSDLURL, 'r');
                    fpassthru($fp);
                }
            } elseif ($this->wsdl) {
                $this->debug("In service, serialize WSDL");
                header("Content-Type: text/xml; charset=ISO-8859-1\r\n");
                print $this->wsdl->serialize($this->debug_flag);
                if ($this->debug_flag) {
                    $this->debug('wsdl:');
                    $this->appendDebug($this->varDump($this->wsdl));
                    print $this->getDebugAsXMLComment();
                }
            } else {
                $this->debug("In service, there is no WSDL");
                header("Content-Type: text/html; charset=ISO-8859-1\r\n");
                print "This service does not provide WSDL";
            }
        } elseif ($this->wsdl) {
            $this->debug("In service, return Web description");
            print $this->wsdl->webDescription();
        } else {
            $this->debug("In service, no Web description");
            header("Content-Type: text/html; charset=ISO-8859-1\r\n");
            print "This service does not provide a Web description";
        }
    }
    
    /**
     * parses HTTP request headers.
     *
     * The following fields are set by this function (when successful)
     *
     * headers
     * request
     * xml_encoding
     * SOAPAction
     *
     * @access   private
     */
    function parse_http_headers() {
        global $HTTP_SERVER_VARS;
        
        $this->request = '';
        $this->SOAPAction = '';
        if(function_exists('getallheaders')){
            $this->debug("In parse_http_headers, use getallheaders");
            $headers = getallheaders();
            foreach($headers as $k=>$v){
                $k = strtolower($k);
                $this->headers[$k] = $v;
                $this->request .= "$k: $v\r\n";
                $this->debug("$k: $v");
            }
            // get SOAPAction header
            if(isset($this->headers['soapaction'])){
                $this->SOAPAction = str_replace('"','',$this->headers['soapaction']);
            }
            // get the character encoding of the incoming request
            if(isset($this->headers['content-type']) && strpos($this->headers['content-type'],'=')){
                $enc = str_replace('"','',substr(strstr($this->headers["content-type"],'='),1));
                if(preg_match('/^(ISO-8859-1|US-ASCII|UTF-8)$/i',$enc)){
                    $this->xml_encoding = strtoupper($enc);
                } else {
                    $this->xml_encoding = 'US-ASCII';
                }
            } else {
                // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
                $this->xml_encoding = 'ISO-8859-1';
            }
        } elseif(isset($_SERVER) && is_array($_SERVER)){
            $this->debug("In parse_http_headers, use _SERVER");
            foreach ($_SERVER as $k => $v) {
                if (substr($k, 0, 5) == 'HTTP_') {
                    $k = str_replace(' ', '-', strtolower(str_replace('_', ' ', substr($k, 5))));
                } else {
                    $k = str_replace(' ', '-', strtolower(str_replace('_', ' ', $k)));
                }
                if ($k == 'soapaction') {
                    // get SOAPAction header
                    $k = 'SOAPAction';
                    $v = str_replace('"', '', $v);
                    $v = str_replace('\\', '', $v);
                    $this->SOAPAction = $v;
                } else if ($k == 'content-type') {
                    // get the character encoding of the incoming request
                    if (strpos($v, '=')) {
                        $enc = substr(strstr($v, '='), 1);
                        $enc = str_replace('"', '', $enc);
                        $enc = str_replace('\\', '', $enc);
                        if (preg_match('/^(ISO-8859-1|US-ASCII|UTF-8)$/i',$enc)) {
                            $this->xml_encoding = strtoupper($enc);
                        } else {
                            $this->xml_encoding = 'US-ASCII';
                        }
                    } else {
                        // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
                        $this->xml_encoding = 'ISO-8859-1';
                    }
                }
                $this->headers[$k] = $v;
                $this->request .= "$k: $v\r\n";
                $this->debug("$k: $v");
            }
        } elseif (is_array($HTTP_SERVER_VARS)) {
            $this->debug("In parse_http_headers, use HTTP_SERVER_VARS");
            foreach ($HTTP_SERVER_VARS as $k => $v) {
                if (substr($k, 0, 5) == 'HTTP_') {
                    $k = str_replace(' ', '-', strtolower(str_replace('_', ' ', substr($k, 5)))); 	                                         $k = strtolower(substr($k, 5));
                } else {
                    $k = str_replace(' ', '-', strtolower(str_replace('_', ' ', $k))); 	                                         $k = strtolower($k);
                }
                if ($k == 'soapaction') {
                    // get SOAPAction header
                    $k = 'SOAPAction';
                    $v = str_replace('"', '', $v);
                    $v = str_replace('\\', '', $v);
                    $this->SOAPAction = $v;
                } else if ($k == 'content-type') {
                    // get the character encoding of the incoming request
                    if (strpos($v, '=')) {
                        $enc = substr(strstr($v, '='), 1);
                        $enc = str_replace('"', '', $enc);
                        $enc = str_replace('\\', '', $enc);
                        if (preg_match('/^(ISO-8859-1|US-ASCII|UTF-8)$/i',$enc)) {
                            $this->xml_encoding = strtoupper($enc);
                        } else {
                            $this->xml_encoding = 'US-ASCII';
                        }
                    } else {
                        // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
                        $this->xml_encoding = 'ISO-8859-1';
                    }
                }
                $this->headers[$k] = $v;
                $this->request .= "$k: $v\r\n";
                $this->debug("$k: $v");
            }
        } else {
            $this->debug("In parse_http_headers, HTTP headers not accessible");
            $this->setError("HTTP headers not accessible");
        }
    }
    
    /**
     * parses a request
     *
     * The following fields are set by this function (when successful)
     *
     * headers
     * request
     * xml_encoding
     * SOAPAction
     * request
     * requestSOAP
     * methodURI
     * methodname
     * methodparams
     * requestHeaders
     * document
     *
     * This sets the fault field on error
     *
     * @param    string $data XML string
     * @access   private
     */
    function parse_request($data='') {
        $this->debug('entering parse_request()');
        $this->parse_http_headers();
        $this->debug('got character encoding: '.$this->xml_encoding);
        // uncompress if necessary
        if (isset($this->headers['content-encoding']) && $this->headers['content-encoding'] != '') {
            $this->debug('got content encoding: ' . $this->headers['content-encoding']);
            if ($this->headers['content-encoding'] == 'deflate' || $this->headers['content-encoding'] == 'gzip') {
                // if decoding works, use it. else assume data wasn't gzencoded
                if (function_exists('gzuncompress')) {
                    if ($this->headers['content-encoding'] == 'deflate' && $degzdata = @gzuncompress($data)) {
                        $data = $degzdata;
                    } elseif ($this->headers['content-encoding'] == 'gzip' && $degzdata = gzinflate(substr($data, 10))) {
                        $data = $degzdata;
                    } else {
                        $this->fault('SOAP-ENV:Client', 'Errors occurred when trying to decode the data');
                        return;
                    }
                } else {
                    $this->fault('SOAP-ENV:Client', 'This Server does not support compressed data');
                    return;
                }
            }
        }
        $this->request .= "\r\n".$data;
        $data = $this->parseRequest($this->headers, $data);
        $this->requestSOAP = $data;
        $this->debug('leaving parse_request');
    }
    
    /**
     * invokes a PHP function for the requested SOAP method
     *
     * The following fields are set by this function (when successful)
     *
     * methodreturn
     *
     * Note that the PHP function that is called may also set the following
     * fields to affect the response sent to the client
     *
     * responseHeaders
     * outgoing_headers
     *
     * This sets the fault field on error
     *
     * @access   private
     */
    function invoke_method() {
        $this->debug('in invoke_method, methodname=' . $this->methodname . ' methodURI=' . $this->methodURI . ' SOAPAction=' . $this->SOAPAction);
        
        //
        // if you are debugging in this area of the code, your service uses a class to implement methods,
        // you use SOAP RPC, and the client is .NET, please be aware of the following...
        // when the .NET wsdl.exe utility generates a proxy, it will remove the '.' or '..' from the
        // method name.  that is fine for naming the .NET methods.  it is not fine for properly constructing
        // the XML request and reading the XML response.  you need to add the RequestElementName and
        // ResponseElementName to the System.Web.Services.Protocols.SoapRpcMethodAttribute that wsdl.exe
        // generates for the method.  these parameters are used to specify the correct XML element names
        // for .NET to use, i.e. the names with the '.' in them.
        //
        $orig_methodname = $this->methodname;
        if ($this->wsdl) {
            if ($this->opData = $this->wsdl->getOperationData($this->methodname)) {
                $this->debug('in invoke_method, found WSDL operation=' . $this->methodname);
                $this->appendDebug('opData=' . $this->varDump($this->opData));
            } elseif ($this->opData = $this->wsdl->getOperationDataForSoapAction($this->SOAPAction)) {
                // Note: hopefully this case will only be used for doc/lit, since rpc services should have wrapper element
                $this->debug('in invoke_method, found WSDL soapAction=' . $this->SOAPAction . ' for operation=' . $this->opData['name']);
                $this->appendDebug('opData=' . $this->varDump($this->opData));
                $this->methodname = $this->opData['name'];
            } else {
                $this->debug('in invoke_method, no WSDL for operation=' . $this->methodname);
                $this->fault('SOAP-ENV:Client', "Operation '" . $this->methodname . "' is not defined in the WSDL for this service");
                return;
            }
        } else {
            $this->debug('in invoke_method, no WSDL to validate method');
        }
        
        // if a . is present in $this->methodname, we see if there is a class in scope,
        // which could be referred to. We will also distinguish between two deliminators,
        // to allow methods to be called a the class or an instance
        if (strpos($this->methodname, '..') > 0) {
            $delim = '..';
        } else if (strpos($this->methodname, '.') > 0) {
            $delim = '.';
        } else {
            $delim = '';
        }
        $this->debug("in invoke_method, delim=$delim");
        
        $class = '';
        $method = '';
        if (strlen($delim) > 0 && substr_count($this->methodname, $delim) == 1) {
            $try_class = substr($this->methodname, 0, strpos($this->methodname, $delim));
            if (class_exists($try_class)) {
                // get the class and method name
                $class = $try_class;
                $method = substr($this->methodname, strpos($this->methodname, $delim) + strlen($delim));
                $this->debug("in invoke_method, class=$class method=$method delim=$delim");
            } else {
                $this->debug("in invoke_method, class=$try_class not found");
            }
        } else {
            $try_class = '';
            $this->debug("in invoke_method, no class to try");
        }
        
        // does method exist?
        if ($class == '') {
            if (!function_exists($this->methodname)) {
                $this->debug("in invoke_method, function '$this->methodname' not found!");
                $this->result = 'fault: method not found';
                $this->fault('SOAP-ENV:Client',"method '$this->methodname'('$orig_methodname') not defined in service('$try_class' '$delim')");
                return;
            }
        } else {
            $method_to_compare = (substr(phpversion(), 0, 2) == '4.') ? strtolower($method) : $method;
            if (!in_array($method_to_compare, get_class_methods($class))) {
                $this->debug("in invoke_method, method '$this->methodname' not found in class '$class'!");
                $this->result = 'fault: method not found';
                $this->fault('SOAP-ENV:Client',"method '$this->methodname'/'$method_to_compare'('$orig_methodname') not defined in service/'$class'('$try_class' '$delim')");
                return;
            }
        }
        
        // evaluate message, getting back parameters
        // verify that request parameters match the method's signature
        if(! $this->verify_method($this->methodname,$this->methodparams)){
            // debug
            $this->debug('ERROR: request not verified against method signature');
            $this->result = 'fault: request failed validation against method signature';
            // return fault
            $this->fault('SOAP-ENV:Client',"Operation '$this->methodname' not defined in service.");
            return;
        }
        
        // if there are parameters to pass
        $this->debug('in invoke_method, params:');
        $this->appendDebug($this->varDump($this->methodparams));
        $this->debug("in invoke_method, calling '$this->methodname'");
        if (!function_exists('call_user_func_array')) {
            if ($class == '') {
                $this->debug('in invoke_method, calling function using eval()');
                $funcCall = "\$this->methodreturn = $this->methodname(";
            } else {
                if ($delim == '..') {
                    $this->debug('in invoke_method, calling class method using eval()');
                    $funcCall = "\$this->methodreturn = ".$class."::".$method."(";
                } else {
                    $this->debug('in invoke_method, calling instance method using eval()');
                    // generate unique instance name
                    $instname = "\$inst_".time();
                    $funcCall = $instname." = new ".$class."(); ";
                    $funcCall .= "\$this->methodreturn = ".$instname."->".$method."(";
                }
            }
            if ($this->methodparams) {
                foreach ($this->methodparams as $param) {
                    if (is_array($param) || is_object($param)) {
                        $this->fault('SOAP-ENV:Client', 'NuSOAP does not handle complexType parameters correctly when using eval; call_user_func_array must be available');
                        return;
                    }
                    $funcCall .= "\"$param\",";
                }
                $funcCall = substr($funcCall, 0, -1);
            }
            $funcCall .= ');';
            $this->debug('in invoke_method, function call: '.$funcCall);
            @eval($funcCall);
        } else {
            if ($class == '') {
                $this->debug('in invoke_method, calling function using call_user_func_array()');
                $call_arg = "$this->methodname";	// straight assignment changes $this->methodname to lower case after call_user_func_array()
            } elseif ($delim == '..') {
                $this->debug('in invoke_method, calling class method using call_user_func_array()');
                $call_arg = array ($class, $method);
            } else {
                $this->debug('in invoke_method, calling instance method using call_user_func_array()');
                $instance = new $class ();
                $call_arg = array(&$instance, $method);
            }
            if (is_array($this->methodparams)) {
                $this->methodreturn = call_user_func_array($call_arg, array_values($this->methodparams));
            } else {
                $this->methodreturn = call_user_func_array($call_arg, array());
            }
        }
        $this->debug('in invoke_method, methodreturn:');
        $this->appendDebug($this->varDump($this->methodreturn));
        $this->debug("in invoke_method, called method $this->methodname, received data of type ".gettype($this->methodreturn));
    }
    
    /**
     * serializes the return value from a PHP function into a full SOAP Envelope
     *
     * The following fields are set by this function (when successful)
     *
     * responseSOAP
     *
     * This sets the fault field on error
     *
     * @access   private
     */
    function serialize_return() {
        $this->debug('Entering serialize_return methodname: ' . $this->methodname . ' methodURI: ' . $this->methodURI);
        // if fault
        if (isset($this->methodreturn) && is_object($this->methodreturn) && ((get_class($this->methodreturn) == 'soap_fault') || (get_class($this->methodreturn) == 'nusoap_fault'))) {
            $this->debug('got a fault object from method');
            $this->fault = $this->methodreturn;
            return;
        } elseif ($this->methodreturnisliteralxml) {
            $return_val = $this->methodreturn;
            // returned value(s)
        } else {
            $this->debug('got a(n) '.gettype($this->methodreturn).' from method');
            $this->debug('serializing return value');
            if($this->wsdl){
                if (sizeof($this->opData['output']['parts']) > 1) {
                    $this->debug('more than one output part, so use the method return unchanged');
                    $opParams = $this->methodreturn;
                } elseif (sizeof($this->opData['output']['parts']) == 1) {
                    $this->debug('exactly one output part, so wrap the method return in a simple array');
                    // TODO: verify that it is not already wrapped!
                    //foreach ($this->opData['output']['parts'] as $name => $type) {
                    //	$this->debug('wrap in element named ' . $name);
                        //}
                        $opParams = array($this->methodreturn);
                }
                $return_val = $this->wsdl->serializeRPCParameters($this->methodname,'output',$opParams);
                $this->appendDebug($this->wsdl->getDebug());
                $this->wsdl->clearDebug();
                if($errstr = $this->wsdl->getError()){
                    $this->debug('got wsdl error: '.$errstr);
                    $this->fault('SOAP-ENV:Server', 'unable to serialize result');
                    return;
                }
            } else {
                if (isset($this->methodreturn)) {
                    $return_val = $this->serialize_val($this->methodreturn, 'return');
                } else {
                    $return_val = '';
                    $this->debug('in absence of WSDL, assume void return for backward compatibility');
                }
            }
        }
        $this->debug('return value:');
        $this->appendDebug($this->varDump($return_val));
        
        $this->debug('serializing response');
        if ($this->wsdl) {
            $this->debug('have WSDL for serialization: style is ' . $this->opData['style']);
            if ($this->opData['style'] == 'rpc') {
                $this->debug('style is rpc for serialization: use is ' . $this->opData['output']['use']);
                if ($this->opData['output']['use'] == 'literal') {
                    // http://www.ws-i.org/Profiles/BasicProfile-1.1-2004-08-24.html R2735 says rpc/literal accessor elements should not be in a namespace
                    if ($this->methodURI) {
                        $payload = '<ns1:'.$this->methodname.'Response xmlns:ns1="'.$this->methodURI.'">'.$return_val.'</ns1:'.$this->methodname."Response>";
                    } else {
                        $payload = '<'.$this->methodname.'Response>'.$return_val.'</'.$this->methodname.'Response>';
                    }
                } else {
                    if ($this->methodURI) {
                        $payload = '<ns1:'.$this->methodname.'Response xmlns:ns1="'.$this->methodURI.'">'.$return_val.'</ns1:'.$this->methodname."Response>";
                    } else {
                        $payload = '<'.$this->methodname.'Response>'.$return_val.'</'.$this->methodname.'Response>';
                    }
                }
            } else {
                $this->debug('style is not rpc for serialization: assume document');
                $payload = $return_val;
            }
        } else {
            $this->debug('do not have WSDL for serialization: assume rpc/encoded');
            $payload = '<ns1:'.$this->methodname.'Response xmlns:ns1="'.$this->methodURI.'">'.$return_val.'</ns1:'.$this->methodname."Response>";
        }
        $this->result = 'successful';
        if($this->wsdl){
            //if($this->debug_flag){
            $this->appendDebug($this->wsdl->getDebug());
            //	}
            if (isset($this->opData['output']['encodingStyle'])) {
                $encodingStyle = $this->opData['output']['encodingStyle'];
            } else {
                $encodingStyle = '';
            }
            // Added: In case we use a WSDL, return a serialized env. WITH the usedNamespaces.
            $this->responseSOAP = $this->serializeEnvelope($payload,$this->responseHeaders,$this->wsdl->usedNamespaces,$this->opData['style'],$this->opData['output']['use'],$encodingStyle);
        } else {
            $this->responseSOAP = $this->serializeEnvelope($payload,$this->responseHeaders);
        }
        $this->debug("Leaving serialize_return");
    }
    
    /**
     * sends an HTTP response
     *
     * The following fields are set by this function (when successful)
     *
     * outgoing_headers
     * response
     *
     * @access   private
     */
    function send_response() {
        $this->debug('Enter send_response');
        if ($this->fault) {
            $payload = $this->fault->serialize();
            $this->outgoing_headers[] = "HTTP/1.0 500 Internal Server Error";
            $this->outgoing_headers[] = "Status: 500 Internal Server Error";
        } else {
            $payload = $this->responseSOAP;
            // Some combinations of PHP+Web server allow the Status
            // to come through as a header.  Since OK is the default
            // just do nothing.
            // $this->outgoing_headers[] = "HTTP/1.0 200 OK";
            // $this->outgoing_headers[] = "Status: 200 OK";
        }
        // add debug data if in debug mode
        if(isset($this->debug_flag) && $this->debug_flag){
            $payload .= $this->getDebugAsXMLComment();
        }
        $this->outgoing_headers[] = "Server: $this->title Server v$this->version";
        preg_match('/\$Revisio' . 'n: ([^ ]+)/', $this->revision, $rev);
        $this->outgoing_headers[] = "X-SOAP-Server: $this->title/$this->version (".$rev[1].")";
        // Let the Web server decide about this
        //$this->outgoing_headers[] = "Connection: Close\r\n";
        $payload = $this->getHTTPBody($payload);
        $type = $this->getHTTPContentType();
        $charset = $this->getHTTPContentTypeCharset();
        $this->outgoing_headers[] = "Content-Type: $type" . ($charset ? '; charset=' . $charset : '');
        //begin code to compress payload - by John
        // NOTE: there is no way to know whether the Web server will also compress
        // this data.
        if (strlen($payload) > 1024 && isset($this->headers) && isset($this->headers['accept-encoding'])) {
            if (strstr($this->headers['accept-encoding'], 'gzip')) {
                if (function_exists('gzencode')) {
                    if (isset($this->debug_flag) && $this->debug_flag) {
                        $payload .= "<!-- Content being gzipped -->";
                    }
                    $this->outgoing_headers[] = "Content-Encoding: gzip";
                    $payload = gzencode($payload);
                } else {
                    if (isset($this->debug_flag) && $this->debug_flag) {
                        $payload .= "<!-- Content will not be gzipped: no gzencode -->";
                    }
                }
            } elseif (strstr($this->headers['accept-encoding'], 'deflate')) {
                // Note: MSIE requires gzdeflate output (no Zlib header and checksum),
                // instead of gzcompress output,
                // which conflicts with HTTP 1.1 spec (http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.5)
                if (function_exists('gzdeflate')) {
                    if (isset($this->debug_flag) && $this->debug_flag) {
                        $payload .= "<!-- Content being deflated -->";
                    }
                    $this->outgoing_headers[] = "Content-Encoding: deflate";
                    $payload = gzdeflate($payload);
                } else {
                    if (isset($this->debug_flag) && $this->debug_flag) {
                        $payload .= "<!-- Content will not be deflated: no gzcompress -->";
                    }
                }
            }
        }
        //end code
        $this->outgoing_headers[] = "Content-Length: ".strlen($payload);
        reset($this->outgoing_headers);
        foreach($this->outgoing_headers as $hdr){
            header($hdr, false);
        }
        print $payload;
        $this->response = join("\r\n",$this->outgoing_headers)."\r\n\r\n".$payload;
    }
    
    /**
     * takes the value that was created by parsing the request
     * and compares to the method's signature, if available.
     *
     * @param	string	$operation	The operation to be invoked
     * @param	array	$request	The array of parameter values
     * @return	boolean	Whether the operation was found
     * @access   private
     */
    function verify_method($operation,$request){
        if(isset($this->wsdl) && is_object($this->wsdl)){
            if($this->wsdl->getOperationData($operation)){
                return true;
            }
        } elseif(isset($this->operations[$operation])){
            return true;
        }
        return false;
    }
    
    /**
     * processes SOAP message received from client
     *
     * @param	array	$headers	The HTTP headers
     * @param	string	$data		unprocessed request data from client
     * @return	mixed	value of the message, decoded into a PHP type
     * @access   private
     */
    function parseRequest($headers, $data) {
        $this->debug('Entering parseRequest() for data of length ' . strlen($data) . ' headers:');
        $this->appendDebug($this->varDump($headers));
        if (!isset($headers['content-type'])) {
            $this->setError('Request not of type text/xml (no content-type header)');
            return false;
        }
        if (!strstr($headers['content-type'], 'text/xml')) {
            $this->setError('Request not of type text/xml');
            return false;
        }
        if (strpos($headers['content-type'], '=')) {
            $enc = str_replace('"', '', substr(strstr($headers["content-type"], '='), 1));
            $this->debug('Got response encoding: ' . $enc);
            if(preg_match('/^(ISO-8859-1|US-ASCII|UTF-8)$/i',$enc)){
                $this->xml_encoding = strtoupper($enc);
            } else {
                $this->xml_encoding = 'US-ASCII';
            }
        } else {
            // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
            $this->xml_encoding = 'ISO-8859-1';
        }
        $this->debug('Use encoding: ' . $this->xml_encoding . ' when creating nusoap_parser');
        // parse response, get soap parser obj
        $parser = new nusoap_parser($data,$this->xml_encoding,'',$this->decode_utf8);
        // parser debug
        $this->debug("parser debug: \n".$parser->getDebug());
        // if fault occurred during message parsing
        if($err = $parser->getError()){
            $this->result = 'fault: error in msg parsing: '.$err;
            $this->fault('SOAP-ENV:Client',"error in msg parsing:\n".$err);
            // else successfully parsed request into soapval object
        } else {
            // get/set methodname
            $this->methodURI = $parser->root_struct_namespace;
            $this->methodname = $parser->root_struct_name;
            $this->debug('methodname: '.$this->methodname.' methodURI: '.$this->methodURI);
            $this->debug('calling parser->get_soapbody()');
            $this->methodparams = $parser->get_soapbody();
            // get SOAP headers
            $this->requestHeaders = $parser->getHeaders();
            // get SOAP Header
            $this->requestHeader = $parser->get_soapheader();
            // add document for doclit support
            $this->document = $parser->document;
        }
    }
    
    /**
     * gets the HTTP body for the current response.
     *
     * @param string $soapmsg The SOAP payload
     * @return string The HTTP body, which includes the SOAP payload
     * @access private
     */
    function getHTTPBody($soapmsg) {
        return $soapmsg;
    }
    
    /**
     * gets the HTTP content type for the current response.
     *
     * Note: getHTTPBody must be called before this.
     *
     * @return string the HTTP content type for the current response.
     * @access private
     */
    function getHTTPContentType() {
        return 'text/xml';
    }
    
    /**
     * gets the HTTP content type charset for the current response.
     * returns false for non-text content types.
     *
     * Note: getHTTPBody must be called before this.
     *
     * @return string the HTTP content type charset for the current response.
     * @access private
     */
    function getHTTPContentTypeCharset() {
        return $this->soap_defencoding;
    }
    
    /**
     * add a method to the dispatch map (this has been replaced by the register method)
     *
     * @param    string $methodname
     * @param    string $in array of input values
     * @param    string $out array of output values
     * @access   public
     * @deprecated
     */
    function add_to_map($methodname,$in,$out){
        $this->operations[$methodname] = array('name' => $methodname,'in' => $in,'out' => $out);
    }
    
    /**
     * register a service function with the server
     *
     * @param    string $name the name of the PHP function, class.method or class..method
     * @param    array $in assoc array of input values: key = param name, value = param type
     * @param    array $out assoc array of output values: key = param name, value = param type
     * @param	mixed $namespace the element namespace for the method or false
     * @param	mixed $soapaction the soapaction for the method or false
     * @param	mixed $style optional (rpc|document) or false Note: when 'document' is specified, parameter and return wrappers are created for you automatically
     * @param	mixed $use optional (encoded|literal) or false
     * @param	string $documentation optional Description to include in WSDL
     * @param	string $encodingStyle optional (usually 'http://schemas.xmlsoap.org/soap/encoding/' for encoded)
     * @access   public
     */
    function register($name,$in=array(),$out=array(),$namespace=false,$soapaction=false,$style=false,$use=false,$documentation='',$encodingStyle=''){
        global $HTTP_SERVER_VARS;
        
        if($this->externalWSDLURL){
            die('You cannot bind to an external WSDL file, and register methods outside of it! Please choose either WSDL or no WSDL.');
        }
        if (! $name) {
            die('You must specify a name when you register an operation');
        }
        if (!is_array($in)) {
            die('You must provide an array for operation inputs');
        }
        if (!is_array($out)) {
            die('You must provide an array for operation outputs');
        }
        if(false == $namespace) {
        }
        if(false == $soapaction) {
            if (isset($_SERVER)) {
                $SERVER_NAME = $_SERVER['SERVER_NAME'];
                $SCRIPT_NAME = isset($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : $_SERVER['SCRIPT_NAME'];
                $HTTPS = isset($_SERVER['HTTPS']) ? $_SERVER['HTTPS'] : (isset($HTTP_SERVER_VARS['HTTPS']) ? $HTTP_SERVER_VARS['HTTPS'] : 'off');
            } elseif (isset($HTTP_SERVER_VARS)) {
                $SERVER_NAME = $HTTP_SERVER_VARS['SERVER_NAME'];
                $SCRIPT_NAME = isset($HTTP_SERVER_VARS['PHP_SELF']) ? $HTTP_SERVER_VARS['PHP_SELF'] : $HTTP_SERVER_VARS['SCRIPT_NAME'];
                $HTTPS = isset($HTTP_SERVER_VARS['HTTPS']) ? $HTTP_SERVER_VARS['HTTPS'] : 'off';
            } else {
                $this->setError("Neither _SERVER nor HTTP_SERVER_VARS is available");
            }
            if ($HTTPS == '1' || $HTTPS == 'on') {
                $SCHEME = 'https';
            } else {
                $SCHEME = 'http';
            }
            $soapaction = "$SCHEME://$SERVER_NAME$SCRIPT_NAME/$name";
        }
        if(false == $style) {
            $style = "rpc";
        }
        if(false == $use) {
            $use = "encoded";
        }
        if ($use == 'encoded' && $encodingStyle == '') {
            $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
        }
        
        $this->operations[$name] = array(
            'name' => $name,
            'in' => $in,
            'out' => $out,
            'namespace' => $namespace,
            'soapaction' => $soapaction,
            'style' => $style);
        if($this->wsdl){
            $this->wsdl->addOperation($name,$in,$out,$namespace,$soapaction,$style,$use,$documentation,$encodingStyle);
        }
        return true;
    }
    
    /**
     * Specify a fault to be returned to the client.
     * This also acts as a flag to the server that a fault has occured.
     *
     * @param	string $faultcode
     * @param	string $faultstring
     * @param	string $faultactor
     * @param	string $faultdetail
     * @access   public
     */
    function fault($faultcode,$faultstring,$faultactor='',$faultdetail=''){
        if ($faultdetail == '' && $this->debug_flag) {
            $faultdetail = $this->getDebug();
        }
        $this->fault = new nusoap_fault($faultcode,$faultactor,$faultstring,$faultdetail);
        $this->fault->soap_defencoding = $this->soap_defencoding;
    }
    
    /**
     * Sets up wsdl object.
     * Acts as a flag to enable internal WSDL generation
     *
     * @param string $serviceName, name of the service
     * @param mixed $namespace optional 'tns' service namespace or false
     * @param mixed $endpoint optional URL of service endpoint or false
     * @param string $style optional (rpc|document) WSDL style (also specified by operation)
     * @param string $transport optional SOAP transport
     * @param mixed $schemaTargetNamespace optional 'types' targetNamespace for service schema or false
     */
    function configureWSDL($serviceName,$namespace = false,$endpoint = false,$style='rpc', $transport = 'http://schemas.xmlsoap.org/soap/http', $schemaTargetNamespace = false)
    {
        global $HTTP_SERVER_VARS;
        
        if (isset($_SERVER)) {
            $SERVER_NAME = $_SERVER['SERVER_NAME'];
            $SERVER_PORT = $_SERVER['SERVER_PORT'];
            $SCRIPT_NAME = isset($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : $_SERVER['SCRIPT_NAME'];
            $HTTPS = isset($_SERVER['HTTPS']) ? $_SERVER['HTTPS'] : (isset($HTTP_SERVER_VARS['HTTPS']) ? $HTTP_SERVER_VARS['HTTPS'] : 'off');
        } elseif (isset($HTTP_SERVER_VARS)) {
            $SERVER_NAME = $HTTP_SERVER_VARS['SERVER_NAME'];
            $SERVER_PORT = $HTTP_SERVER_VARS['SERVER_PORT'];
            $SCRIPT_NAME = isset($HTTP_SERVER_VARS['PHP_SELF']) ? $HTTP_SERVER_VARS['PHP_SELF'] : $HTTP_SERVER_VARS['SCRIPT_NAME'];
            $HTTPS = isset($HTTP_SERVER_VARS['HTTPS']) ? $HTTP_SERVER_VARS['HTTPS'] : 'off';
        } else {
            $this->setError("Neither _SERVER nor HTTP_SERVER_VARS is available");
        }
        // If server name has port number attached then strip it (else port number gets duplicated in WSDL output) (occurred using lighttpd and FastCGI)
        $colon = strpos($SERVER_NAME,":");
        if ($colon) {
            $SERVER_NAME = substr($SERVER_NAME, 0, $colon);
        }
        if ($SERVER_PORT == 80) {
            $SERVER_PORT = '';
        } else {
            $SERVER_PORT = ':' . $SERVER_PORT;
        }
        if(false == $namespace) {
            $namespace = "http://$SERVER_NAME/soap/$serviceName";
        }
        
        if(false == $endpoint) {
            if ($HTTPS == '1' || $HTTPS == 'on') {
                $SCHEME = 'https';
            } else {
                $SCHEME = 'http';
            }
            $endpoint = "$SCHEME://$SERVER_NAME$SERVER_PORT$SCRIPT_NAME";
        }
        
        if(false == $schemaTargetNamespace) {
            $schemaTargetNamespace = $namespace;
        }
        
        $this->wsdl = new wsdl;
        $this->wsdl->serviceName = $serviceName;
        $this->wsdl->endpoint = $endpoint;
        $this->wsdl->namespaces['tns'] = $namespace;
        $this->wsdl->namespaces['soap'] = 'http://schemas.xmlsoap.org/wsdl/soap/';
        $this->wsdl->namespaces['wsdl'] = 'http://schemas.xmlsoap.org/wsdl/';
        if ($schemaTargetNamespace != $namespace) {
            $this->wsdl->namespaces['types'] = $schemaTargetNamespace;
        }
        $this->wsdl->schemas[$schemaTargetNamespace][0] = new nusoap_xmlschema('', '', $this->wsdl->namespaces);
        if ($style == 'document') {
            $this->wsdl->schemas[$schemaTargetNamespace][0]->schemaInfo['elementFormDefault'] = 'qualified';
        }
        $this->wsdl->schemas[$schemaTargetNamespace][0]->schemaTargetNamespace = $schemaTargetNamespace;
        $this->wsdl->schemas[$schemaTargetNamespace][0]->imports['http://schemas.xmlsoap.org/soap/encoding/'][0] = array('location' => '', 'loaded' => true);
        $this->wsdl->schemas[$schemaTargetNamespace][0]->imports['http://schemas.xmlsoap.org/wsdl/'][0] = array('location' => '', 'loaded' => true);
        $this->wsdl->bindings[$serviceName.'Binding'] = array(
            'name'=>$serviceName.'Binding',
            'style'=>$style,
            'transport'=>$transport,
            'portType'=>$serviceName.'PortType');
        $this->wsdl->ports[$serviceName.'Port'] = array(
            'binding'=>$serviceName.'Binding',
            'location'=>$endpoint,
            'bindingType'=>'http://schemas.xmlsoap.org/wsdl/soap/');
    }
}

<?php
namespace com\linways\base\soap;
/**
 *
 * nusoap_parser class parses SOAP XML messages into native PHP values
 *
 * @author   Dietrich Ayala <dietrich@ganx4.com>
 * @author   Scott Nichol <snichol@users.sourceforge.net>
 * @version  $Id: nusoap.php,v 1.123 2010/04/26 20:15:08 snichol Exp $
 * @access   public
 */
class nusoap_parser extends nusoap_base {
    
    var $xml = '';
    var $xml_encoding = '';
    var $method = '';
    var $root_struct = '';
    var $root_struct_name = '';
    var $root_struct_namespace = '';
    var $root_header = '';
    var $document = '';			// incoming SOAP body (text)
    // determines where in the message we are (envelope,header,body,method)
    var $status = '';
    var $position = 0;
    var $depth = 0;
    var $default_namespace = '';
    var $namespaces = array();
    var $message = array();
    var $parent = '';
    var $fault = false;
    var $fault_code = '';
    var $fault_str = '';
    var $fault_detail = '';
    var $depth_array = array();
    var $debug_flag = true;
    var $soapresponse = NULL;	// parsed SOAP Body
    var $soapheader = NULL;		// parsed SOAP Header
    var $responseHeaders = '';	// incoming SOAP headers (text)
    var $body_position = 0;
    // for multiref parsing:
    // array of id => pos
    var $ids = array();
    // array of id => hrefs => pos
    var $multirefs = array();
    // toggle for auto-decoding element content
    var $decode_utf8 = true;
    
    /**
     * constructor that actually does the parsing
     *
     * @param    string $xml SOAP message
     * @param    string $encoding character encoding scheme of message
     * @param    string $method method for which XML is parsed (unused?)
     * @param    string $decode_utf8 whether to decode UTF-8 to ISO-8859-1
     * @access   public
     */
    function nusoap_parser($xml,$encoding='UTF-8',$method='',$decode_utf8=true){
        parent::nusoap_base();
        $this->xml = $xml;
        $this->xml_encoding = $encoding;
        $this->method = $method;
        $this->decode_utf8 = $decode_utf8;
        
        // Check whether content has been read.
        if(!empty($xml)){
            // Check XML encoding
            $pos_xml = strpos($xml, '<?xml');
            if ($pos_xml !== FALSE) {
                $xml_decl = substr($xml, $pos_xml, strpos($xml, '?>', $pos_xml + 2) - $pos_xml + 1);
                if (preg_match("/encoding=[\"']([^\"']*)[\"']/", $xml_decl, $res)) {
                    $xml_encoding = $res[1];
                    if (strtoupper($xml_encoding) != $encoding) {
                        $err = "Charset from HTTP Content-Type '" . $encoding . "' does not match encoding from XML declaration '" . $xml_encoding . "'";
                        $this->debug($err);
                        if ($encoding != 'ISO-8859-1' || strtoupper($xml_encoding) != 'UTF-8') {
                            $this->setError($err);
                            return;
                        }
                        // when HTTP says ISO-8859-1 (the default) and XML says UTF-8 (the typical), assume the other endpoint is just sloppy and proceed
                    } else {
                        $this->debug('Charset from HTTP Content-Type matches encoding from XML declaration');
                    }
                } else {
                    $this->debug('No encoding specified in XML declaration');
                }
            } else {
                $this->debug('No XML declaration');
            }
            $this->debug('Entering nusoap_parser(), length='.strlen($xml).', encoding='.$encoding);
            // Create an XML parser - why not xml_parser_create_ns?
            $this->parser = xml_parser_create($this->xml_encoding);
            // Set the options for parsing the XML data.
            //xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
            xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
            xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, $this->xml_encoding);
            // Set the object for the parser.
            xml_set_object($this->parser, $this);
            // Set the element handlers for the parser.
            xml_set_element_handler($this->parser, 'start_element','end_element');
            xml_set_character_data_handler($this->parser,'character_data');
            
            // Parse the XML file.
            if(!xml_parse($this->parser,$xml,true)){
                // Display an error message.
                $err = sprintf('XML error parsing SOAP payload on line %d: %s',
                    xml_get_current_line_number($this->parser),
                    xml_error_string(xml_get_error_code($this->parser)));
                $this->debug($err);
                $this->debug("XML payload:\n" . $xml);
                $this->setError($err);
            } else {
                $this->debug('in nusoap_parser ctor, message:');
                $this->appendDebug($this->varDump($this->message));
                $this->debug('parsed successfully, found root struct: '.$this->root_struct.' of name '.$this->root_struct_name);
                // get final value
                $this->soapresponse = $this->message[$this->root_struct]['result'];
                // get header value
                if($this->root_header != '' && isset($this->message[$this->root_header]['result'])){
                    $this->soapheader = $this->message[$this->root_header]['result'];
                }
                // resolve hrefs/ids
                if(sizeof($this->multirefs) > 0){
                    foreach($this->multirefs as $id => $hrefs){
                        $this->debug('resolving multirefs for id: '.$id);
                        $idVal = $this->buildVal($this->ids[$id]);
                        if (is_array($idVal) && isset($idVal['!id'])) {
                            unset($idVal['!id']);
                        }
                        foreach($hrefs as $refPos => $ref){
                            $this->debug('resolving href at pos '.$refPos);
                            $this->multirefs[$id][$refPos] = $idVal;
                        }
                    }
                }
            }
            xml_parser_free($this->parser);
        } else {
            $this->debug('xml was empty, didn\'t parse!');
            $this->setError('xml was empty, didn\'t parse!');
        }
    }
    
    /**
     * start-element handler
     *
     * @param    resource $parser XML parser object
     * @param    string $name element name
     * @param    array $attrs associative array of attributes
     * @access   private
     */
    function start_element($parser, $name, $attrs) {
        // position in a total number of elements, starting from 0
        // update class level pos
        $pos = $this->position++;
        // and set mine
        $this->message[$pos] = array('pos' => $pos,'children'=>'','cdata'=>'');
        // depth = how many levels removed from root?
        // set mine as current global depth and increment global depth value
        $this->message[$pos]['depth'] = $this->depth++;
        
        // else add self as child to whoever the current parent is
        if($pos != 0){
            $this->message[$this->parent]['children'] .= '|'.$pos;
        }
        // set my parent
        $this->message[$pos]['parent'] = $this->parent;
        // set self as current parent
        $this->parent = $pos;
        // set self as current value for this depth
        $this->depth_array[$this->depth] = $pos;
        // get element prefix
        if(strpos($name,':')){
            // get ns prefix
            $prefix = substr($name,0,strpos($name,':'));
            // get unqualified name
            $name = substr(strstr($name,':'),1);
        }
        // set status
        if ($name == 'Envelope' && $this->status == '') {
            $this->status = 'envelope';
        } elseif ($name == 'Header' && $this->status == 'envelope') {
            $this->root_header = $pos;
            $this->status = 'header';
        } elseif ($name == 'Body' && $this->status == 'envelope'){
            $this->status = 'body';
            $this->body_position = $pos;
            // set method
        } elseif($this->status == 'body' && $pos == ($this->body_position+1)) {
            $this->status = 'method';
            $this->root_struct_name = $name;
            $this->root_struct = $pos;
            $this->message[$pos]['type'] = 'struct';
            $this->debug("found root struct $this->root_struct_name, pos $this->root_struct");
        }
        // set my status
        $this->message[$pos]['status'] = $this->status;
        // set name
        $this->message[$pos]['name'] = htmlspecialchars($name);
        // set attrs
        $this->message[$pos]['attrs'] = $attrs;
        
        // loop through atts, logging ns and type declarations
        $attstr = '';
        foreach($attrs as $key => $value){
            $key_prefix = $this->getPrefix($key);
            $key_localpart = $this->getLocalPart($key);
            // if ns declarations, add to class level array of valid namespaces
            if($key_prefix == 'xmlns'){
                if(preg_match('/^http:\/\/www.w3.org\/[0-9]{4}\/XMLSchema$/',$value)){
                    $this->XMLSchemaVersion = $value;
                    $this->namespaces['xsd'] = $this->XMLSchemaVersion;
                    $this->namespaces['xsi'] = $this->XMLSchemaVersion.'-instance';
                }
                $this->namespaces[$key_localpart] = $value;
                // set method namespace
                if($name == $this->root_struct_name){
                    $this->methodNamespace = $value;
                }
                // if it's a type declaration, set type
            } elseif($key_localpart == 'type'){
                if (isset($this->message[$pos]['type']) && $this->message[$pos]['type'] == 'array') {
                    // do nothing: already processed arrayType
                } else {
                    $value_prefix = $this->getPrefix($value);
                    $value_localpart = $this->getLocalPart($value);
                    $this->message[$pos]['type'] = $value_localpart;
                    $this->message[$pos]['typePrefix'] = $value_prefix;
                    if(isset($this->namespaces[$value_prefix])){
                        $this->message[$pos]['type_namespace'] = $this->namespaces[$value_prefix];
                    } else if(isset($attrs['xmlns:'.$value_prefix])) {
                        $this->message[$pos]['type_namespace'] = $attrs['xmlns:'.$value_prefix];
                    }
                    // should do something here with the namespace of specified type?
                }
            } elseif($key_localpart == 'arrayType'){
                $this->message[$pos]['type'] = 'array';
                /* do arrayType ereg here
                 [1]    arrayTypeValue    ::=    atype asize
                 [2]    atype    ::=    QName rank*
                 [3]    rank    ::=    '[' (',')* ']'
                 [4]    asize    ::=    '[' length~ ']'
                 [5]    length    ::=    nextDimension* Digit+
                 [6]    nextDimension    ::=    Digit+ ','
                 */
                $expr = '/([A-Za-z0-9_]+):([A-Za-z]+[A-Za-z0-9_]+)\[([0-9]+),?([0-9]*)\]/';
                if(preg_match($expr,$value,$regs)){
                    $this->message[$pos]['typePrefix'] = $regs[1];
                    $this->message[$pos]['arrayTypePrefix'] = $regs[1];
                    if (isset($this->namespaces[$regs[1]])) {
                        $this->message[$pos]['arrayTypeNamespace'] = $this->namespaces[$regs[1]];
                    } else if (isset($attrs['xmlns:'.$regs[1]])) {
                        $this->message[$pos]['arrayTypeNamespace'] = $attrs['xmlns:'.$regs[1]];
                    }
                    $this->message[$pos]['arrayType'] = $regs[2];
                    $this->message[$pos]['arraySize'] = $regs[3];
                    $this->message[$pos]['arrayCols'] = $regs[4];
                }
                // specifies nil value (or not)
            } elseif ($key_localpart == 'nil'){
                $this->message[$pos]['nil'] = ($value == 'true' || $value == '1');
                // some other attribute
            } elseif ($key != 'href' && $key != 'xmlns' && $key_localpart != 'encodingStyle' && $key_localpart != 'root') {
                $this->message[$pos]['xattrs']['!' . $key] = $value;
            }
            
            if ($key == 'xmlns') {
                $this->default_namespace = $value;
            }
            // log id
            if($key == 'id'){
                $this->ids[$value] = $pos;
            }
            // root
            if($key_localpart == 'root' && $value == 1){
                $this->status = 'method';
                $this->root_struct_name = $name;
                $this->root_struct = $pos;
                $this->debug("found root struct $this->root_struct_name, pos $pos");
            }
            // for doclit
            $attstr .= " $key=\"$value\"";
        }
        // get namespace - must be done after namespace atts are processed
        if(isset($prefix)){
            $this->message[$pos]['namespace'] = $this->namespaces[$prefix];
            $this->default_namespace = $this->namespaces[$prefix];
        } else {
            $this->message[$pos]['namespace'] = $this->default_namespace;
        }
        if($this->status == 'header'){
            if ($this->root_header != $pos) {
                $this->responseHeaders .= "<" . (isset($prefix) ? $prefix . ':' : '') . "$name$attstr>";
            }
        } elseif($this->root_struct_name != ''){
            $this->document .= "<" . (isset($prefix) ? $prefix . ':' : '') . "$name$attstr>";
        }
    }
    
    /**
     * end-element handler
     *
     * @param    resource $parser XML parser object
     * @param    string $name element name
     * @access   private
     */
    function end_element($parser, $name) {
        // position of current element is equal to the last value left in depth_array for my depth
        $pos = $this->depth_array[$this->depth--];
        
        // get element prefix
        if(strpos($name,':')){
            // get ns prefix
            $prefix = substr($name,0,strpos($name,':'));
            // get unqualified name
            $name = substr(strstr($name,':'),1);
        }
        
        // build to native type
        if(isset($this->body_position) && $pos > $this->body_position){
            // deal w/ multirefs
            if(isset($this->message[$pos]['attrs']['href'])){
                // get id
                $id = substr($this->message[$pos]['attrs']['href'],1);
                // add placeholder to href array
                $this->multirefs[$id][$pos] = 'placeholder';
                // add set a reference to it as the result value
                $this->message[$pos]['result'] =& $this->multirefs[$id][$pos];
                // build complexType values
            } elseif($this->message[$pos]['children'] != ''){
                // if result has already been generated (struct/array)
                if(!isset($this->message[$pos]['result'])){
                    $this->message[$pos]['result'] = $this->buildVal($pos);
                }
                // build complexType values of attributes and possibly simpleContent
            } elseif (isset($this->message[$pos]['xattrs'])) {
                if (isset($this->message[$pos]['nil']) && $this->message[$pos]['nil']) {
                    $this->message[$pos]['xattrs']['!'] = null;
                } elseif (isset($this->message[$pos]['cdata']) && trim($this->message[$pos]['cdata']) != '') {
                    if (isset($this->message[$pos]['type'])) {
                        $this->message[$pos]['xattrs']['!'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$pos]['type'], isset($this->message[$pos]['type_namespace']) ? $this->message[$pos]['type_namespace'] : '');
                    } else {
                        $parent = $this->message[$pos]['parent'];
                        if (isset($this->message[$parent]['type']) && ($this->message[$parent]['type'] == 'array') && isset($this->message[$parent]['arrayType'])) {
                            $this->message[$pos]['xattrs']['!'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$parent]['arrayType'], isset($this->message[$parent]['arrayTypeNamespace']) ? $this->message[$parent]['arrayTypeNamespace'] : '');
                        } else {
                            $this->message[$pos]['xattrs']['!'] = $this->message[$pos]['cdata'];
                        }
                    }
                }
                $this->message[$pos]['result'] = $this->message[$pos]['xattrs'];
                // set value of simpleType (or nil complexType)
            } else {
                //$this->debug('adding data for scalar value '.$this->message[$pos]['name'].' of value '.$this->message[$pos]['cdata']);
                if (isset($this->message[$pos]['nil']) && $this->message[$pos]['nil']) {
                    $this->message[$pos]['xattrs']['!'] = null;
                } elseif (isset($this->message[$pos]['type'])) {
                    $this->message[$pos]['result'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$pos]['type'], isset($this->message[$pos]['type_namespace']) ? $this->message[$pos]['type_namespace'] : '');
                } else {
                    $parent = $this->message[$pos]['parent'];
                    if (isset($this->message[$parent]['type']) && ($this->message[$parent]['type'] == 'array') && isset($this->message[$parent]['arrayType'])) {
                        $this->message[$pos]['result'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$parent]['arrayType'], isset($this->message[$parent]['arrayTypeNamespace']) ? $this->message[$parent]['arrayTypeNamespace'] : '');
                    } else {
                        $this->message[$pos]['result'] = $this->message[$pos]['cdata'];
                    }
                }
                
                /* add value to parent's result, if parent is struct/array
                 $parent = $this->message[$pos]['parent'];
                 if($this->message[$parent]['type'] != 'map'){
                 if(strtolower($this->message[$parent]['type']) == 'array'){
                 $this->message[$parent]['result'][] = $this->message[$pos]['result'];
                 } else {
                 $this->message[$parent]['result'][$this->message[$pos]['name']] = $this->message[$pos]['result'];
                 }
                 }
                 */
            }
        }
        
        // for doclit
        if($this->status == 'header'){
            if ($this->root_header != $pos) {
                $this->responseHeaders .= "</" . (isset($prefix) ? $prefix . ':' : '') . "$name>";
            }
        } elseif($pos >= $this->root_struct){
            $this->document .= "</" . (isset($prefix) ? $prefix . ':' : '') . "$name>";
        }
        // switch status
        if ($pos == $this->root_struct){
            $this->status = 'body';
            $this->root_struct_namespace = $this->message[$pos]['namespace'];
        } elseif ($pos == $this->root_header) {
            $this->status = 'envelope';
        } elseif ($name == 'Body' && $this->status == 'body') {
            $this->status = 'envelope';
        } elseif ($name == 'Header' && $this->status == 'header') { // will never happen
            $this->status = 'envelope';
        } elseif ($name == 'Envelope' && $this->status == 'envelope') {
            $this->status = '';
        }
        // set parent back to my parent
        $this->parent = $this->message[$pos]['parent'];
    }
    
    /**
     * element content handler
     *
     * @param    resource $parser XML parser object
     * @param    string $data element content
     * @access   private
     */
    function character_data($parser, $data){
        $pos = $this->depth_array[$this->depth];
        if ($this->xml_encoding=='UTF-8'){
            // TODO: add an option to disable this for folks who want
            // raw UTF-8 that, e.g., might not map to iso-8859-1
            // TODO: this can also be handled with xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, "ISO-8859-1");
            if($this->decode_utf8){
                $data = utf8_decode($data);
            }
        }
        $this->message[$pos]['cdata'] .= $data;
        // for doclit
        if($this->status == 'header'){
            $this->responseHeaders .= $data;
        } else {
            $this->document .= $data;
        }
    }
    
    /**
     * get the parsed message (SOAP Body)
     *
     * @return	mixed
     * @access   public
     * @deprecated	use get_soapbody instead
     */
    function get_response(){
        return $this->soapresponse;
    }
    
    /**
     * get the parsed SOAP Body (NULL if there was none)
     *
     * @return	mixed
     * @access   public
     */
    function get_soapbody(){
        return $this->soapresponse;
    }
    
    /**
     * get the parsed SOAP Header (NULL if there was none)
     *
     * @return	mixed
     * @access   public
     */
    function get_soapheader(){
        return $this->soapheader;
    }
    
    /**
     * get the unparsed SOAP Header
     *
     * @return	string XML or empty if no Header
     * @access   public
     */
    function getHeaders(){
        return $this->responseHeaders;
    }
    
    /**
     * decodes simple types into PHP variables
     *
     * @param    string $value value to decode
     * @param    string $type XML type to decode
     * @param    string $typens XML type namespace to decode
     * @return	mixed PHP value
     * @access   private
     */
    function decodeSimple($value, $type, $typens) {
        // TODO: use the namespace!
        if ((!isset($type)) || $type == 'string' || $type == 'long' || $type == 'unsignedLong') {
            return (string) $value;
        }
        if ($type == 'int' || $type == 'integer' || $type == 'short' || $type == 'byte') {
            return (int) $value;
        }
        if ($type == 'float' || $type == 'double' || $type == 'decimal') {
            return (double) $value;
        }
        if ($type == 'boolean') {
            if (strtolower($value) == 'false' || strtolower($value) == 'f') {
                return false;
            }
            return (boolean) $value;
        }
        if ($type == 'base64' || $type == 'base64Binary') {
            $this->debug('Decode base64 value');
            return base64_decode($value);
        }
        // obscure numeric types
        if ($type == 'nonPositiveInteger' || $type == 'negativeInteger'
            || $type == 'nonNegativeInteger' || $type == 'positiveInteger'
            || $type == 'unsignedInt'
            || $type == 'unsignedShort' || $type == 'unsignedByte') {
                return (int) $value;
            }
            // bogus: parser treats array with no elements as a simple type
            if ($type == 'array') {
                return array();
            }
            // everything else
            return (string) $value;
    }
    
    /**
     * builds response structures for compound values (arrays/structs)
     * and scalars
     *
     * @param    integer $pos position in node tree
     * @return	mixed	PHP value
     * @access   private
     */
    function buildVal($pos){
        if(!isset($this->message[$pos]['type'])){
            $this->message[$pos]['type'] = '';
        }
        $this->debug('in buildVal() for '.$this->message[$pos]['name']."(pos $pos) of type ".$this->message[$pos]['type']);
        // if there are children...
        if($this->message[$pos]['children'] != ''){
            $this->debug('in buildVal, there are children');
            $children = explode('|',$this->message[$pos]['children']);
            array_shift($children); // knock off empty
            // md array
            if(isset($this->message[$pos]['arrayCols']) && $this->message[$pos]['arrayCols'] != ''){
                $r=0; // rowcount
                $c=0; // colcount
                foreach($children as $child_pos){
                    $this->debug("in buildVal, got an MD array element: $r, $c");
                    $params[$r][] = $this->message[$child_pos]['result'];
                    $c++;
                    if($c == $this->message[$pos]['arrayCols']){
                        $c = 0;
                        $r++;
                    }
                }
                // array
            } elseif($this->message[$pos]['type'] == 'array' || $this->message[$pos]['type'] == 'Array'){
                $this->debug('in buildVal, adding array '.$this->message[$pos]['name']);
                foreach($children as $child_pos){
                    $params[] = &$this->message[$child_pos]['result'];
                }
                // apache Map type: java hashtable
            } elseif($this->message[$pos]['type'] == 'Map' && $this->message[$pos]['type_namespace'] == 'http://xml.apache.org/xml-soap'){
                $this->debug('in buildVal, Java Map '.$this->message[$pos]['name']);
                foreach($children as $child_pos){
                    $kv = explode("|",$this->message[$child_pos]['children']);
                    $params[$this->message[$kv[1]]['result']] = &$this->message[$kv[2]]['result'];
                }
                // generic compound type
                //} elseif($this->message[$pos]['type'] == 'SOAPStruct' || $this->message[$pos]['type'] == 'struct') {
            } else {
                // Apache Vector type: treat as an array
                $this->debug('in buildVal, adding Java Vector or generic compound type '.$this->message[$pos]['name']);
                if ($this->message[$pos]['type'] == 'Vector' && $this->message[$pos]['type_namespace'] == 'http://xml.apache.org/xml-soap') {
                    $notstruct = 1;
                } else {
                    $notstruct = 0;
                }
                //
                foreach($children as $child_pos){
                    if($notstruct){
                        $params[] = &$this->message[$child_pos]['result'];
                    } else {
                        if (isset($params[$this->message[$child_pos]['name']])) {
                            // de-serialize repeated element name into an array
                            if ((!is_array($params[$this->message[$child_pos]['name']])) || (!isset($params[$this->message[$child_pos]['name']][0]))) {
                                $params[$this->message[$child_pos]['name']] = array($params[$this->message[$child_pos]['name']]);
                            }
                            $params[$this->message[$child_pos]['name']][] = &$this->message[$child_pos]['result'];
                        } else {
                            $params[$this->message[$child_pos]['name']] = &$this->message[$child_pos]['result'];
                        }
                    }
                }
            }
            if (isset($this->message[$pos]['xattrs'])) {
                $this->debug('in buildVal, handling attributes');
                foreach ($this->message[$pos]['xattrs'] as $n => $v) {
                    $params[$n] = $v;
                }
            }
            // handle simpleContent
            if (isset($this->message[$pos]['cdata']) && trim($this->message[$pos]['cdata']) != '') {
                $this->debug('in buildVal, handling simpleContent');
                if (isset($this->message[$pos]['type'])) {
                    $params['!'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$pos]['type'], isset($this->message[$pos]['type_namespace']) ? $this->message[$pos]['type_namespace'] : '');
                } else {
                    $parent = $this->message[$pos]['parent'];
                    if (isset($this->message[$parent]['type']) && ($this->message[$parent]['type'] == 'array') && isset($this->message[$parent]['arrayType'])) {
                        $params['!'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$parent]['arrayType'], isset($this->message[$parent]['arrayTypeNamespace']) ? $this->message[$parent]['arrayTypeNamespace'] : '');
                    } else {
                        $params['!'] = $this->message[$pos]['cdata'];
                    }
                }
            }
            $ret = is_array($params) ? $params : array();
            $this->debug('in buildVal, return:');
            $this->appendDebug($this->varDump($ret));
            return $ret;
            } else {
                $this->debug('in buildVal, no children, building scalar');
                $cdata = isset($this->message[$pos]['cdata']) ? $this->message[$pos]['cdata'] : '';
                if (isset($this->message[$pos]['type'])) {
                    $ret = $this->decodeSimple($cdata, $this->message[$pos]['type'], isset($this->message[$pos]['type_namespace']) ? $this->message[$pos]['type_namespace'] : '');
                    $this->debug("in buildVal, return: $ret");
                    return $ret;
                }
                $parent = $this->message[$pos]['parent'];
                if (isset($this->message[$parent]['type']) && ($this->message[$parent]['type'] == 'array') && isset($this->message[$parent]['arrayType'])) {
                    $ret = $this->decodeSimple($cdata, $this->message[$parent]['arrayType'], isset($this->message[$parent]['arrayTypeNamespace']) ? $this->message[$parent]['arrayTypeNamespace'] : '');
                    $this->debug("in buildVal, return: $ret");
                    return $ret;
                }
                $ret = $this->message[$pos]['cdata'];
                $this->debug("in buildVal, return: $ret");
                return $ret;
            }
        }
    }

<?php
namespace com\linways\base\soap;
/**
 * Backward compatibility
 */
class XMLSchema extends nusoap_xmlschema {
}

<?php
namespace com\linways\base\soap;

class soapclient extends nusoap_client {
}

<?php
namespace com\linways\base\constant;

class UserType
{
    const STAFF = "STAFF";
    const STUDENT = "STUDENT";
    const ADMIN = "ADMIN";
    const HR_ADMIN="HR_ADMIN";
    const LIBRARY_ADMIN = "LIBRARY_ADMIN";
    const PLACEMENT_ADMIN="PLACEMENT_ADMIN";
    const ADMISSION_ADMIN="ADMISSION_ADMIN";
    const FEE_ADMIN = "FEE_ADMIN";
    const SYSTEM = "SYSTEM";
}

<?php

namespace com\linways\base\toggles;


/**
 * Class FeatureToggle - Temporary feature toggle implementation
 * @package com\linways\base\toggles
 * @deprecated This should not be used in production version. Instead create custom classes
 * which implements FeatureToggleInterface
 */
class FeatureToggle implements FeatureToggleInterface
{
    private $toggles = [];
    // this method will be defined in the child class
    public function initToggles(){}

    public function _isEnabled($feature){
        $this->toggles = $this->initToggles();

        if(!isset($this->toggles[$feature])){
            error_log('Undefined Feature Toggle: '+ $feature);
            return false;
        }elseif (is_callable($this->toggles[$feature])){
            return $this->toggles[$feature]();
        }
        return $this->toggles[$feature];
    }


    public static function isEnabled($feature){
        $instance = new static(); // Create instance of the child class, which used to call this static function
        return $instance->_isEnabled($feature);
    }
}<?php
/**
 * Created by PhpStorm.
 * User: rohithb
 * Date: 16/10/18
 * Time: 4:46 PM
 */

namespace com\linways\base\toggles;


abstract class AbstractToggleMigration
{
    /**
     * @var \com\linways\base\dto\Toggle[]
     */
    private $toggles;
    public function __construct()
    {
        $this->toggles = $this->setToggles();
    }

    /**
     * Returns the details of the toggle.
     * @return \com\linways\base\dto\Toggle[]
     */
    public function getToggles(){
        return $this->toggles;
    }

    public abstract function setToggles();
}<?php


namespace com\linways\base\toggles;


interface FeatureToggleInterface
{
    function initToggles();
    static function isEnabled($feature);
}<?php
namespace com\linways\base\hooks;

use voku\helper\Hooks;


/**
 * Extended From PHP Hooks(voku/php-hooks).
 * @author gadheyan
 */
class LinwaysHooks extends Hooks
{
    protected static $_instance = null;

    protected function __construct()
    {
        parent::__construct();
        $this->loadAllHooks();
    }

    // To prevent any object or instance of that class to be cloned
    protected function __clone()
    {}

    /**
     * Loads all attached hooks and returns the singleton instance
     * @return Hooks
     */
    public static function getInstance()
    {
        static $instance;
        if (null === $instance) {
            $instance = new self();
        }
        return $instance;
    }

    /**
     * Loads all attached hooks.
     * In each project the constant `HOOKS_LOADER` should be defined and it should contain
     * path to a file which includes all required hooks.
     */
    private function loadAllHooks(){
        if(defined('HOOKS_LOADER')){
          $hooks = $this;
          require_once HOOKS_LOADER;
        }
    }
}
<?php
namespace com\linways\base\mcrypt;

class CryptAES
{
    protected $cipher     = MCRYPT_RIJNDAEL_128;
    protected $mode       = MCRYPT_MODE_CBC;
    protected $pad_method = NULL;
    public function set_cipher($cipher)
    {
        $this->cipher = $cipher;
    }
    
    public function set_mode($mode)
    {
        $this->mode = $mode;
    }
    
    public function set_iv($iv)
    {
        $this->iv = $iv;
    }
    
    public function set_key($secret_key)
    {
        $this->secret_key = $secret_key;
    }
    
    public function require_pkcs5()
    {
        $this->pad_method = 'pkcs5';
    }
    
    protected function pad_or_unpad($str, $ext)
    {
        if (is_null($this->pad_method))
        {
            return $str;
        }
        else
        {
            $func_name = __CLASS__ . '::' . $this->pad_method . '_' . $ext . 'pad';
            if (is_callable($func_name))
            {
                $size = mcrypt_get_block_size($this->cipher, $this->mode);
                return call_user_func($func_name, $str, $size);
            }
        }
        
        return $str;
    }
    
    protected function pad($str)
    {
        return $this->pad_or_unpad($str, '');
    }
    
    protected function unpad($str)
    {
        return $this->pad_or_unpad($str, 'un');
    }
    
    
    public function encrypt($str)
    {
        $str = $this->pad($str);
        $td = mcrypt_module_open($this->cipher, '', $this->mode, '');
        
        if (empty($this->iv))
        {
            $iv = @mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
        }
        else
        {
            $iv = $this->iv;
        }
        
        mcrypt_generic_init($td, $this->secret_key, $iv);
        $cyper_text = mcrypt_generic($td, $str);
        $rt = base64_encode($cyper_text);
        // $rt = bin2hex($cyper_text);
        mcrypt_generic_deinit($td);
        mcrypt_module_close($td);
        
        return $rt;
    }
    
    public function decrypt($str){
        $td = mcrypt_module_open($this->cipher, '', $this->mode, '');
        
        if (empty($this->iv))
        {
            $iv = @mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
        }
        else
        {
            $iv = $this->iv;
        }
        
        mcrypt_generic_init($td, $this->secret_key, $iv);
        $decrypted_text = mdecrypt_generic($td, base64_decode($str));
        // $decrypted_text = mdecrypt_generic($td, self::hex2bin($str));
        $rt = $decrypted_text;
        mcrypt_generic_deinit($td);
        mcrypt_module_close($td);
        
        return $this->unpad($rt);
    }
    
    public static function hex2bin($hexdata) {
        $bindata = '';
        $length = strlen($hexdata);
        for ($i=0; $i < $length; $i += 2)
        {
            $bindata .= chr(hexdec(substr($hexdata, $i, 2)));
        }
        return $bindata;
    }
    
    public static function pkcs5_pad($text, $blocksize)
    {
        $pad = $blocksize - (strlen($text) % $blocksize);
        return $text . str_repeat(chr($pad), $pad);
    }
    
    public static function pkcs5_unpad($text)
    {
        $pad = ord($text{strlen($text) - 1});
        if ($pad > strlen($text)) return false;
        if (strspn($text, chr($pad), strlen($text) - $pad) != $pad) return false;
        return substr($text, 0, -1 * $pad);
    }
}

/*$aes = new CryptAES();
 $aes->require_pkcs5();
 
 $aes->set_cipher(MCRYPT_RIJNDAEL_128);
 $aes->set_mode(MCRYPT_MODE_CBC);
 $aes->set_iv('2112842308FFQCOY');
 echo $set_iv;
 $aes->set_key('8660558953GPQKOD');
 
 $txn_id =  rand(5, 15);
 $date = date('Y-m-d');
 
 //$payload=
 $payload="techprocess";
 $checksumkey =  SHA1($payload);
 
 echo $checksumkey.'<br/>';
 
 $payloadWithSha1 = $payload.'|hash='.$checksumkey;
 
 
 
 echo $a = $aes->encrypt($payloadWithSha1);*/

?>

<?php
namespace com\linways\base\queue;

use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
use com\linways\base\exception\TaskQueueException;
/**
 * For queuing tasks and running services asyncronously.
 *
 * @author Rohith Bhaskaran
 */
class Queue {
    const TASK_QUEUE = "TASK_QUEUE";

    private $host;
    private $port;
    private $user;
    private $password;
    private $queueName;
    private $connection;
    private $channel;

    private $conf = [];

    /**
     * establishConnection establish connection to RabbitMQ server
     * and creates/select channel for task queue.
     *
     * @return void/Exception
     * @throws TaskQueueException
     */
    public function establishConnection()
    {
        try{
            $this->connection = new AMQPStreamConnection($this->host, $this->port, $this->user, $this->password);
            $this->channel = $this->connection->channel();
            $this->channel->queue_declare($this->queueName , false, true, false, false);
        }catch(\Exception $e){
            throw new TaskQueueException ( TaskQueueException::RABBITMQ_CONNECTION_FAILED,  $e->getMessage() );
        }
    }

    /**
     * Establishes the connection with the server and set the configuration variale
     *
     * @param [Array] $conf     Array of conf file paths, This will be included before running a task
     *                          example : $conf = [getenv('AMS_CONFIG'), getenv('AMS_CONFIG_2')] or $conf = []
     * @throws TaskQueueException
     */
    public function __construct($conf = [], $queueName = null){
        global $RABBITMQ_HOST, $RABBITMQ_PORT, $RABBITMQ_USER, $RABBITMQ_PASSWORD;
        if (!isset ( $RABBITMQ_HOST ) || !isset ( $RABBITMQ_USER ) || !isset ( $RABBITMQ_PASSWORD )) {
            throw new TaskQueueException ( TaskQueueException::RABBITMQ_CREDENTIALS_REQUIRED, "Credentials for RabbitMQ server is required" );
        }
        if(empty($queueName)){
            $this->queueName = self::TASK_QUEUE;
        }
        else {
            $this->queueName = $queueName;
        }
        $this->conf = $conf;
        $this->host = $RABBITMQ_HOST;
        $this->port = isset($RABBITMQ_PORT)? $RABBITMQ_PORT: 5672;
        $this->user = $RABBITMQ_USER;
        $this->password = $RABBITMQ_PASSWORD;
        $this->establishConnection();
    }

    /**
     * enqueue - To push a command to the task queue
     *
     * @param array $data         Parameters to be passed to the task runner for the command to execute
     * @return void
     */
    public function enqueue($data){
        $data["conf"] = $data["conf"]?$data["conf"]:$this->conf;
        $data = json_encode($data);
        $msg = new AMQPMessage($data,array('delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT));
        $this->channel->basic_publish($msg, '', $this->queueName);
    }

    /**
     * basicConsume  Waits indefinitely while listening to the task queue
     *
     * @param [function] $msgHandler  Function for handling the messages.
     *                                This function will be called when a message  (PhpAmqpLib\Message\AMQPMessage)
     *                                is received with the message as parameter
     *
     * @return void
     * @throws TaskQueueException
     */
    public function basicConsume($msgHandler){
        if(empty($msgHandler)){
            throw new TaskQueueException ( TaskQueueException::MESSAGE_HANDLER_REQUIRED, "Message handler is required" );
        }
        $this->channel->basic_qos(null, 70, null);
        $this->channel->basic_consume($this->queueName, '', false, false, false, false, $msgHandler);
        while(count($this->channel->callbacks)) {
            $this->channel->wait();
        }
    }

    /**
     * sendACK - send the acknowledgement for the received message
     *
     * @param [PhpAmqpLib\Message\AMQPMessage] $message  Message received by the message handler
     * @return void
     */
    public static function sendACK($message){
        $message->delivery_info['channel']->basic_ack($message->delivery_info['delivery_tag']);
    }

    /**
     * Closes the channel and connection with RabbitMQ.
     */
    public function __destruct(){
      $this->channel->close();
      $this->connection->close();
    }

}
<?php
namespace com\linways\base\request;

use com\linways\base\dto\PageSupport;

class BaseRequest extends PageSupport
{

    public $sortBy;

    public $sortOrder = "ASC";
    /**
     * get full data for export
     * @var boolean
     */
    public $isExport=FALSE;
    
    public $createdBy;
    public $updatedBy;
}<?php

namespace  com\linways\base\service;

use com\linways\base\email\PHPMailer;
use com\linways\base\exception\EmailException;
use com\linways\base\dto\Email;
use com\linways\base\dto\EmailAttachment;

class EmailService 
{
    // /Condition 1 - Presence of a static member variable
    private static $_instance = null;
    
    // /Condition 2 - Locked down the constructor
    private function __construct()
    {}
    // Prevent any oustide instantiation of this class
    
    // /Condition 3 - Prevent any object or instance of that class to be cloned
    private function __clone()
    {}
    // Prevent any copy of this object
    
    // /Condition 4 - Have a single globally accessible static method
    public static function getInstance()
    {
        if (! is_object(self::$_instance)) // or if( is_null(self::$_instance) ) or if( self::$_instance == null )
            self::$_instance = new self();
        return self::$_instance;
    }
    /**
     *
     * @param Email $email
     */
    private function validateSendEmail($email)
    {
        if($email != NULL)
        {
            if($email->from == NULL  || empty($email->from->email))
            {
                throw new EmailException(EmailException::EMAIL_FROM_NOT_DEFINED,"Email from is not defined.");
                
            }
            
            if(($email->to == NULL  || empty($email->to->email)) && (empty($email->toList) || count($email->toList) == 0))
            {
                throw new EmailException(EmailException::EMAIL_TO_NOT_DEFINED,"Email to is not defined.");
            
            }
        }
        else {
            
            throw new EmailException(EmailException::EMAIL_OBJECT__NOT_DEFINED,"Email Object is null.");
        }
        
    }
    /**
     * Method for sending email in AMS
     * @param Email $email
     * @return true/false
     * 
     * parammeters to set 
     * <p>From,to,subject,body</p> other parameted will take the default value if custom required set those values 
     * .For more info check Email.php class
     */
    public function sendEmail($email)
    {
        
        //Validate send email
        $this->validateSendEmail($email);
        //Currenlty only single reply to possible
        
        $mail=new PHPMailer();
        $mail->SMTPAuth   = true;                  		// enable SMTP authentication
        $mail->SMTPSecure = "ssl";
        //Set email mailer type
        switch ($email->mailerType) {
            case Email::SMTP:
                $mail = $this->setSMTPMailerTypeConfig($mail);
                break;
            case Email::SEND_MAIL:
                $mail = $this->setSendEmailMailerTypeConfig($mail);
                break;
            case Email::MAIL:
                $mail = $this->setMailMailerTypeConfig($mail);
                break;
            default:
                $mail = $this->setSMTPMailerTypeConfig();
                break;
        }
        
        $mail->From = $email->from->email;
        $mail->FromName   = $email->from->name;
        $mail->Subject    = $email->subject;
        $mail->Body       = $email->body;                 // HTML Body
        $mail->AltBody    = $email->body;    					// Text Body
        // set word wrap
        $mail->WordWrap   = $email->wordWrap;
        
        if(!empty($email->to))
        {
            $mail->AddAddress($email->to->email,$email->to->name);
            $mail->AddReplyTo($email->to->email,$email->to->name);
            
        }
       
        $mail->IsHTML($email->isHtml); // send as HTML
        //Email To List  
        if(!empty($email->toList))
        {
            foreach ($email->toList as $emailTo) {
                $mail->AddAddress($emailTo->email,$emailTo->name);
                $mail->AddReplyTo($emailTo->email,$emailTo->name);
            }
        }
        
        //Email CC List
        if(!empty($email->ccList))
        {
            foreach ($email->ccList as $emailCC) {
                $mail->AddCC($emailCC->email,$emailCC->name);
            }
        }
        //Email attachments List
        if(!empty($email->attachments))
        {
            foreach ($email->attachments as $attachment) {
                $mail->AddAttachment($attachment->path,$attachment->fileName,$attachment->encoding,$attachment->type);
            }
        }
        $isSuccess = $mail->Send();
        
        if(!$isSuccess)
        {
            throw new EmailException(EmailException::EMAIL_SEND_FAILED,$mail->ErrorInfo);
        }
        
        return $isSuccess;
    }
    /**
     * set SMTP mailerType configuration as per mailer type
     * @param PHPMailer $mail
     */
    private  function setSMTPMailerTypeConfig($mail)
    {
        global  $SMTPHost, $SMTPPort, $mailID, $mailpassword;
        //
        if($mail != null)
        {
            $mail->Mailer = Email::SMTP;
            // sets the prefix to the servier
            $mail->Host       = $SMTPHost;      				// sets GMAIL as the SMTP server
            $mail->Port       = $SMTPPort;                  // set the SMTP port
            $mail->Username   = $mailID;  						// GMAIL username
            $mail->Password   = $mailpassword;            	// GMAIL password
        }
        
        return $mail;
    }
    
    /**
     * 
     * set sendemail mailerType configuration as per mailer type.
     * <p> Should configure these parameter in conf.php</p>
     * <p> $SEND_EMAIL_HOST, $SEND_EMAIL_PORT, $SEND_EMAIL_USERNAME, $SEND_EMAIL_PASSWORD;
        </p>
     * @param PHPMailer $mail
     */
    private  function setSendEmailMailerTypeConfig($mail)
    {
        global  $SEND_EMAIL_HOST, $SEND_EMAIL_PORT, $SEND_EMAIL_USERNAME, $SEND_EMAIL_PASSWORD;
        //
        if($mail != null)
        {
            $mail->Mailer = Email::SEND_MAIL;
            // sets the prefix to the servier
            $mail->Host       = $SEND_EMAIL_HOST;      				// sets GMAIL as the SMTP server
            $mail->Port       = $SEND_EMAIL_PORT;                  // set the SMTP port
            $mail->Username   = $SEND_EMAIL_USERNAME;  						// GMAIL username
            $mail->Password   = $SEND_EMAIL_PASSWORD;            	// GMAIL password
        }
    
        return $mail;
    }
    /**
     *
     * set sendemail mailerType configuration as per mailer type.
     * <p> Should configure these parameter in conf.php</p>
     * <p>$SEND_MAIL_HOST, $SEND_MAIL_PORT, $SEND_MAIL_USERNAME, $SEND_MAIL_PASSWORD;
       </p>
     * @param PHPMailer $mail
     */
    private  function setMailMailerTypeConfig($mail)
    {
        global  $SEND_MAIL_HOST, $SEND_MAIL_PORT, $SEND_MAIL_USERNAME, $SEND_MAIL_PASSWORD;
        //
        if($mail != null)
        {
            $mail->Mailer = Email::SEND_MAIL;
            // sets the prefix to the servier
            $mail->Host       = $SEND_MAIL_HOST;      				
            $mail->Port       = $SEND_MAIL_PORT;                  
            $mail->Username   = $SEND_MAIL_USERNAME;  						
            $mail->Password   = $SEND_MAIL_PASSWORD;            	
        }
    
        return $mail;
    }
}
?><?php
namespace com\linways\base\helper;

class ResultHandler
{
    /**
     * Method for sending success response
     *
     * @param
     *            $data
     * @param string $msg
     */
    public static function result($msg = null, $data = "")
    {
        echo json_encode(array(
            'success' => true,
            'msg' => $msg,
            'data' => $data,
            'errorCode' => NULL
        ));
    }
    
    /**
     * Method for sending fault response
     *
     * @param string $msg
     * @param string $errorCode
     * @param
     *            $data
     */
    public static function fault($msg = "", $data = null, $errorCode = "")
    {
        echo json_encode(array(
            'success' => false,
            'msg' => $msg,
            'data' => $data,
            'errorCode' => $errorCode
        ));
    }
}<?php
namespace com\linways\base\exception;

class SMSException extends CoreException
{
    const SMS_PROVIDER_NOT_CONFIGURED = "SMS_PROVIDER_NOT_CONFIGURED";
    const SMS_PROVIDER_NOT_FOUND = "SMS_PROVIDER_NOT_FOUND";
    const SMS_PROVIDER_API_EXCEPTION = "SMS_PROVIDER_API_EXCEPTION";
    const SMS_PROVIDER_NOT_INTEGRATED = "SMS_PROVIDER_NOT_INTEGRATED";
    const CONTEXT_EMPTY = "CONTEXT_EMPTY";
    const MOBILE_NO_EMPTY = "MOBILE_NO_EMPTY";
    const MODULE_EMPTY = "MODULE_EMPTY";
}<?php
namespace com\linways\base\exception;

class SQLException extends CoreException
{
    
}<?php
namespace com\linways\base\exception;
use com\linways\base\exception\CoreException;

class TaskQueueException extends CoreException
{
    const CONF_ARRAY_REQUIRED="CONF_ARRAY_IS_REQUIRED";
    const RABBITMQ_CREDENTIALS_REQUIRED = "RABBITMQ_CREDENTIALS_REQUIRED";
    const RABBITMQ_CONNECTION_FAILED = "RABBITMQ_CONNECTION_FAILED";
    const COMMAND_REQUIRED = "COMMAND_REQUIRED";
    const MESSAGE_HANDLER_REQUIRED = "MESSAGE_HANDLER_REQUIRED";    
}<?php
namespace com\linways\base\exception;
use com\linways\base\exception\CoreException;

class EmailException extends CoreException
{
    const EMAIL_FROM_NOT_DEFINED="EMAIL_FROM_NOT_DEFINED";
    const EMAIL_TO_NOT_DEFINED="EMAIL_TO_NOT_DEFINED";
    const EMAIL_OBJECT__NOT_DEFINED="EMAIL_OBJECT__NOT_DEFINED";
    const EMAIL_SEND_FAILED="EMAIL_SEND_FAILED";
}<?php
namespace com\linways\base\exception;

use com\linways\base\exception\CoreException;

class MapperException extends CoreException
{
    const MAPPER_NOT_DEFINED= "MAPPER_NOT_DEFINED";
    const MAPPER_PRIMARY_KEY_NOT_DEFINED = "MAPPER_PRIMARY_KEY_NOT_DEFINED";
    const MAPPER_PRIMARY_KEY_COLUMN_NOT_DEFINED = "MAPPER_PRIMARY_KEY_COLUMN_NOT_DEFINED";
}<?php
namespace com\linways\base\exception;

class CoreException extends \Exception
{

    const SERVICE_NOT_DEFINED = "SERVICE_NOT_DEFINED";

    const INTERNAL_SERVICE_FAILURE = "INTERNAL_SERVICE_FAILURE";

    const QUERY_EXECUTION_FAILED = "QUERY_EXECUTION_FAILED";

    const CONNECTION_TIMEOUT = "CONNECTION_TIMEOUT";

    const CONNECTION_FAILED = "CONNECTION_FAILED";

    const SORT_ORDER_NOT_DEFINED = "SORT_ORDER_NOT_DEFINED";

    const SORTBY_NOT_DEFINED = "SORTBY_NOT_DEFINED";

    const INVALID_REQUEST = "INVALID_REQUEST";

    const DUPLICATE_ENTRY = "DUPLICATE_ENTRY";
    const CANNOT_ADD_FOREIGN_KEY = "CANNOT_ADD_FOREIGN_KEY";
    const CANNOT_DELETE_OR_UPDATE_ROW_FOREIGN_KEY_FAILED = "CANNOT_DELETE_OR_UPDATE_ROW_FOREIGN_KEY_FAILED";
    

    const INVALID_LOGIN = "INVALID_LOGIN";

    const DB_CONFIG_NOT_SET = "DB_CONFIG_NOT_SET";

    const DB_CONFIG_FILE_PATH_NOT_DEFINED_IN_SERVER = "DB_CONFIG_FILE_PATH_NOT_DEFINED_IN_SERVER";

    const ELASTIC_SEARCH_HOST_NOT_DEFINED = "ELASTIC_SEARCH_HOST_NOT_DEFINED";

    const INVALID_SIGNATURE = "INVALID_SIGNATURE";
    /**
     * Exception code
     *
     * @var string
     */
    protected $code;

    /**
     *
     * @param $message [optional]            
     * @param $code [optional]            
     */
    public function __construct($code = null, $message = null)
    {
        $this->message = $message;
        $this->code = $code;
        $this->logError();
    }

    /**
     * Log exception in error log
     */
    private function logError()
    {
        $errorMsg = "Error Code : '" . $this->code . "' . Error Message : " . $this->message . " Error Trace : " . $this->getTraceAsString();
        error_log($errorMsg);
    }
}<?php

namespace com\linways\base\logging;
use com\linways\base\exception\CoreException;
use Elasticsearch\ClientBuilder;

/**
 * Class ElasticHandler
 * @package com\linways\base\logging
 * This is not monolog handler for elastic search. This is a custom handler for pushing
 * logs in JSON format to elastic search server
 */
class ElasticHandler
{
    private $client;
    // Current docker image is an old one (5.6.) use 6.*
    public function __construct()
    {
        if(getenv('NUCLEUS_CONF')) {
            require_once getenv('NUCLEUS_CONF');
        }
        global $ELASTIC_HOSTS;
        if(empty($ELASTIC_HOSTS)){
            throw new CoreException(CoreException::ELASTIC_SEARCH_HOST_NOT_DEFINED,'Elastic search hosts not defined in nuclues.conf');
        }
        $this->client = ClientBuilder::create()->setHosts($ELASTIC_HOSTS)->build();
    }

    /**
     * Add the given data to elastic search
     * @param $data    The data to be added to elastic
     * @param string $index Name of the index (default: ams-logs)
     * @return array
     */
    public function write($data, $index="ams-logs"){
        //change index every month
        $doc['index'] = $index .'-' . date('mY');
        $doc['type'] = '_doc';
        $doc['body'] = $data;
        return $this->client->index($doc);
    }

    public function handleBatch( array $records){
        //TODO: store multiple records in a single HTTP request for reducing number of requests.
    }
}<?php

namespace com\linways\base\logging;


use com\linways\base\exception\CoreException;
use com\linways\base\exception\TaskQueueException;
use Monolog\Handler\AmqpHandler;
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use PhpAmqpLib\Channel\AMQPChannel;
use PhpAmqpLib\Connection\AMQPSocketConnection;

/**
 *
 * Class Logger
 * @package com\linways\base\logging
 */
class Logger
{
    private static $logger;
    // This is to prevent developers from creating an instance of this class
    private function __construct()
    {
    }

    /**
     * Returns instance of \Monolog\Logger with required handlers and processors.
     * TO TURN OFF THE LOGGER SET 'DISABLE_LOGGING' ENV VARIABLE AS 'true'
     * @param $channelName          name of the log channel
     * @param null $collegeCode     college code [Optional]; This is to override global college code
     * @param null $logFile         absolute path to a file where the logs are stored in DEBUG mode
     * @param string $exchangeName  name of the Rabbit MQ exchange.
     * @return \Monolog\Logger
     */
    public static function getLogger($channelName, $collegeCode = null, $logFile = null, $exchangeName="linways.logs")
    {
        if (is_object(self::$logger))
            return self::$logger;

        self::$logger = new \Monolog\Logger($channelName);
        // Adding college code in the log object
        self::$logger->pushProcessor(function ($record) {
            global $COLLEGE_CODE;
            isset($collegeCode) ? $record['collegeCode'] = $collegeCode : $record['collegeCode'] = $COLLEGE_CODE;
            $record['datetime'] = date("c"); //For elastic search to index timestamp easily- ISO 8601 date
            return $record;
        });
        if(getenv('NUCLEUS_CONF')){
            require_once getenv('NUCLEUS_CONF');
        }

        //If Logging is disabled
        if(getenv('DISABLE_LOGGING') === 'true'){
            self::$logger->pushHandler(new NullHandler());
            return self::$logger;
        }

        if(getenv('DEBUG') === 'true'){
            /** If env var DEBUG is true, logs are stored to a file instead of pushing it to RabbitMQ **/
            if(!isset($logFile)){
                $logFile = dirname(getenv('AMS_CONFIG')) . '/' . $exchangeName;
            }
            self::$logger->pushHandler(new StreamHandler($logFile));
        }else{
            /**
             * if DEBUG is not true, logs are pushed to specified RabbitMQ Exchange
             * NB: The exchange need to be manually created in the rabbit mq server
             **/
            try{
                $rbmqChannel = self::establishMQConnectionAndGetChannel();
                $rbmqhandler = new AmqpHandler($rbmqChannel, $exchangeName);
                self::$logger->pushHandler($rbmqhandler);
            }catch(\Exception $e){
                //TODO: Add some sort of alert mechanism here. Like post to slack or mail every 10 minutes when connection is failed.
                error_log('Logger: RabbitMQ connection failed.');
            }

        }
        return self::$logger;
    }

    /**
     * Establish rabbitmq connection and returns the connection
     * @return AMQPChannel
     * @throws CoreException
     * @throws TaskQueueException
     */
    private static function establishMQConnectionAndGetChannel(){

        global $RABBITMQ_HOST, $RABBITMQ_PORT, $RABBITMQ_USER, $RABBITMQ_PASSWORD;
        if (!isset ( $RABBITMQ_HOST ) || !isset ( $RABBITMQ_USER ) || !isset ( $RABBITMQ_PASSWORD )) {
            throw new TaskQueueException ( TaskQueueException::RABBITMQ_CREDENTIALS_REQUIRED, "Credentials for RabbitMQ server is required" );
        }
        $port = isset($RABBITMQ_PORT)? $RABBITMQ_PORT: 5672;
        try{
            $connection = new AMQPSocketConnection($RABBITMQ_HOST, $port, $RABBITMQ_USER, $RABBITMQ_PASSWORD);
            return new AMQPChannel($connection);
        }catch(\Exception $e){
            throw new CoreException ( TaskQueueException::RABBITMQ_CONNECTION_FAILED,  $e->getMessage() );
        }
    }


}<?php
/**
 * Created by PhpStorm.
 * User: rohithb
 * Date: 19/09/18
 * Time: 12:01 PM
 */

namespace com\linways\base\logging;


/**
 * Base class for creating logging entities
 * Class BaseEntity
 * @package com\linways\base\logging
 */
abstract class BaseEntity implements \JsonSerializable
{
    /**
     * General constructor to set any property.
     * BaseEntity constructor.
     * @param array $params
     */
    public function __construct($params=[])
    {
        foreach ($params as $key => $value) {
            if(empty($this->$key))
                $this->$key = $value;
        }
    }

    public $isDefinedEntity = true;

    abstract public function getQuery();

    /**
     * Assign values for empty properties from the given object
     * @param $object
     */
    public function import($object){
        foreach (get_object_vars($object) as $key => $value) {
            if(empty($this->$key))
                $this->$key = $value;
        }
    }

    public function jsonSerialize()
    {
       $array = get_object_vars($this);
       $array['className'] = get_class($this);
       return $array;
    }
}<?php
namespace com\linways\base\email;

/*~ class.phpmailer.php
 .---------------------------------------------------------------------------.
 |  Software: PHPMailer - PHP email class                                    |
 |   Version: 2.0.0 rc1                                                      |
 |   Contact: via sourceforge.net support pages (also www.codeworxtech.com)  |
 |      Info: http://phpmailer.sourceforge.net                               |
 |   Support: http://sourceforge.net/projects/phpmailer/                     |
 | ------------------------------------------------------------------------- |
 |    Author: Andy Prevost (project admininistrator)                         |
 |    Author: Brent R. Matzelle (original founder)                           |
 | Copyright (c) 2004-2007, Andy Prevost. All Rights Reserved.               |
 | Copyright (c) 2001-2003, Brent R. Matzelle                                |
 | ------------------------------------------------------------------------- |
 |   License: Distributed under the Lesser General Public License (LGPL)     |
 |            http://www.gnu.org/copyleft/lesser.html                        |
 | This program is distributed in the hope that it will be useful - WITHOUT  |
 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or     |
 | FITNESS FOR A PARTICULAR PURPOSE.                                         |
 | ------------------------------------------------------------------------- |
 | We offer a number of paid services (www.codeworxtech.com):                |
 | - Web Hosting on highly optimized fast and secure servers                 |
 | - Technology Consulting                                                   |
 | - Oursourcing (highly qualified programmers and graphic designers)        |
 '---------------------------------------------------------------------------'

 /**
 * PHPMailer - PHP email transport class
 * @package PHPMailer
 * @author Andy Prevost
 * @copyright 2004 - 2007 Andy Prevost
 */

class PHPMailer {

    /////////////////////////////////////////////////
    // PROPERTIES, PUBLIC
    /////////////////////////////////////////////////

    /**
     * Email priority (1 = High, 3 = Normal, 5 = low).
    * @var int
    */
    public $Priority          = 3;

    /**
     * Sets the CharSet of the message.
     * @var string
     */
    public $CharSet           = 'iso-8859-1';

    /**
     * Sets the Content-type of the message.
     * @var string
     */
    public $ContentType       = 'text/plain';

    /**
     * Sets the Encoding of the message. Options for this are "8bit",
     * "7bit", "binary", "base64", and "quoted-printable".
     * @var string
     */
    public $Encoding          = '8bit';

    /**
     * Holds the most recent mailer error message.
     * @var string
     */
    public $ErrorInfo         = '';

    /**
     * Sets the From email address for the message.
     * @var string
     */
    public $From              = 'root@localhost';

    /**
     * Sets the From name of the message.
     * @var string
     */
    public $FromName          = 'Root User';

    /**
     * Sets the Sender email (Return-Path) of the message.  If not empty,
     * will be sent via -f to sendmail or as 'MAIL FROM' in smtp mode.
     * @var string
     */
    public $Sender            = '';

    /**
     * Sets the Subject of the message.
     * @var string
     */
    public $Subject           = '';

    /**
     * Sets the Body of the message.  This can be either an HTML or text body.
     * If HTML then run IsHTML(true).
     * @var string
     */
    public $Body              = '';

    /**
     * Sets the text-only body of the message.  This automatically sets the
     * email to multipart/alternative.  This body can be read by mail
     * clients that do not have HTML email capability such as mutt. Clients
     * that can read HTML will view the normal Body.
     * @var string
     */
    public $AltBody           = '';

    /**
     * Sets word wrapping on the body of the message to a given number of
     * characters.
     * @var int
     */
    public $WordWrap          = 0;

    /**
     * Method to send mail: ("mail", "sendmail", or "smtp").
     * @var string
     */
    public $Mailer            = 'mail';

    /**
     * Sets the path of the sendmail program.
     * @var string
     */
    public $Sendmail          = '/usr/sbin/sendmail';

    /**
     * Path to PHPMailer plugins.  This is now only useful if the SMTP class
     * is in a different directory than the PHP include path.
     * @var string
     */
    public $PluginDir         = '';

    /**
     * Holds PHPMailer version.
     * @var string
     */
    public $Version           = "2.0.0 rc1";

    /**
     * Sets the email address that a reading confirmation will be sent.
     * @var string
     */
    public $ConfirmReadingTo  = '';

    /**
     * Sets the hostname to use in Message-Id and Received headers
     * and as default HELO string. If empty, the value returned
     * by SERVER_NAME is used or 'localhost.localdomain'.
     * @var string
     */
    public $Hostname          = '';

    /////////////////////////////////////////////////
    // PROPERTIES FOR SMTP
    /////////////////////////////////////////////////

    /**
     * Sets the SMTP hosts.  All hosts must be separated by a
    * semicolon.  You can also specify a different port
    * for each host by using this format: [hostname:port]
    * (e.g. "smtp1.example.com:25;smtp2.example.com").
    * Hosts will be tried in order.
    * @var string
    */
    public $Host        = 'localhost';

    /**
     * Sets the default SMTP server port.
     * @var int
     */
    public $Port        = 25;

    /**
     * Sets the SMTP HELO of the message (Default is $Hostname).
     * @var string
     */
    public $Helo        = '';

    /**
     * Sets connection prefix.
     * Options are "", "ssl" or "tls"
     * @var string
     */
    public $SMTPSecure = "";

    /**
     * Sets SMTP authentication. Utilizes the Username and Password variables.
     * @var bool
     */
    public $SMTPAuth     = false;

    /**
     * Sets SMTP username.
     * @var string
     */
    public $Username     = '';

    /**
     * Sets SMTP password.
     * @var string
     */
    public $Password     = '';

    /**
     * Sets the SMTP server timeout in seconds. This function will not
     * work with the win32 version.
     * @var int
     */
    public $Timeout      = 10;

    /**
     * Sets SMTP class debugging on or off.
     * @var bool
     */
    public $SMTPDebug    = false;

    /**
     * Prevents the SMTP connection from being closed after each mail
     * sending.  If this is set to true then to close the connection
     * requires an explicit call to SmtpClose().
     * @var bool
     */
    public $SMTPKeepAlive = false;

    /**
     * Provides the ability to have the TO field process individual
     * emails, instead of sending to entire TO addresses
     * @var bool
     */
    public $SingleTo = false;

    /////////////////////////////////////////////////
    // PROPERTIES, PRIVATE
    /////////////////////////////////////////////////

    private $smtp            = NULL;
    private $to              = array();
    private $cc              = array();
    private $bcc             = array();
    private $ReplyTo         = array();
    private $attachment      = array();
    private $CustomHeader    = array();
    private $message_type    = '';
    private $boundary        = array();
    private $language        = array();
    private $error_count     = 0;
    private $LE              = "\n";

    /////////////////////////////////////////////////
    // METHODS, VARIABLES
    /////////////////////////////////////////////////

    /**
     * Sets message type to HTML.
    * @param bool $bool
    * @return void
    */
    public function IsHTML($bool) {
        if($bool == true) {
            $this->ContentType = 'text/html';
        } else {
            $this->ContentType = 'text/plain';
        }
    }

    /**
     * Sets Mailer to send message using SMTP.
     * @return void
     */
    public function IsSMTP() {
        $this->Mailer = 'smtp';
    }

    /**
     * Sets Mailer to send message using PHP mail() function.
     * @return void
     */
    public function IsMail() {
        $this->Mailer = 'mail';
    }

    /**
     * Sets Mailer to send message using the $Sendmail program.
     * @return void
     */
    public function IsSendmail() {
        $this->Mailer = 'sendmail';
    }

    /**
     * Sets Mailer to send message using the qmail MTA.
     * @return void
     */
    public function IsQmail() {
        $this->Sendmail = '/var/qmail/bin/sendmail';
        $this->Mailer = 'sendmail';
    }

    /////////////////////////////////////////////////
    // METHODS, RECIPIENTS
    /////////////////////////////////////////////////

    /**
     * Adds a "To" address.
    * @param string $address
    * @param string $name
    * @return void
    */
    public function AddAddress($address, $name = '') {
        $cur = count($this->to);
        $this->to[$cur][0] = trim($address);
        $this->to[$cur][1] = $name;
    }

    /**
     * Adds a "Cc" address. Note: this public function works
     * with the SMTP mailer on win32, not with the "mail"
     * mailer.
     * @param string $address
     * @param string $name
     * @return void
     */
    public function AddCC($address, $name = '') {
        $cur = count($this->cc);
        $this->cc[$cur][0] = trim($address);
        $this->cc[$cur][1] = $name;
    }

    /**
     * Adds a "Bcc" address. Note: this function works
     * with the SMTP mailer on win32, not with the "mail"
     * mailer.
     * @param string $address
     * @param string $name
     * @return void
     */
    public function AddBCC($address, $name = '') {
        $cur = count($this->bcc);
        $this->bcc[$cur][0] = trim($address);
        $this->bcc[$cur][1] = $name;
    }

    /**
     * Adds a "Reply-to" address.
     * @param string $address
     * @param string $name
     * @return void
     */
    public function AddReplyTo($address, $name = '') {
        $cur = count($this->ReplyTo);
        $this->ReplyTo[$cur][0] = trim($address);
        $this->ReplyTo[$cur][1] = $name;
    }

    /////////////////////////////////////////////////
    // METHODS, MAIL SENDING
    /////////////////////////////////////////////////

    /**
     * Creates message and assigns Mailer. If the message is
    * not sent successfully then it returns false.  Use the ErrorInfo
    * variable to view description of the error.
    * @return bool
    */
    public function Send() {
        $header = '';
        $body = '';
        $result = true;

        if((count($this->to) + count($this->cc) + count($this->bcc)) < 1) {
            $this->SetError($this->Lang('provide_address'));
            return false;
        }

        /* Set whether the message is multipart/alternative */
        if(!empty($this->AltBody)) {
            $this->ContentType = 'multipart/alternative';
        }

        $this->error_count = 0; // reset errors
        $this->SetMessageType();
        $header .= $this->CreateHeader();
        $body = $this->CreateBody();

        if($body == '') {
            return false;
        }

        /* Choose the mailer */
        switch($this->Mailer) {
            case 'sendmail':
                $result = $this->SendmailSend($header, $body);
                break;
            case 'smtp':
                $result = $this->SmtpSend($header, $body);
                break;
            case 'mail':
                $result = $this->MailSend($header, $body);
                break;
            default:
                $result = $this->MailSend($header, $body);
                break;
                //$this->SetError($this->Mailer . $this->Lang('mailer_not_supported'));
                //$result = false;
                //break;
        }

        return $result;
    }

    /**
     * Sends mail using the $Sendmail program.
     * @access private
     * @return bool
     */
    public function SendmailSend($header, $body) {
        if ($this->Sender != '') {
            $sendmail = sprintf("%s -oi -f %s -t", escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender));
        } else {
            $sendmail = sprintf("%s -oi -t", escapeshellcmd($this->Sendmail));
        }

        if(!@$mail = popen($sendmail, 'w')) {
            $this->SetError($this->Lang('execute') . $this->Sendmail);
            return false;
        }

        fputs($mail, $header);
        fputs($mail, $body);

        $result = pclose($mail) >> 8 & 0xFF;
        if($result != 0) {
            $this->SetError($this->Lang('execute') . $this->Sendmail);
            return false;
        }

        return true;
    }

    /**
     * Sends mail using the PHP mail() function.
     * @access private
     * @return bool
     */
    public function MailSend($header, $body) {

        $to = '';
        for($i = 0; $i < count($this->to); $i++) {
            if($i != 0) { $to .= ', '; }
            $to .= $this->AddrFormat($this->to[$i]);
        }

        $toArr = split(',', $to);

        if ($this->Sender != '' && strlen(ini_get('safe_mode'))< 1) {
            $old_from = ini_get('sendmail_from');
            ini_set('sendmail_from', $this->Sender);
            $params = sprintf("-oi -f %s", $this->Sender);
            if ($this->SingleTo === true && count($toArr) > 1) {
                foreach ($toArr as $key => $val) {
                    $rt = @mail($val, $this->EncodeHeader($this->SecureHeader($this->Subject)), $body, $header, $params);
                }
            } else {
                $rt = @mail($to, $this->EncodeHeader($this->SecureHeader($this->Subject)), $body, $header, $params);
            }
        } else {
            if ($this->SingleTo === true && count($toArr) > 1) {
                foreach ($toArr as $key => $val) {
                    $rt = @mail($val, $this->EncodeHeader($this->SecureHeader($this->Subject)), $body, $header, $params);
                }
            } else {
                $rt = @mail($to, $this->EncodeHeader($this->SecureHeader($this->Subject)), $body, $header);
            }
        }

        if (isset($old_from)) {
            ini_set('sendmail_from', $old_from);
        }

        if(!$rt) {
            $this->SetError($this->Lang('instantiate'));
            return false;
        }

        return true;
    }

    /**
     * Sends mail via SMTP using PhpSMTP (Author:
     * Chris Ryan).  Returns bool.  Returns false if there is a
     * bad MAIL FROM, RCPT, or DATA input.
     * @access private
     * @return bool
     */
    public function SmtpSend($header, $body) {
//         include_once($this->PluginDir . 'class.smtp.php');
        $error = '';
        $bad_rcpt = array();

        if(!$this->SmtpConnect()) {
            return false;
        }

        $smtp_from = ($this->Sender == '') ? $this->From : $this->Sender;
        if(!$this->smtp->Mail($smtp_from)) {
            $error = $this->Lang('from_failed') . $smtp_from;
            $this->SetError($error);
            $this->smtp->Reset();
            return false;
        }

        /* Attempt to send attach all recipients */
        for($i = 0; $i < count($this->to); $i++) {
            if(!$this->smtp->Recipient($this->to[$i][0])) {
                $bad_rcpt[] = $this->to[$i][0];
            }
        }
        for($i = 0; $i < count($this->cc); $i++) {
            if(!$this->smtp->Recipient($this->cc[$i][0])) {
                $bad_rcpt[] = $this->cc[$i][0];
            }
        }
        for($i = 0; $i < count($this->bcc); $i++) {
            if(!$this->smtp->Recipient($this->bcc[$i][0])) {
                $bad_rcpt[] = $this->bcc[$i][0];
            }
        }

        if(count($bad_rcpt) > 0) { // Create error message
            for($i = 0; $i < count($bad_rcpt); $i++) {
                if($i != 0) {
                    $error .= ', ';
                }
                $error .= $bad_rcpt[$i];
            }
            $error = $this->Lang('recipients_failed') . $error;
            $this->SetError($error);
            $this->smtp->Reset();
            return false;
        }

        if(!$this->smtp->Data($header . $body)) {
            $this->SetError($this->Lang('data_not_accepted'));
            $this->smtp->Reset();
            return false;
        }
        if($this->SMTPKeepAlive == true) {
            $this->smtp->Reset();
        } else {
            $this->SmtpClose();
        }

        return true;
    }

    /**
     * Initiates a connection to an SMTP server.  Returns false if the
     * operation failed.
     * @access private
     * @return bool
     */
    public function SmtpConnect() {
        if($this->smtp == NULL) {
            $this->smtp = new SMTP();
        }

        $this->smtp->do_debug = $this->SMTPDebug;
        $hosts = explode(';', $this->Host);
        $index = 0;
        $connection = ($this->smtp->Connected());

        /* Retry while there is no connection */
        while($index < count($hosts) && $connection == false) {
            $hostinfo = array();
            if(preg_match('/^(.+):([0-9]+)$/i', $hosts[$index], $hostinfo)) {
                $host = $hostinfo[1];
                $port = $hostinfo[2];
            } else {
                $host = $hosts[$index];
                $port = $this->Port;
            }

            if($this->smtp->Connect(((!empty($this->SMTPSecure))?$this->SMTPSecure.'://':'').$host, $port, $this->Timeout)) {
                if ($this->Helo != '') {
                    $this->smtp->Hello($this->Helo);
                } else {
                    $this->smtp->Hello($this->ServerHostname());
                }

                $connection = true;
                if($this->SMTPAuth) {
                    if(!$this->smtp->Authenticate($this->Username, $this->Password)) {
                        $this->SetError($this->Lang('authenticate'));
                        $this->smtp->Reset();
                        $connection = false;
                    }
                }
            }
            $index++;
        }
        if(!$connection) {
            $this->SetError($this->Lang('connect_host'));
        }

        return $connection;
    }

    /**
     * Closes the active SMTP session if one exists.
     * @return void
     */
    public function SmtpClose() {
        if($this->smtp != NULL) {
            if($this->smtp->Connected()) {
                $this->smtp->Quit();
                $this->smtp->Close();
            }
        }
    }

    /**
     * Sets the language for all class error messages.  Returns false
     * if it cannot load the language file.  The default language type
     * is English.
     * @param string $lang_type Type of language (e.g. Portuguese: "br")
     * @param string $lang_path Path to the language file directory
     * @access public
     * @return bool
     */
    public function SetLanguage($lang_type, $lang_path = 'language/') {
        if(file_exists($lang_path.'phpmailer.lang-'.$lang_type.'.php')) {
            include($lang_path.'phpmailer.lang-'.$lang_type.'.php');
        } elseif (file_exists($lang_path.'phpmailer.lang-en.php')) {
            include($lang_path.'phpmailer.lang-en.php');
        } else {
            $this->SetError('Could not load language file');
            return false;
        }
        $this->language = $PHPMAILER_LANG;

        return true;
    }

    /////////////////////////////////////////////////
    // METHODS, MESSAGE CREATION
    /////////////////////////////////////////////////

    /**
     * Creates recipient headers.
    * @access private
    * @return string
    */
    public function AddrAppend($type, $addr) {
        $addr_str = $type . ': ';
        $addr_str .= $this->AddrFormat($addr[0]);
        if(count($addr) > 1) {
            for($i = 1; $i < count($addr); $i++) {
                $addr_str .= ', ' . $this->AddrFormat($addr[$i]);
            }
        }
        $addr_str .= $this->LE;

        return $addr_str;
    }

    /**
     * Formats an address correctly.
     * @access private
     * @return string
     */
    public function AddrFormat($addr) {
        if(empty($addr[1])) {
            $formatted = $this->SecureHeader($addr[0]);
        } else {
            $formatted = $this->EncodeHeader($this->SecureHeader($addr[1]), 'phrase') . " <" . $this->SecureHeader($addr[0]) . ">";
        }

        return $formatted;
    }

    /**
     * Wraps message for use with mailers that do not
     * automatically perform wrapping and for quoted-printable.
     * Original written by philippe.
     * @access private
     * @return string
     */
    public function WrapText($message, $length, $qp_mode = false) {
        $soft_break = ($qp_mode) ? sprintf(" =%s", $this->LE) : $this->LE;

        $message = $this->FixEOL($message);
        if (substr($message, -1) == $this->LE) {
            $message = substr($message, 0, -1);
        }

        $line = explode($this->LE, $message);
        $message = '';
        for ($i=0 ;$i < count($line); $i++) {
            $line_part = explode(' ', $line[$i]);
            $buf = '';
            for ($e = 0; $e<count($line_part); $e++) {
                $word = $line_part[$e];
                if ($qp_mode and (strlen($word) > $length)) {
                    $space_left = $length - strlen($buf) - 1;
                    if ($e != 0) {
                        if ($space_left > 20) {
                            $len = $space_left;
                            if (substr($word, $len - 1, 1) == '=') {
                                $len--;
                            } elseif (substr($word, $len - 2, 1) == '=') {
                                $len -= 2;
                            }
                            $part = substr($word, 0, $len);
                            $word = substr($word, $len);
                            $buf .= ' ' . $part;
                            $message .= $buf . sprintf("=%s", $this->LE);
                        } else {
                            $message .= $buf . $soft_break;
                        }
                        $buf = '';
                    }
                    while (strlen($word) > 0) {
                        $len = $length;
                        if (substr($word, $len - 1, 1) == '=') {
                            $len--;
                        } elseif (substr($word, $len - 2, 1) == '=') {
                            $len -= 2;
                        }
                        $part = substr($word, 0, $len);
                        $word = substr($word, $len);

                        if (strlen($word) > 0) {
                            $message .= $part . sprintf("=%s", $this->LE);
                        } else {
                            $buf = $part;
                        }
                    }
                } else {
                    $buf_o = $buf;
                    $buf .= ($e == 0) ? $word : (' ' . $word);

                    if (strlen($buf) > $length and $buf_o != '') {
                        $message .= $buf_o . $soft_break;
                        $buf = $word;
                    }
                }
            }
            $message .= $buf . $this->LE;
        }

        return $message;
    }

    /**
     * Set the body wrapping.
     * @access private
     * @return void
     */
    public function SetWordWrap() {
        if($this->WordWrap < 1) {
            return;
        }

        switch($this->message_type) {
            case 'alt':
                /* fall through */
            case 'alt_attachments':
                $this->AltBody = $this->WrapText($this->AltBody, $this->WordWrap);
                break;
            default:
                $this->Body = $this->WrapText($this->Body, $this->WordWrap);
                break;
        }
    }

    /**
     * Assembles message header.
     * @access private
     * @return string
     */
    public function CreateHeader() {
        $result = '';

        /* Set the boundaries */
        $uniq_id = md5(uniqid(time()));
        $this->boundary[1] = 'b1_' . $uniq_id;
        $this->boundary[2] = 'b2_' . $uniq_id;

        $result .= $this->HeaderLine('Date', $this->RFCDate());
        if($this->Sender == '') {
            $result .= $this->HeaderLine('Return-Path', trim($this->From));
        } else {
            $result .= $this->HeaderLine('Return-Path', trim($this->Sender));
        }

        /* To be created automatically by mail() */
        if($this->Mailer != 'mail') {
            if(count($this->to) > 0) {
                $result .= $this->AddrAppend('To', $this->to);
            } elseif (count($this->cc) == 0) {
                $result .= $this->HeaderLine('To', 'undisclosed-recipients:;');
            }
            if(count($this->cc) > 0) {
                $result .= $this->AddrAppend('Cc', $this->cc);
            }
        }

        $from = array();
        $from[0][0] = trim($this->From);
        $from[0][1] = $this->FromName;
        $result .= $this->AddrAppend('From', $from);

        /* sendmail and mail() extract Cc from the header before sending */
        if((($this->Mailer == 'sendmail') || ($this->Mailer == 'mail')) && (count($this->cc) > 0)) {
            $result .= $this->AddrAppend('Cc', $this->cc);
        }

        /* sendmail and mail() extract Bcc from the header before sending */
        if((($this->Mailer == 'sendmail') || ($this->Mailer == 'mail')) && (count($this->bcc) > 0)) {
            $result .= $this->AddrAppend('Bcc', $this->bcc);
        }

        if(count($this->ReplyTo) > 0) {
            $result .= $this->AddrAppend('Reply-to', $this->ReplyTo);
        }

        /* mail() sets the subject itself */
        if($this->Mailer != 'mail') {
            $result .= $this->HeaderLine('Subject', $this->EncodeHeader($this->SecureHeader($this->Subject)));
        }

        $result .= sprintf("Message-ID: <%s@%s>%s", $uniq_id, $this->ServerHostname(), $this->LE);
        $result .= $this->HeaderLine('X-Priority', $this->Priority);
        $result .= $this->HeaderLine('X-Mailer', 'PHPMailer (phpmailer.sourceforge.net) [version ' . $this->Version . ']');

        if($this->ConfirmReadingTo != '') {
            $result .= $this->HeaderLine('Disposition-Notification-To', '<' . trim($this->ConfirmReadingTo) . '>');
        }

        // Add custom headers
        for($index = 0; $index < count($this->CustomHeader); $index++) {
            $result .= $this->HeaderLine(trim($this->CustomHeader[$index][0]), $this->EncodeHeader(trim($this->CustomHeader[$index][1])));
        }
        $result .= $this->HeaderLine('MIME-Version', '1.0');

        switch($this->message_type) {
            case 'plain':
                $result .= $this->HeaderLine('Content-Transfer-Encoding', $this->Encoding);
                $result .= sprintf("Content-Type: %s; charset=\"%s\"", $this->ContentType, $this->CharSet);
                break;
            case 'attachments':
                /* fall through */
            case 'alt_attachments':
                if($this->InlineImageExists()){
                    $result .= sprintf("Content-Type: %s;%s\ttype=\"text/html\";%s\tboundary=\"%s\"%s", 'multipart/related', $this->LE, $this->LE, $this->boundary[1], $this->LE);
                } else {
                    $result .= $this->HeaderLine('Content-Type', 'multipart/mixed;');
                    $result .= $this->TextLine("\tboundary=\"" . $this->boundary[1] . '"');
                }
                break;
            case 'alt':
                $result .= $this->HeaderLine('Content-Type', 'multipart/alternative;');
                $result .= $this->TextLine("\tboundary=\"" . $this->boundary[1] . '"');
                break;
        }

        if($this->Mailer != 'mail') {
            $result .= $this->LE.$this->LE;
        }

        return $result;
    }

    /**
     * Assembles the message body.  Returns an empty string on failure.
     * @access private
     * @return string
     */
    public function CreateBody() {
        $result = '';

        $this->SetWordWrap();

        switch($this->message_type) {
            case 'alt':
                $result .= $this->GetBoundary($this->boundary[1], '', 'text/plain', '');
                $result .= $this->EncodeString($this->AltBody, $this->Encoding);
                $result .= $this->LE.$this->LE;
                $result .= $this->GetBoundary($this->boundary[1], '', 'text/html', '');
                $result .= $this->EncodeString($this->Body, $this->Encoding);
                $result .= $this->LE.$this->LE;
                $result .= $this->EndBoundary($this->boundary[1]);
                break;
            case 'plain':
                $result .= $this->EncodeString($this->Body, $this->Encoding);
                break;
            case 'attachments':
                $result .= $this->GetBoundary($this->boundary[1], '', '', '');
                $result .= $this->EncodeString($this->Body, $this->Encoding);
                $result .= $this->LE;
                $result .= $this->AttachAll();
                break;
            case 'alt_attachments':
                $result .= sprintf("--%s%s", $this->boundary[1], $this->LE);
                $result .= sprintf("Content-Type: %s;%s" . "\tboundary=\"%s\"%s", 'multipart/alternative', $this->LE, $this->boundary[2], $this->LE.$this->LE);
                $result .= $this->GetBoundary($this->boundary[2], '', 'text/plain', '') . $this->LE; // Create text body
                $result .= $this->EncodeString($this->AltBody, $this->Encoding);
                $result .= $this->LE.$this->LE;
                $result .= $this->GetBoundary($this->boundary[2], '', 'text/html', '') . $this->LE; // Create the HTML body
                $result .= $this->EncodeString($this->Body, $this->Encoding);
                $result .= $this->LE.$this->LE;
                $result .= $this->EndBoundary($this->boundary[2]);
                $result .= $this->AttachAll();
                break;
        }
        if($this->IsError()) {
            $result = '';
        }

        return $result;
    }

    /**
     * Returns the start of a message boundary.
     * @access private
     */
    public function GetBoundary($boundary, $charSet, $contentType, $encoding) {
        $result = '';
        if($charSet == '') {
            $charSet = $this->CharSet;
        }
        if($contentType == '') {
            $contentType = $this->ContentType;
        }
        if($encoding == '') {
            $encoding = $this->Encoding;
        }
        $result .= $this->TextLine('--' . $boundary);
        $result .= sprintf("Content-Type: %s; charset = \"%s\"", $contentType, $charSet);
        $result .= $this->LE;
        $result .= $this->HeaderLine('Content-Transfer-Encoding', $encoding);
        $result .= $this->LE;

        return $result;
    }

    /**
     * Returns the end of a message boundary.
     * @access private
     */
    public function EndBoundary($boundary) {
        return $this->LE . '--' . $boundary . '--' . $this->LE;
    }

    /**
     * Sets the message type.
     * @access private
     * @return void
     */
    public function SetMessageType() {
        if(count($this->attachment) < 1 && strlen($this->AltBody) < 1) {
            $this->message_type = 'plain';
        } else {
            if(count($this->attachment) > 0) {
                $this->message_type = 'attachments';
            }
            if(strlen($this->AltBody) > 0 && count($this->attachment) < 1) {
                $this->message_type = 'alt';
            }
            if(strlen($this->AltBody) > 0 && count($this->attachment) > 0) {
                $this->message_type = 'alt_attachments';
            }
        }
    }

    /* Returns a formatted header line.
     * @access private
     * @return string
     */
    public function HeaderLine($name, $value) {
        return $name . ': ' . $value . $this->LE;
    }

    /**
     * Returns a formatted mail line.
     * @access private
     * @return string
     */
    public function TextLine($value) {
        return $value . $this->LE;
    }

    /////////////////////////////////////////////////
    // CLASS METHODS, ATTACHMENTS
    /////////////////////////////////////////////////

    /**
     * Adds an attachment from a path on the filesystem.
    * Returns false if the file could not be found
    * or accessed.
    * @param string $path Path to the attachment.
    * @param string $name Overrides the attachment name.
    * @param string $encoding File encoding (see $Encoding).
    * @param string $type File extension (MIME) type.
    * @return bool
    */
    public function AddAttachment($path, $name = '', $encoding = 'base64', $type = 'application/octet-stream') {
        if(!@is_file($path)) {
            $this->SetError($this->Lang('file_access') . $path);
            return false;
        }

        $filename = basename($path);
        if($name == '') {
            $name = $filename;
        }

        $cur = count($this->attachment);
        $this->attachment[$cur][0] = $path;
        $this->attachment[$cur][1] = $filename;
        $this->attachment[$cur][2] = $name;
        $this->attachment[$cur][3] = $encoding;
        $this->attachment[$cur][4] = $type;
        $this->attachment[$cur][5] = false; // isStringAttachment
        $this->attachment[$cur][6] = 'attachment';
        $this->attachment[$cur][7] = 0;

        return true;
    }

    /**
     * Attaches all fs, string, and binary attachments to the message.
     * Returns an empty string on failure.
     * @access private
     * @return string
     */
    public function AttachAll() {
        /* Return text of body */
        $mime = array();

        /* Add all attachments */
        for($i = 0; $i < count($this->attachment); $i++) {
            /* Check for string attachment */
            $bString = $this->attachment[$i][5];
            if ($bString) {
                $string = $this->attachment[$i][0];
            } else {
                $path = $this->attachment[$i][0];
            }

            $filename    = $this->attachment[$i][1];
            $name        = $this->attachment[$i][2];
            $encoding    = $this->attachment[$i][3];
            $type        = $this->attachment[$i][4];
            $disposition = $this->attachment[$i][6];
            $cid         = $this->attachment[$i][7];

            $mime[] = sprintf("--%s%s", $this->boundary[1], $this->LE);
            $mime[] = sprintf("Content-Type: %s; name=\"%s\"%s", $type, $name, $this->LE);
            $mime[] = sprintf("Content-Transfer-Encoding: %s%s", $encoding, $this->LE);

            if($disposition == 'inline') {
                $mime[] = sprintf("Content-ID: <%s>%s", $cid, $this->LE);
            }

            $mime[] = sprintf("Content-Disposition: %s; filename=\"%s\"%s", $disposition, $name, $this->LE.$this->LE);

            /* Encode as string attachment */
            if($bString) {
                $mime[] = $this->EncodeString($string, $encoding);
                if($this->IsError()) {
                    return '';
                }
                $mime[] = $this->LE.$this->LE;
            } else {
                $mime[] = $this->EncodeFile($path, $encoding);
                if($this->IsError()) {
                    return '';
                }
                $mime[] = $this->LE.$this->LE;
            }
        }

        $mime[] = sprintf("--%s--%s", $this->boundary[1], $this->LE);

        return join('', $mime);
    }

    /**
     * Encodes attachment in requested format.  Returns an
     * empty string on failure.
     * @access private
     * @return string
     */
    public function EncodeFile ($path, $encoding = 'base64') {
        if(!@$fd = fopen($path, 'rb')) {
            $this->SetError($this->Lang('file_open') . $path);
            return '';
        }
        $magic_quotes = get_magic_quotes_runtime();
        set_magic_quotes_runtime(0);
        $file_buffer = fread($fd, filesize($path));
        $file_buffer = $this->EncodeString($file_buffer, $encoding);
        fclose($fd);
        set_magic_quotes_runtime($magic_quotes);

        return $file_buffer;
    }

    /**
     * Encodes string to requested format. Returns an
     * empty string on failure.
     * @access private
     * @return string
     */
    public function EncodeString ($str, $encoding = 'base64') {
        $encoded = '';
        switch(strtolower($encoding)) {
            case 'base64':
                /* chunk_split is found in PHP >= 3.0.6 */
                $encoded = chunk_split(base64_encode($str), 76, $this->LE);
                break;
            case '7bit':
            case '8bit':
                $encoded = $this->FixEOL($str);
                if (substr($encoded, -(strlen($this->LE))) != $this->LE)
                    $encoded .= $this->LE;
                    break;
            case 'binary':
                $encoded = $str;
                break;
            case 'quoted-printable':
                $encoded = $this->EncodeQP($str);
                break;
            default:
                $this->SetError($this->Lang('encoding') . $encoding);
                break;
        }
        return $encoded;
    }

    /**
     * Encode a header string to best of Q, B, quoted or none.
     * @access private
     * @return string
     */
    public function EncodeHeader ($str, $position = 'text') {
        $x = 0;

        switch (strtolower($position)) {
            case 'phrase':
                if (!preg_match('/[\200-\377]/', $str)) {
                    /* Can't use addslashes as we don't know what value has magic_quotes_sybase. */
                    $encoded = addcslashes($str, "\0..\37\177\\\"");
                    if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) {
                        return ($encoded);
                    } else {
                        return ("\"$encoded\"");
                    }
                }
                $x = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches);
                break;
            case 'comment':
                $x = preg_match_all('/[()"]/', $str, $matches);
                /* Fall-through */
            case 'text':
            default:
                $x += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches);
                break;
        }

        if ($x == 0) {
            return ($str);
        }

        $maxlen = 75 - 7 - strlen($this->CharSet);
        /* Try to select the encoding which should produce the shortest output */
        if (strlen($str)/3 < $x) {
            $encoding = 'B';
            $encoded = base64_encode($str);
            $maxlen -= $maxlen % 4;
            $encoded = trim(chunk_split($encoded, $maxlen, "\n"));
        } else {
            $encoding = 'Q';
            $encoded = $this->EncodeQ($str, $position);
            $encoded = $this->WrapText($encoded, $maxlen, true);
            $encoded = str_replace('='.$this->LE, "\n", trim($encoded));
        }

        $encoded = preg_replace('/^(.*)$/m', " =?".$this->CharSet."?$encoding?\\1?=", $encoded);
        $encoded = trim(str_replace("\n", $this->LE, $encoded));

        return $encoded;
    }

    /**
     * Encode string to quoted-printable.
     * @access private
     * @return string
     */
    public function EncodeQP ($str) {
        $encoded = $this->FixEOL($str);
        if (substr($encoded, -(strlen($this->LE))) != $this->LE) {
            $encoded .= $this->LE;
        }

        /* Replace every high ascii, control and = characters */
        $encoded = preg_replace('/([\000-\010\013\014\016-\037\075\177-\377])/e', "'='.sprintf('%02X', ord('\\1'))", $encoded);
        /* Replace every spaces and tabs when it's the last character on a line */
        $encoded = preg_replace("/([\011\040])".$this->LE."/e", "'='.sprintf('%02X', ord('\\1')).'".$this->LE."'", $encoded);

        /* Maximum line length of 76 characters before CRLF (74 + space + '=') */
        $encoded = $this->WrapText($encoded, 74, true);

        return $encoded;
    }

    /**
     * Encode string to q encoding.
     * @access private
     * @return string
     */
    public function EncodeQ ($str, $position = 'text') {
        /* There should not be any EOL in the string */
        $encoded = preg_replace("[\r\n]", '', $str);

        switch (strtolower($position)) {
            case 'phrase':
                $encoded = preg_replace("/([^A-Za-z0-9!*+\/ -])/e", "'='.sprintf('%02X', ord('\\1'))", $encoded);
                break;
            case 'comment':
                $encoded = preg_replace("/([\(\)\"])/e", "'='.sprintf('%02X', ord('\\1'))", $encoded);
            case 'text':
            default:
                /* Replace every high ascii, control =, ? and _ characters */
                $encoded = preg_replace('/([\000-\011\013\014\016-\037\075\077\137\177-\377])/e',
                "'='.sprintf('%02X', ord('\\1'))", $encoded);
                break;
        }

        /* Replace every spaces to _ (more readable than =20) */
        $encoded = str_replace(' ', '_', $encoded);

        return $encoded;
    }

    /**
     * Adds a string or binary attachment (non-filesystem) to the list.
     * This method can be used to attach ascii or binary data,
     * such as a BLOB record from a database.
     * @param string $string String attachment data.
     * @param string $filename Name of the attachment.
     * @param string $encoding File encoding (see $Encoding).
     * @param string $type File extension (MIME) type.
     * @return void
     */
    public function AddStringAttachment($string, $filename, $encoding = 'base64', $type = 'application/octet-stream') {
        /* Append to $attachment array */
        $cur = count($this->attachment);
        $this->attachment[$cur][0] = $string;
        $this->attachment[$cur][1] = $filename;
        $this->attachment[$cur][2] = $filename;
        $this->attachment[$cur][3] = $encoding;
        $this->attachment[$cur][4] = $type;
        $this->attachment[$cur][5] = true; // isString
        $this->attachment[$cur][6] = 'attachment';
        $this->attachment[$cur][7] = 0;
    }

    /**
     * Adds an embedded attachment.  This can include images, sounds, and
     * just about any other document.  Make sure to set the $type to an
     * image type.  For JPEG images use "image/jpeg" and for GIF images
     * use "image/gif".
     * @param string $path Path to the attachment.
     * @param string $cid Content ID of the attachment.  Use this to identify
     *        the Id for accessing the image in an HTML form.
     * @param string $name Overrides the attachment name.
     * @param string $encoding File encoding (see $Encoding).
     * @param string $type File extension (MIME) type.
     * @return bool
     */
    public function AddEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = 'application/octet-stream') {

        if(!@is_file($path)) {
            $this->SetError($this->Lang('file_access') . $path);
            return false;
        }

        $filename = basename($path);
        if($name == '') {
            $name = $filename;
        }

        /* Append to $attachment array */
        $cur = count($this->attachment);
        $this->attachment[$cur][0] = $path;
        $this->attachment[$cur][1] = $filename;
        $this->attachment[$cur][2] = $name;
        $this->attachment[$cur][3] = $encoding;
        $this->attachment[$cur][4] = $type;
        $this->attachment[$cur][5] = false;
        $this->attachment[$cur][6] = 'inline';
        $this->attachment[$cur][7] = $cid;

        return true;
    }

    /**
     * Returns true if an inline attachment is present.
     * @access private
     * @return bool
     */
    public function InlineImageExists() {
        $result = false;
        for($i = 0; $i < count($this->attachment); $i++) {
            if($this->attachment[$i][6] == 'inline') {
                $result = true;
                break;
            }
        }

        return $result;
    }

    /////////////////////////////////////////////////
    // CLASS METHODS, MESSAGE RESET
    /////////////////////////////////////////////////

    /**
     * Clears all recipients assigned in the TO array.  Returns void.
    * @return void
    */
    public function ClearAddresses() {
        $this->to = array();
    }

    /**
     * Clears all recipients assigned in the CC array.  Returns void.
     * @return void
     */
    public function ClearCCs() {
        $this->cc = array();
    }

    /**
     * Clears all recipients assigned in the BCC array.  Returns void.
     * @return void
     */
    public function ClearBCCs() {
        $this->bcc = array();
    }

    /**
     * Clears all recipients assigned in the ReplyTo array.  Returns void.
     * @return void
     */
    public function ClearReplyTos() {
        $this->ReplyTo = array();
    }

    /**
     * Clears all recipients assigned in the TO, CC and BCC
     * array.  Returns void.
     * @return void
     */
    public function ClearAllRecipients() {
        $this->to = array();
        $this->cc = array();
        $this->bcc = array();
    }

    /**
     * Clears all previously set filesystem, string, and binary
     * attachments.  Returns void.
     * @return void
     */
    public function ClearAttachments() {
        $this->attachment = array();
    }

    /**
     * Clears all custom headers.  Returns void.
     * @return void
     */
    public function ClearCustomHeaders() {
        $this->CustomHeader = array();
    }

    /////////////////////////////////////////////////
    // CLASS METHODS, MISCELLANEOUS
    /////////////////////////////////////////////////

    /**
     * Adds the error message to the error container.
    * Returns void.
    * @access private
    * @return void
    */
    private function SetError($msg) {
        $this->error_count++;
        $this->ErrorInfo = $msg;
    }

    /**
     * Returns the proper RFC 822 formatted date.
     * @access private
     * @return string
     */
    private static function RFCDate() {
        $tz = date('Z');
        $tzs = ($tz < 0) ? '-' : '+';
        $tz = abs($tz);
        $tz = (int)($tz/3600)*100 + ($tz%3600)/60;
        $result = sprintf("%s %s%04d", date('D, j M Y H:i:s'), $tzs, $tz);

        return $result;
    }

    /**
     * Returns the server hostname or 'localhost.localdomain' if unknown.
     * @access private
     * @return string
     */
    private function ServerHostname() {
        if (!empty($this->Hostname)) {
            $result = $this->Hostname;
        } elseif (isset($_SERVER['SERVER_NAME'])) {
            $result = $_SERVER['SERVER_NAME'];
        } else {
            $result = "localhost.localdomain";
        }

        return $result;
    }

    /**
     * Returns a message in the appropriate language.
     * @access private
     * @return string
     */
    private function Lang($key) {
        if(count($this->language) < 1) {
            $this->SetLanguage('en'); // set the default language
        }

        if(isset($this->language[$key]))
        {
            return $this->language[$key];
        }
        else
        {
            return 'Language string failed to load: ' . $key;
        }
    }

    /**
     * Returns true if an error occurred.
     * @return bool
     */
    public function IsError() {
        return ($this->error_count > 0);
    }

    /**
     * Changes every end of line from CR or LF to CRLF.
     * @access private
     * @return string
     */
    private function FixEOL($str) {
        $str = str_replace("\r\n", "\n", $str);
        $str = str_replace("\r", "\n", $str);
        $str = str_replace("\n", $this->LE, $str);
        return $str;
    }

    /**
     * Adds a custom header.
     * @return void
     */
    public function AddCustomHeader($custom_header) {
        $this->CustomHeader[] = explode(':', $custom_header, 2);
    }

    /**
     * Evaluates the message and returns modifications for inline images and backgrounds
     * @access public
     * @return $message
     */
    public function MsgHTML($message) {
        preg_match_all("/(src|background)=\"(.*)\"/Ui", $message, $images);
        if(isset($images[2])) {
            foreach($images[2] as $i => $url) {
                $filename  = basename($url);
                $directory = dirname($url);
                $cid       = 'cid:' . md5($filename);
                $fileParts = split("\.", $filename);
                $ext       = $fileParts[1];
                $mimeType  = $this->_mime_types($ext);
                $message = preg_replace("/".$images[1][$i]."=\"".preg_quote($url, '/')."\"/Ui", $images[1][$i]."=\"".$cid."\"", $message);
                $this->AddEmbeddedImage($url, md5($filename), $filename, 'base64', $mimeType);
            }
        }
        $this->IsHTML(true);
        $this->Body = $message;
        $textMsg = trim(strip_tags($message));
        if ( !empty($textMsg) && empty($this->AltBody) ) {
            $this->AltBody = $textMsg;
        }
        if ( empty($this->AltBody) ) {
            $this->AltBody = 'To view this email message, open the email in with HTML compatibility!' . "\n\n";
        }
    }

    /**
     * Gets the mime type of the embedded or inline image
     * @access private
     * @return mime type of ext
     */
    public function _mime_types($ext = '') {
        $mimes = array(
            'hqx'  =>  'application/mac-binhex40',
            'cpt'   =>  'application/mac-compactpro',
            'doc'   =>  'application/msword',
            'bin'   =>  'application/macbinary',
            'dms'   =>  'application/octet-stream',
            'lha'   =>  'application/octet-stream',
            'lzh'   =>  'application/octet-stream',
            'exe'   =>  'application/octet-stream',
            'class' =>  'application/octet-stream',
            'psd'   =>  'application/octet-stream',
            'so'    =>  'application/octet-stream',
            'sea'   =>  'application/octet-stream',
            'dll'   =>  'application/octet-stream',
            'oda'   =>  'application/oda',
            'pdf'   =>  'application/pdf',
            'ai'    =>  'application/postscript',
            'eps'   =>  'application/postscript',
            'ps'    =>  'application/postscript',
            'smi'   =>  'application/smil',
            'smil'  =>  'application/smil',
            'mif'   =>  'application/vnd.mif',
            'xls'   =>  'application/vnd.ms-excel',
            'ppt'   =>  'application/vnd.ms-powerpoint',
            'wbxml' =>  'application/vnd.wap.wbxml',
            'wmlc'  =>  'application/vnd.wap.wmlc',
            'dcr'   =>  'application/x-director',
            'dir'   =>  'application/x-director',
            'dxr'   =>  'application/x-director',
            'dvi'   =>  'application/x-dvi',
            'gtar'  =>  'application/x-gtar',
            'php'   =>  'application/x-httpd-php',
            'php4'  =>  'application/x-httpd-php',
            'php3'  =>  'application/x-httpd-php',
            'phtml' =>  'application/x-httpd-php',
            'phps'  =>  'application/x-httpd-php-source',
            'js'    =>  'application/x-javascript',
            'swf'   =>  'application/x-shockwave-flash',
            'sit'   =>  'application/x-stuffit',
            'tar'   =>  'application/x-tar',
            'tgz'   =>  'application/x-tar',
            'xhtml' =>  'application/xhtml+xml',
            'xht'   =>  'application/xhtml+xml',
            'zip'   =>  'application/zip',
            'mid'   =>  'audio/midi',
            'midi'  =>  'audio/midi',
            'mpga'  =>  'audio/mpeg',
            'mp2'   =>  'audio/mpeg',
            'mp3'   =>  'audio/mpeg',
            'aif'   =>  'audio/x-aiff',
            'aiff'  =>  'audio/x-aiff',
            'aifc'  =>  'audio/x-aiff',
            'ram'   =>  'audio/x-pn-realaudio',
            'rm'    =>  'audio/x-pn-realaudio',
            'rpm'   =>  'audio/x-pn-realaudio-plugin',
            'ra'    =>  'audio/x-realaudio',
            'rv'    =>  'video/vnd.rn-realvideo',
            'wav'   =>  'audio/x-wav',
            'bmp'   =>  'image/bmp',
            'gif'   =>  'image/gif',
            'jpeg'  =>  'image/jpeg',
            'jpg'   =>  'image/jpeg',
            'jpe'   =>  'image/jpeg',
            'png'   =>  'image/png',
            'tiff'  =>  'image/tiff',
            'tif'   =>  'image/tiff',
            'css'   =>  'text/css',
            'html'  =>  'text/html',
            'htm'   =>  'text/html',
            'shtml' =>  'text/html',
            'txt'   =>  'text/plain',
            'text'  =>  'text/plain',
            'log'   =>  'text/plain',
            'rtx'   =>  'text/richtext',
            'rtf'   =>  'text/rtf',
            'xml'   =>  'text/xml',
            'xsl'   =>  'text/xml',
            'mpeg'  =>  'video/mpeg',
            'mpg'   =>  'video/mpeg',
            'mpe'   =>  'video/mpeg',
            'qt'    =>  'video/quicktime',
            'mov'   =>  'video/quicktime',
            'avi'   =>  'video/x-msvideo',
            'movie' =>  'video/x-sgi-movie',
            'doc'   =>  'application/msword',
            'word'  =>  'application/msword',
            'xl'    =>  'application/excel',
            'eml'   =>  'message/rfc822'
        );
        return ( ! isset($mimes[strtolower($ext)])) ? 'application/x-unknown-content-type' : $mimes[strtolower($ext)];
    }

    /**
     * Set (or reset) Class Objects (variables)
     *
     * Usage Example:
     * $page->set('X-Priority', '3');
     *
     * @access public
     * @param string $name Parameter Name
     * @param mixed $value Parameter Value
     * NOTE: will not work with arrays, there are no arrays to set/reset
     */
    public function set ( $name, $value = '' ) {
        if ( isset($this->$name) ) {
            $this->$name = $value;
        } else {
            $this->SetError('Cannot set or reset variable ' . $name);
            return false;
        }
    }

    /**
     * Read a file from a supplied filename and return it.
     *
     * @access public
     * @param string $filename Parameter File Name
     */
    public function getFile($filename) {
        $return = '';
        if ($fp = fopen($filename, 'rb')) {
            while (!feof($fp)) {
                $return .= fread($fp, 1024);
            }
            fclose($fp);
            return $return;
        } else {
            return false;
        }
    }

    /**
     * Strips newlines to prevent header injection.
     * @access private
     * @param string $str String
     * @return string
     */
    public function SecureHeader($str) {
        $str = trim($str);
        $str = str_replace("\r", "", $str);
        $str = str_replace("\n", "", $str);
        return $str;
    }

}<?php
namespace com\linways\base\email;
/*~ class.smtp.php
 .---------------------------------------------------------------------------.
 |  Software: PHPMailer - PHP email class                                    |
 |   Version: 2.0.0 rc1                                                      |
 |   Contact: via sourceforge.net support pages (also www.codeworxtech.com)  |
 |      Info: http://phpmailer.sourceforge.net                               |
 |   Support: http://sourceforge.net/projects/phpmailer/                     |
 | ------------------------------------------------------------------------- |
 |    Author: Andy Prevost (project admininistrator)                         |
 |    Author: Brent R. Matzelle (original founder)                           |
 | Copyright (c) 2004-2007, Andy Prevost. All Rights Reserved.               |
 | Copyright (c) 2001-2003, Brent R. Matzelle                                |
 | ------------------------------------------------------------------------- |
 |   License: Distributed under the Lesser General Public License (LGPL)     |
 |            http://www.gnu.org/copyleft/lesser.html                        |
 | This program is distributed in the hope that it will be useful - WITHOUT  |
 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or     |
 | FITNESS FOR A PARTICULAR PURPOSE.                                         |
 | ------------------------------------------------------------------------- |
 | We offer a number of paid services (www.codeworxtech.com):                |
 | - Web Hosting on highly optimized fast and secure servers                 |
 | - Technology Consulting                                                   |
 | - Oursourcing (highly qualified programmers and graphic designers)        |
 '---------------------------------------------------------------------------'

 /**
 * SMTP is rfc 821 compliant and implements all the rfc 821 SMTP
 * commands except TURN which will always return a not implemented
 * error. SMTP also provides some utility methods for sending mail
 * to an SMTP server.
 * @package PHPMailer
 * @author Chris Ryan
 */

class SMTP {
    /**
     *  SMTP server port
     *  @var int
     */
    public $SMTP_PORT = 25;

    /**
     *  SMTP reply line ending
     *  @var string
     */
    public $CRLF = "\r\n";

    /**
     *  Sets whether debugging is turned on
     *  @var bool
     */
    public $do_debug;       // the level of debug to perform

    /**
     *  Sets VERP use on/off (default is off)
     *  @var bool
     */
    public $do_verp = false;

    /**#@+
     * @access private
     */
    private $smtp_conn;      // the socket to the server
    private $error;          // error if any on the last call
    private $helo_rply;      // the reply the server sent to us for HELO
    /**#@-*/

    /**
     * Initialize the class so that the data is in a known state.
     * @access public
     * @return void
     */
    public function __construct() {
        $this->smtp_conn = 0;
        $this->error = null;
        $this->helo_rply = null;

        $this->do_debug = 0;
    }

    /*************************************************************
     *                    CONNECTION FUNCTIONS                  *
     ***********************************************************/

    /**
     * Connect to the server specified on the port specified.
     * If the port is not specified use the default SMTP_PORT.
     * If tval is specified then a connection will try and be
     * established with the server for that number of seconds.
     * If tval is not specified the default is 30 seconds to
     * try on the connection.
     *
     * SMTP CODE SUCCESS: 220
     * SMTP CODE FAILURE: 421
     * @access public
     * @return bool
     */
    public function Connect($host,$port=0,$tval=30) {
        /* set the error val to null so there is no confusion */
        $this->error = null;

        /* make sure we are __not__ connected */
        if($this->connected()) {
            /* ok we are connected! what should we do?
             * for now we will just give an error saying we
             * are already connected
             */
            $this->error = array("error" => "Already connected to a server");
            return false;
        }

        if(empty($port)) {
            $port = $this->SMTP_PORT;
        }

        /* connect to the smtp server */
        $this->smtp_conn = fsockopen($host,    // the host of the server
            $port,    // the port to use
            $errno,   // error number if any
            $errstr,  // error message if any
            $tval);   // give up after ? secs
            /* verify we connected properly */
            if(empty($this->smtp_conn)) {
                $this->error = array("error" => "Failed to connect to server",
                    "errno" => $errno,
                    "errstr" => $errstr);
                if($this->do_debug >= 1) {
                    echo "SMTP -> ERROR: " . $this->error["error"] .
                    ": $errstr ($errno)" . $this->CRLF;
                }
                return false;
            }

            /* sometimes the SMTP server takes a little longer to respond
             * so we will give it a longer timeout for the first read
             * - Windows still does not have support for this timeout function
             */
            if(substr(PHP_OS, 0, 3) != "WIN")
                socket_set_timeout($this->smtp_conn, $tval, 0);

                /* get any announcement stuff */
                $announce = $this->get_lines();

                /* set the timeout  of any socket functions at 1/10 of a second */
                //if(function_exists("socket_set_timeout"))
                //   socket_set_timeout($this->smtp_conn, 0, 100000);

                if($this->do_debug >= 2) {
                    echo "SMTP -> FROM SERVER:" . $this->CRLF . $announce;
                }

                return true;
    }

    /**
     * Performs SMTP authentication.  Must be run after running the
     * Hello() method.  Returns true if successfully authenticated.
     * @access public
     * @return bool
     */
    public function Authenticate($username, $password) {
        // Start authentication
        fputs($this->smtp_conn,"AUTH LOGIN" . $this->CRLF);

        $rply = $this->get_lines();
        $code = substr($rply,0,3);

        if($code != 334) {
            $this->error =
            array("error" => "AUTH not accepted from server",
                "smtp_code" => $code,
                "smtp_msg" => substr($rply,4));
            if($this->do_debug >= 1) {
                echo "SMTP -> ERROR: " . $this->error["error"] .
                ": " . $rply . $this->CRLF;
            }
            return false;
        }

        // Send encoded username
        fputs($this->smtp_conn, base64_encode($username) . $this->CRLF);

        $rply = $this->get_lines();
        $code = substr($rply,0,3);

        if($code != 334) {
            $this->error =
            array("error" => "Username not accepted from server",
                "smtp_code" => $code,
                "smtp_msg" => substr($rply,4));
            if($this->do_debug >= 1) {
                echo "SMTP -> ERROR: " . $this->error["error"] .
                ": " . $rply . $this->CRLF;
            }
            return false;
        }

        // Send encoded password
        fputs($this->smtp_conn, base64_encode($password) . $this->CRLF);

        $rply = $this->get_lines();
        $code = substr($rply,0,3);

        if($code != 235) {
            $this->error =
            array("error" => "Password not accepted from server",
                "smtp_code" => $code,
                "smtp_msg" => substr($rply,4));
            if($this->do_debug >= 1) {
                echo "SMTP -> ERROR: " . $this->error["error"] .
                ": " . $rply . $this->CRLF;
            }
            return false;
        }

        return true;
    }

    /**
     * Returns true if connected to a server otherwise false
     * @access private
     * @return bool
     */
    public function Connected() {
        if(!empty($this->smtp_conn)) {
            $sock_status = socket_get_status($this->smtp_conn);
            if($sock_status["eof"]) {
                // hmm this is an odd situation... the socket is
                // valid but we are not connected anymore
                if($this->do_debug >= 1) {
                    echo "SMTP -> NOTICE:" . $this->CRLF .
                    "EOF caught while checking if connected";
                }
                $this->Close();
                return false;
            }
            return true; // everything looks good
        }
        return false;
    }

    /**
     * Closes the socket and cleans up the state of the class.
     * It is not considered good to use this function without
     * first trying to use QUIT.
     * @access public
     * @return void
     */
    public function Close() {
        $this->error = null; // so there is no confusion
        $this->helo_rply = null;
        if(!empty($this->smtp_conn)) {
            // close the connection and cleanup
            fclose($this->smtp_conn);
            $this->smtp_conn = 0;
        }
    }

    /***************************************************************
     *                        SMTP COMMANDS                       *
     *************************************************************/

    /**
     * Issues a data command and sends the msg_data to the server
     * finializing the mail transaction. $msg_data is the message
     * that is to be send with the headers. Each header needs to be
     * on a single line followed by a <CRLF> with the message headers
     * and the message body being seperated by and additional <CRLF>.
     *
     * Implements rfc 821: DATA <CRLF>
     *
     * SMTP CODE INTERMEDIATE: 354
     *     [data]
     *     <CRLF>.<CRLF>
     *     SMTP CODE SUCCESS: 250
     *     SMTP CODE FAILURE: 552,554,451,452
     * SMTP CODE FAILURE: 451,554
     * SMTP CODE ERROR  : 500,501,503,421
     * @access public
     * @return bool
     */
    public function Data($msg_data) {
        $this->error = null; // so no confusion is caused

        if(!$this->connected()) {
            $this->error = array(
                "error" => "Called Data() without being connected");
            return false;
        }

        fputs($this->smtp_conn,"DATA" . $this->CRLF);

        $rply = $this->get_lines();
        $code = substr($rply,0,3);

        if($this->do_debug >= 2) {
            echo "SMTP -> FROM SERVER:" . $this->CRLF . $rply;
        }

        if($code != 354) {
            $this->error =
            array("error" => "DATA command not accepted from server",
                "smtp_code" => $code,
                "smtp_msg" => substr($rply,4));
            if($this->do_debug >= 1) {
                echo "SMTP -> ERROR: " . $this->error["error"] .
                ": " . $rply . $this->CRLF;
            }
            return false;
        }

        /* the server is ready to accept data!
         * according to rfc 821 we should not send more than 1000
         * including the CRLF
         * characters on a single line so we will break the data up
         * into lines by \r and/or \n then if needed we will break
         * each of those into smaller lines to fit within the limit.
         * in addition we will be looking for lines that start with
         * a period '.' and append and additional period '.' to that
         * line. NOTE: this does not count towards are limit.
         */

        // normalize the line breaks so we know the explode works
        $msg_data = str_replace("\r\n","\n",$msg_data);
        $msg_data = str_replace("\r","\n",$msg_data);
        $lines = explode("\n",$msg_data);

        /* we need to find a good way to determine is headers are
         * in the msg_data or if it is a straight msg body
         * currently I am assuming rfc 822 definitions of msg headers
         * and if the first field of the first line (':' sperated)
         * does not contain a space then it _should_ be a header
         * and we can process all lines before a blank "" line as
         * headers.
         */
        $field = substr($lines[0],0,strpos($lines[0],":"));
        $in_headers = false;
        if(!empty($field) && !strstr($field," ")) {
            $in_headers = true;
        }

        $max_line_length = 998; // used below; set here for ease in change

        while(list(,$line) = @each($lines)) {
            $lines_out = null;
            if($line == "" && $in_headers) {
                $in_headers = false;
            }
            // ok we need to break this line up into several smaller lines
            while(strlen($line) > $max_line_length) {
                $pos = strrpos(substr($line,0,$max_line_length)," ");

                // Patch to fix DOS attack
                if(!$pos) {
                    $pos = $max_line_length - 1;
                }

                $lines_out[] = substr($line,0,$pos);
                $line = substr($line,$pos + 1);
                /* if we are processing headers we need to
                 * add a LWSP-char to the front of the new line
                 * rfc 822 on long msg headers
                 */
                if($in_headers) {
                    $line = "\t" . $line;
                }
            }
            $lines_out[] = $line;

            // now send the lines to the server
            while(list(,$line_out) = @each($lines_out)) {
                if(strlen($line_out) > 0)
                {
                    if(substr($line_out, 0, 1) == ".") {
                        $line_out = "." . $line_out;
                    }
                }
                fputs($this->smtp_conn,$line_out . $this->CRLF);
            }
        }

        // ok all the message data has been sent so lets get this
        // over with aleady
        fputs($this->smtp_conn, $this->CRLF . "." . $this->CRLF);

        $rply = $this->get_lines();
        $code = substr($rply,0,3);

        if($this->do_debug >= 2) {
            echo "SMTP -> FROM SERVER:" . $this->CRLF . $rply;
        }

        if($code != 250) {
            $this->error =
            array("error" => "DATA not accepted from server",
                "smtp_code" => $code,
                "smtp_msg" => substr($rply,4));
            if($this->do_debug >= 1) {
                echo "SMTP -> ERROR: " . $this->error["error"] .
                ": " . $rply . $this->CRLF;
            }
            return false;
        }
        return true;
    }

    /**
     * Expand takes the name and asks the server to list all the
     * people who are members of the _list_. Expand will return
     * back and array of the result or false if an error occurs.
     * Each value in the array returned has the format of:
     *     [ <full-name> <sp> ] <path>
     * The definition of <path> is defined in rfc 821
     *
     * Implements rfc 821: EXPN <SP> <string> <CRLF>
     *
     * SMTP CODE SUCCESS: 250
     * SMTP CODE FAILURE: 550
     * SMTP CODE ERROR  : 500,501,502,504,421
     * @access public
     * @return string array
     */
    public function Expand($name) {
        $this->error = null; // so no confusion is caused

        if(!$this->connected()) {
            $this->error = array(
                "error" => "Called Expand() without being connected");
            return false;
        }

        fputs($this->smtp_conn,"EXPN " . $name . $this->CRLF);

        $rply = $this->get_lines();
        $code = substr($rply,0,3);

        if($this->do_debug >= 2) {
            echo "SMTP -> FROM SERVER:" . $this->CRLF . $rply;
        }

        if($code != 250) {
            $this->error =
            array("error" => "EXPN not accepted from server",
                "smtp_code" => $code,
                "smtp_msg" => substr($rply,4));
            if($this->do_debug >= 1) {
                echo "SMTP -> ERROR: " . $this->error["error"] .
                ": " . $rply . $this->CRLF;
            }
            return false;
        }

        // parse the reply and place in our array to return to user
        $entries = explode($this->CRLF,$rply);
        while(list(,$l) = @each($entries)) {
            $list[] = substr($l,4);
        }

        return $list;
    }

    /**
     * Sends the HELO command to the smtp server.
     * This makes sure that we and the server are in
     * the same known state.
     *
     * Implements from rfc 821: HELO <SP> <domain> <CRLF>
     *
     * SMTP CODE SUCCESS: 250
     * SMTP CODE ERROR  : 500, 501, 504, 421
     * @access public
     * @return bool
     */
    public function Hello($host="") {
        $this->error = null; // so no confusion is caused

        if(!$this->connected()) {
            $this->error = array(
                "error" => "Called Hello() without being connected");
            return false;
        }

        // if a hostname for the HELO was not specified determine
        //a suitable one to send
        if(empty($host)) {
            // we need to determine some sort of appopiate default
            // to send to the server
            $host = "localhost";
        }

        // Send extended hello first (RFC 2821)
        if(!$this->SendHello("EHLO", $host))
        {
            if(!$this->SendHello("HELO", $host))
                return false;
        }

        return true;
    }

    /**
     * Sends a HELO/EHLO command.
     * @access private
     * @return bool
     */
    public function SendHello($hello, $host) {
        fputs($this->smtp_conn, $hello . " " . $host . $this->CRLF);

        $rply = $this->get_lines();
        $code = substr($rply,0,3);

        if($this->do_debug >= 2) {
            echo "SMTP -> FROM SERVER: " . $this->CRLF . $rply;
        }

        if($code != 250) {
            $this->error =
            array("error" => $hello . " not accepted from server",
                "smtp_code" => $code,
                "smtp_msg" => substr($rply,4));
            if($this->do_debug >= 1) {
                echo "SMTP -> ERROR: " . $this->error["error"] .
                ": " . $rply . $this->CRLF;
            }
            return false;
        }

        $this->helo_rply = $rply;

        return true;
    }

    /**
     * Gets help information on the keyword specified. If the keyword
     * is not specified then returns generic help, ussually contianing
     * A list of keywords that help is available on. This function
     * returns the results back to the user. It is up to the user to
     * handle the returned data. If an error occurs then false is
     * returned with $this->error set appropiately.
     *
     * Implements rfc 821: HELP [ <SP> <string> ] <CRLF>
     *
     * SMTP CODE SUCCESS: 211,214
     * SMTP CODE ERROR  : 500,501,502,504,421
     * @access public
     * @return string
     */
    public function Help($keyword="") {
        $this->error = null; // to avoid confusion

        if(!$this->connected()) {
            $this->error = array(
                "error" => "Called Help() without being connected");
            return false;
        }

        $extra = "";
        if(!empty($keyword)) {
            $extra = " " . $keyword;
        }

        fputs($this->smtp_conn,"HELP" . $extra . $this->CRLF);

        $rply = $this->get_lines();
        $code = substr($rply,0,3);

        if($this->do_debug >= 2) {
            echo "SMTP -> FROM SERVER:" . $this->CRLF . $rply;
        }

        if($code != 211 && $code != 214) {
            $this->error =
            array("error" => "HELP not accepted from server",
                "smtp_code" => $code,
                "smtp_msg" => substr($rply,4));
            if($this->do_debug >= 1) {
                echo "SMTP -> ERROR: " . $this->error["error"] .
                ": " . $rply . $this->CRLF;
            }
            return false;
        }

        return $rply;
    }

    /**
     * Starts a mail transaction from the email address specified in
     * $from. Returns true if successful or false otherwise. If True
     * the mail transaction is started and then one or more Recipient
     * commands may be called followed by a Data command.
     *
     * Implements rfc 821: MAIL <SP> FROM:<reverse-path> <CRLF>
     *
     * SMTP CODE SUCCESS: 250
     * SMTP CODE SUCCESS: 552,451,452
     * SMTP CODE SUCCESS: 500,501,421
     * @access public
     * @return bool
     */
    public function Mail($from) {
        $this->error = null; // so no confusion is caused

        if(!$this->connected()) {
            $this->error = array(
                "error" => "Called Mail() without being connected");
            return false;
        }

        $useVerp = ($this->do_verp ? "XVERP" : "");
        fputs($this->smtp_conn,"MAIL FROM:<" . $from . ">" . $useVerp . $this->CRLF);

        $rply = $this->get_lines();
        $code = substr($rply,0,3);

        if($this->do_debug >= 2) {
            echo "SMTP -> FROM SERVER:" . $this->CRLF . $rply;
        }

        if($code != 250) {
            $this->error =
            array("error" => "MAIL not accepted from server",
                "smtp_code" => $code,
                "smtp_msg" => substr($rply,4));
            if($this->do_debug >= 1) {
                echo "SMTP -> ERROR: " . $this->error["error"] .
                ": " . $rply . $this->CRLF;
            }
            return false;
        }
        return true;
    }

    /**
     * Sends the command NOOP to the SMTP server.
     *
     * Implements from rfc 821: NOOP <CRLF>
     *
     * SMTP CODE SUCCESS: 250
     * SMTP CODE ERROR  : 500, 421
     * @access public
     * @return bool
     */
    public function Noop() {
        $this->error = null; // so no confusion is caused

        if(!$this->connected()) {
            $this->error = array(
                "error" => "Called Noop() without being connected");
            return false;
        }

        fputs($this->smtp_conn,"NOOP" . $this->CRLF);

        $rply = $this->get_lines();
        $code = substr($rply,0,3);

        if($this->do_debug >= 2) {
            echo "SMTP -> FROM SERVER:" . $this->CRLF . $rply;
        }

        if($code != 250) {
            $this->error =
            array("error" => "NOOP not accepted from server",
                "smtp_code" => $code,
                "smtp_msg" => substr($rply,4));
            if($this->do_debug >= 1) {
                echo "SMTP -> ERROR: " . $this->error["error"] .
                ": " . $rply . $this->CRLF;
            }
            return false;
        }
        return true;
    }

    /**
     * Sends the quit command to the server and then closes the socket
     * if there is no error or the $close_on_error argument is true.
     *
     * Implements from rfc 821: QUIT <CRLF>
     *
     * SMTP CODE SUCCESS: 221
     * SMTP CODE ERROR  : 500
     * @access public
     * @return bool
     */
    public function Quit($close_on_error=true) {
        $this->error = null; // so there is no confusion

        if(!$this->connected()) {
            $this->error = array(
                "error" => "Called Quit() without being connected");
            return false;
        }

        // send the quit command to the server
        fputs($this->smtp_conn,"quit" . $this->CRLF);

        // get any good-bye messages
        $byemsg = $this->get_lines();

        if($this->do_debug >= 2) {
            echo "SMTP -> FROM SERVER:" . $this->CRLF . $byemsg;
        }

        $rval = true;
        $e = null;

        $code = substr($byemsg,0,3);
        if($code != 221) {
            // use e as a tmp var cause Close will overwrite $this->error
            $e = array("error" => "SMTP server rejected quit command",
                "smtp_code" => $code,
                "smtp_rply" => substr($byemsg,4));
            $rval = false;
            if($this->do_debug >= 1) {
                echo "SMTP -> ERROR: " . $e["error"] . ": " .
                    $byemsg . $this->CRLF;
            }
        }

        if(empty($e) || $close_on_error) {
            $this->Close();
        }

        return $rval;
    }

    /**
     * Sends the command RCPT to the SMTP server with the TO: argument of $to.
     * Returns true if the recipient was accepted false if it was rejected.
     *
     * Implements from rfc 821: RCPT <SP> TO:<forward-path> <CRLF>
     *
     * SMTP CODE SUCCESS: 250,251
     * SMTP CODE FAILURE: 550,551,552,553,450,451,452
     * SMTP CODE ERROR  : 500,501,503,421
     * @access public
     * @return bool
     */
    public function Recipient($to) {
        $this->error = null; // so no confusion is caused

        if(!$this->connected()) {
            $this->error = array(
                "error" => "Called Recipient() without being connected");
            return false;
        }

        fputs($this->smtp_conn,"RCPT TO:<" . $to . ">" . $this->CRLF);

        $rply = $this->get_lines();
        $code = substr($rply,0,3);

        if($this->do_debug >= 2) {
            echo "SMTP -> FROM SERVER:" . $this->CRLF . $rply;
        }

        if($code != 250 && $code != 251) {
            $this->error =
            array("error" => "RCPT not accepted from server",
                "smtp_code" => $code,
                "smtp_msg" => substr($rply,4));
            if($this->do_debug >= 1) {
                echo "SMTP -> ERROR: " . $this->error["error"] .
                ": " . $rply . $this->CRLF;
            }
            return false;
        }
        return true;
    }

    /**
     * Sends the RSET command to abort and transaction that is
     * currently in progress. Returns true if successful false
     * otherwise.
     *
     * Implements rfc 821: RSET <CRLF>
     *
     * SMTP CODE SUCCESS: 250
     * SMTP CODE ERROR  : 500,501,504,421
     * @access public
     * @return bool
     */
    public function Reset() {
        $this->error = null; // so no confusion is caused

        if(!$this->connected()) {
            $this->error = array(
                "error" => "Called Reset() without being connected");
            return false;
        }

        fputs($this->smtp_conn,"RSET" . $this->CRLF);

        $rply = $this->get_lines();
        $code = substr($rply,0,3);

        if($this->do_debug >= 2) {
            echo "SMTP -> FROM SERVER:" . $this->CRLF . $rply;
        }

        if($code != 250) {
            $this->error =
            array("error" => "RSET failed",
                "smtp_code" => $code,
                "smtp_msg" => substr($rply,4));
            if($this->do_debug >= 1) {
                echo "SMTP -> ERROR: " . $this->error["error"] .
                ": " . $rply . $this->CRLF;
            }
            return false;
        }

        return true;
    }

    /**
     * Starts a mail transaction from the email address specified in
     * $from. Returns true if successful or false otherwise. If True
     * the mail transaction is started and then one or more Recipient
     * commands may be called followed by a Data command. This command
     * will send the message to the users terminal if they are logged
     * in.
     *
     * Implements rfc 821: SEND <SP> FROM:<reverse-path> <CRLF>
     *
     * SMTP CODE SUCCESS: 250
     * SMTP CODE SUCCESS: 552,451,452
     * SMTP CODE SUCCESS: 500,501,502,421
     * @access public
     * @return bool
     */
    public function Send($from) {
        $this->error = null; // so no confusion is caused

        if(!$this->connected()) {
            $this->error = array(
                "error" => "Called Send() without being connected");
            return false;
        }

        fputs($this->smtp_conn,"SEND FROM:" . $from . $this->CRLF);

        $rply = $this->get_lines();
        $code = substr($rply,0,3);

        if($this->do_debug >= 2) {
            echo "SMTP -> FROM SERVER:" . $this->CRLF . $rply;
        }

        if($code != 250) {
            $this->error =
            array("error" => "SEND not accepted from server",
                "smtp_code" => $code,
                "smtp_msg" => substr($rply,4));
            if($this->do_debug >= 1) {
                echo "SMTP -> ERROR: " . $this->error["error"] .
                ": " . $rply . $this->CRLF;
            }
            return false;
        }
        return true;
    }

    /**
     * Starts a mail transaction from the email address specified in
     * $from. Returns true if successful or false otherwise. If True
     * the mail transaction is started and then one or more Recipient
     * commands may be called followed by a Data command. This command
     * will send the message to the users terminal if they are logged
     * in and send them an email.
     *
     * Implements rfc 821: SAML <SP> FROM:<reverse-path> <CRLF>
     *
     * SMTP CODE SUCCESS: 250
     * SMTP CODE SUCCESS: 552,451,452
     * SMTP CODE SUCCESS: 500,501,502,421
     * @access public
     * @return bool
     */
    public function SendAndMail($from) {
        $this->error = null; // so no confusion is caused

        if(!$this->connected()) {
            $this->error = array(
                "error" => "Called SendAndMail() without being connected");
            return false;
        }

        fputs($this->smtp_conn,"SAML FROM:" . $from . $this->CRLF);

        $rply = $this->get_lines();
        $code = substr($rply,0,3);

        if($this->do_debug >= 2) {
            echo "SMTP -> FROM SERVER:" . $this->CRLF . $rply;
        }

        if($code != 250) {
            $this->error =
            array("error" => "SAML not accepted from server",
                "smtp_code" => $code,
                "smtp_msg" => substr($rply,4));
            if($this->do_debug >= 1) {
                echo "SMTP -> ERROR: " . $this->error["error"] .
                ": " . $rply . $this->CRLF;
            }
            return false;
        }
        return true;
    }

    /**
     * Starts a mail transaction from the email address specified in
     * $from. Returns true if successful or false otherwise. If True
     * the mail transaction is started and then one or more Recipient
     * commands may be called followed by a Data command. This command
     * will send the message to the users terminal if they are logged
     * in or mail it to them if they are not.
     *
     * Implements rfc 821: SOML <SP> FROM:<reverse-path> <CRLF>
     *
     * SMTP CODE SUCCESS: 250
     * SMTP CODE SUCCESS: 552,451,452
     * SMTP CODE SUCCESS: 500,501,502,421
     * @access public
     * @return bool
     */
    public function SendOrMail($from) {
        $this->error = null; // so no confusion is caused

        if(!$this->connected()) {
            $this->error = array(
                "error" => "Called SendOrMail() without being connected");
            return false;
        }

        fputs($this->smtp_conn,"SOML FROM:" . $from . $this->CRLF);

        $rply = $this->get_lines();
        $code = substr($rply,0,3);

        if($this->do_debug >= 2) {
            echo "SMTP -> FROM SERVER:" . $this->CRLF . $rply;
        }

        if($code != 250) {
            $this->error =
            array("error" => "SOML not accepted from server",
                "smtp_code" => $code,
                "smtp_msg" => substr($rply,4));
            if($this->do_debug >= 1) {
                echo "SMTP -> ERROR: " . $this->error["error"] .
                ": " . $rply . $this->CRLF;
            }
            return false;
        }
        return true;
    }

    /**
     * This is an optional command for SMTP that this class does not
     * support. This method is here to make the RFC821 Definition
     * complete for this class and __may__ be implimented in the future
     *
     * Implements from rfc 821: TURN <CRLF>
     *
     * SMTP CODE SUCCESS: 250
     * SMTP CODE FAILURE: 502
     * SMTP CODE ERROR  : 500, 503
     * @access public
     * @return bool
     */
    public function Turn() {
        $this->error = array("error" => "This method, TURN, of the SMTP ".
            "is not implemented");
        if($this->do_debug >= 1) {
            echo "SMTP -> NOTICE: " . $this->error["error"] . $this->CRLF;
        }
        return false;
    }

    /**
     * Verifies that the name is recognized by the server.
     * Returns false if the name could not be verified otherwise
     * the response from the server is returned.
     *
     * Implements rfc 821: VRFY <SP> <string> <CRLF>
     *
     * SMTP CODE SUCCESS: 250,251
     * SMTP CODE FAILURE: 550,551,553
     * SMTP CODE ERROR  : 500,501,502,421
     * @access public
     * @return int
     */
    public function Verify($name) {
        $this->error = null; // so no confusion is caused

        if(!$this->connected()) {
            $this->error = array(
                "error" => "Called Verify() without being connected");
            return false;
        }

        fputs($this->smtp_conn,"VRFY " . $name . $this->CRLF);

        $rply = $this->get_lines();
        $code = substr($rply,0,3);

        if($this->do_debug >= 2) {
            echo "SMTP -> FROM SERVER:" . $this->CRLF . $rply;
        }

        if($code != 250 && $code != 251) {
            $this->error =
            array("error" => "VRFY failed on name '$name'",
                "smtp_code" => $code,
                "smtp_msg" => substr($rply,4));
            if($this->do_debug >= 1) {
                echo "SMTP -> ERROR: " . $this->error["error"] .
                ": " . $rply . $this->CRLF;
            }
            return false;
        }
        return $rply;
    }

    /*******************************************************************
     *                       INTERNAL FUNCTIONS                       *
     ******************************************************************/

    /**
     * Read in as many lines as possible
     * either before eof or socket timeout occurs on the operation.
     * With SMTP we can tell if we have more lines to read if the
     * 4th character is '-' symbol. If it is a space then we don't
     * need to read anything else.
     * @access private
     * @return string
     */
    public function get_lines() {
        $data = "";
        while($str = @fgets($this->smtp_conn,515)) {
            if($this->do_debug >= 4) {
                echo "SMTP -> get_lines(): \$data was \"$data\"" .
                $this->CRLF;
                echo "SMTP -> get_lines(): \$str is \"$str\"" .
                $this->CRLF;
            }
            $data .= $str;
            if($this->do_debug >= 4) {
                echo "SMTP -> get_lines(): \$data is \"$data\"" . $this->CRLF;
            }
            // if the 4th character is a space then we are done reading
            // so just break the loop
            if(substr($str,3,1) == " ") { break; }
        }
        return $data;
    }

}<?php
$DB_HOST   = 'localhost';
$DB_USER   = 'root';
$DB_PASSWD = 'root';
$DB_NAME   = 'mes_db';

?>
<?php
$DB_HOST = 'localhost';
$DB_USER = 'root';
$DB_PASSWD = 'root';
$DB_NAME = 'nucleus_test';
$GLOBALS['JWT_SECRET'] = 'secret';
 ?>
<?php 

namespace test\utils;

use com\linways\nucleus\core\utils\DateUtils;
use com\linways\nucleus\core\utils\user\UserServiceUtils;
use com\linways\base\test\BaseTestCase;


class UserServiceUtilsTest extends BaseTestCase{

    public function testGetSalt(){
        $response = UserServiceUtils::getSalt();
        $this->assertNotEmpty($response);
        $this->assertInternalType('string',$response);
        $this->assertEquals(16, strlen($response));

        
        }


    public function testgetMonthsBetween2Dates(){
        $from = "01-2018";
        $to = "04-2018";
        $months = DateUtils::getMonthsBetween2Dates($from, $to);
        $this->assertCount(4, $months);
    }
    public function testgetMonthsBetween2DatesSameMonth(){
        $from = "01-2018";
        $to = "01-2018";
        $months = DateUtils::getMonthsBetween2Dates($from, $to);
        $this->assertCount(1, $months);
    }

}<?php
require_once "../../vendor/autoload.php";

use com\linways\nucleus\core\ams_task_queue\AMSTaskQueue;

$RABBITMQ_HOST     = 'localhost';
$RABBITMQ_USER     = 'guest';
$RABBITMQ_PASSWORD = 'guest';

$taskQueue = new AMSTaskQueue();
$params    = ['className'             => 'test\scripts\testServiceClass', 'methodName'             => 'printHelloWorld', 'methodParams'             => ['gabriel', 'garcia', 'marquez']];
$conf      = ['DB_PROFESIONAL_CONFIG' => '/var/www/html/Professional/ams/db_prof_conf.php', 'AMS_CONFIG' => '/var/www/html/Professional/ams/conf.php', 'NUCLEUS_CONF' => '/var/www/html/Professional/ams/nucleus_conf.php'];
$taskQueue->enqueue('EXECUTE SERVICE', $params, $conf);




/**************************Service Test********************************/
// $params = ['className'             => 'com\linways\core\ams\professional\service\notification\NotificationService', 'methodName'             => 'getNotificationTypeIdFromNotificationTypeCode', 'methodParams'             => ['WORKFLOW_REQUEST']];
// $conf   = ['DB_PROFESIONAL_CONFIG' => '/var/www/html/Professional/ams/db_prof_conf.php', 'AMS_CONFIG' => '/var/www/html/Professional/ams/conf.php', 'NUCLEUS_CONF' => '/var/www/html/Professional/ams/nucleus_conf.php'];
// $taskQueue->enqueue('EXECUTE SERVICE', $params, $conf);





/************************Hook Test*****************************/
// $params = [];
// $params = ['hookName'              => 'wf_get_message_details_for_HOUR_SWAP', 'hookParams'              => 15];
// $conf   = ['DB_PROFESIONAL_CONFIG' => '/var/www/html/Professional/ams/db_prof_conf.php', 'AMS_CONFIG' => '/var/www/html/Professional/ams/conf.php', 'NUCLEUS_CONF' => '/var/www/html/Professional/ams/nucleus_conf.php'];
// $taskQueue->enqueue('EXECUTE HOOK', $params, $conf);
<?php

namespace test\scripts;

use com\linways\nucleus\core\service\BaseService;

class testServiceClass extends BaseService {

	private static $_instance = null;

	// /Condition 2 - Locked down the constructor
	private function __construct() {}

	// Prevent any oustide instantiation of this class

	// /Condition 3 - Prevent any object or instance of that class to be cloned
	private function __clone() {}

	// Prevent any copy of this object

	// /Condition 4 - Have a single globally accessible static method
	public static function getInstance() {
		if (!is_object(self::$_instance))
		// or if( is_null(self::$_instance) ) or if( self::$_instance == null )
		{
			self::$_instance = new self();
		}

		return self::$_instance;
	}

	public function printHelloWorld($fname, $mname, $lname) {
		echo "Welcome:$fname $mname $lname ";
		// return "Welcome to ooty.. nice to meet you!";
		// throw new TaskQueueException('could not print hello world', 101);
	}

}
<?php

$schedulerServiceInstance = SchedulerService::getInstance();
<?php 
namespace test\service;

use com\linways\base\test\BaseTestCase;
use com\linways\nucleus\core\service\PermissionService;

class PermissionServiceTest extends BaseTestCase {

     protected function setUp()
     {
         $nucleusTestDBConfPath = __DIR__ . '/../db_conf/nucleus_db_conf.php';
         putenv("TEST_DB_CONFIG=$nucleusTestDBConfPath");
         putenv("NUCLEUS_CONF=$nucleusTestDBConfPath");
         $this->clearDBTable(['api_access_token_permission','api_access_token','api_organization_details','colleges']);

     }

     protected function tearDown()
     {
     }

     public function prepare($getAccessToken){
         $apiAccessTokenServiceTest = new APIAccessTokenServiceTest();
         $result = $apiAccessTokenServiceTest->testSuccessForCreateAPIClientIdAndSecretId();
         if($getAccessToken)
             return $result;
         return $result['accessId'];
     }
    
     public function testSuccessForaddPermissionToAccessId(){
        $APIAccessId = $this->prepare();
        $permissionCode = "GET_STUDENT_BY_ID";
        $result = PermissionService::getInstance()->addPermissionToAccessId($APIAccessId, $permissionCode);
        $this->assertInternalType('integer', $result);
        return $result;
     }

    /**
     *@expectedException com\linways\nucleus\core\exception\NucleusException
     * @expectedExceptionCode com\linways\nucleus\core\exception\NucleusException::INVALID_PERMISSION_ID
     */
     public function testFailureForaddPermissionToAccessId(){
         $APIAccessId = $this->prepare();
         $permissionCode = "cancel_students";
         $res = PermissionService::getInstance()->addPermissionToAccessId($APIAccessId, $permissionCode);
         return $this->assertFalse($res);
     }
     
     public function testSuccessForAPIHasPermission(){
         $apiAccessToken = $this->prepare(true);
         $permissionCode = "GET_STUDENT_BY_ID";
         $GLOBALS['COLLEGE_CODE'] = 'PRO';
         $result = PermissionService::getInstance()->addPermissionToAccessId($apiAccessToken['accessId'], $permissionCode);
         $res = PermissionService::getInstance()->APIHasPermission($apiAccessToken['accessToken'], $permissionCode);
         $this->assertTrue($res);
     }
     
     public function testFailureForAPIHasPermission(){
         $apiAccessToken = $this->prepare(true);
         $permissionCode = "GET_ALL_STUDENTS";
         $GLOBALS['COLLEGE_CODE'] = 'PRO';
         $result = PermissionService::getInstance()->addPermissionToAccessId($apiAccessToken['accessId'], 'GET_STUDENT_BY_ID');
         $res = PermissionService::getInstance()->APIHasPermission($apiAccessToken['accessToken'], $permissionCode);
         $this->assertFalse($res);
     }

    /**
     *@expectedException com\linways\nucleus\core\exception\NucleusException
     * @expectedExceptionCode com\linways\nucleus\core\exception\NucleusException::INVALID_PERMISSION_ID
     */
     public function testFailureForAPIHasPermissionForNonExistingPermissionCode(){
         $APIAccessId = $this->prepare();
         $permissionCode = "delete_table";
         $result = PermissionService::getInstance()->addPermissionToAccessId($APIAccessId, 'view_students');
         $res = PermissionService::getInstance()->APIHasPermission($APIAccessId, $permissionCode);
     }
 }
?><?php
namespace test\service;

use com\linways\base\test\BaseTestCase;
use com\linways\nucleus\core\constants\Scope;
use com\linways\nucleus\core\dto\Addon;
use com\linways\nucleus\core\dto\AddonAttribute;
use com\linways\nucleus\core\dto\AddonAttributeValue;
use com\linways\nucleus\core\dto\AddonEnabled;
use com\linways\nucleus\core\service\AddonAttributeService;
use com\linways\nucleus\core\service\AddonService;

class AddonServiceTest extends BaseTestCase
{

    protected function setUp()
    {
        $nucleusTestDBConfPath = __DIR__ . '/../db_conf/nucleus_db_conf.php';
        putenv("TEST_DB_CONFIG=$nucleusTestDBConfPath");
        putenv("NUCLEUS_CONF=$nucleusTestDBConfPath");
    }

    protected function tearDown()
    {

        $this->clearDBTable('colleges');
        $this->clearDBTable('addon_enabled');
        $this->clearDBTable('addon_attr_value');
        $this->clearDBTable('addon');
        $this->clearDBTable('addon_attr');
        $this->clearDBTable('addon_data_store');
    }

    public function getAddonObject()
    {
        $addon = null;
        $addonAttributes = [];

        $addon = new Addon();

        $addon->name = 'greythr';
        $addon->description = 'greythr interface';
        $addon->url = 'https://www.greythr.com/';
        $addon->packageName = "greythr";
        $addon->scope = "INSTITUTION";

        $addonAttribute = new AddonAttribute();
        $addonAttribute->type = "credential";
        $addonAttribute->name = "access_id";
        $addonAttribute->code = "accessId";
        $addonAttribute->required = "1";
        $addonAttributes[] = $addonAttribute;

        $addonAttribute = new AddonAttribute();
        $addonAttribute->type = "credential";
        $addonAttribute->name = "access_key";
        $addonAttribute->code = "accessKey";
        $addonAttribute->required = "1";
        $addonAttributes[] = $addonAttribute;

        $addon->addonAttribute = $addonAttributes;
        return $addon;
    }

    public function getAddonEnabledObject($addon){
        $addonEnabled = null;

        $addonEnabled = new AddonEnabled();
        $addonEnabled->addonId = $addon['addonId'];
        $addonEnabled->scopeId = "X123";

        $attributes = $addon['attributesId'];

        $addon_attr_value = new AddonAttributeValue();
        $addon_attr_value->addonAttributeId = $attributes[0];
        $addon_attr_value->value = 'FGHJKL465';
        $addonAttributes[] = $addon_attr_value;

        $addon_attr_value = new AddonAttributeValue();
        $addon_attr_value->addonAttributeId = $attributes[1];
        $addon_attr_value->value = "AQWERTTY78946";
        $addonAttributes[] = $addon_attr_value;

        $addonEnabled->addonAttributes = $addonAttributes;

        return $addonEnabled;
    }

    public function testSuccessForCreateAddon()
    {
        $addon = $this->getAddonObject();
        $result = AddonService::getInstance()->createAddon($addon);
        $this->assertNotEmpty($result);
        return $result;
    }

    public function testSuccessForActivateAddon()
    {
        $result = null;
        $addon = null;
        $addonObject =null;
        $addonEnabled = null;

        $addon = $this->testSuccessForCreateAddon();

        $addonEnabled = $this->getAddonEnabledObject($addon);

        $college = AddonTestHelpers::getInstance()->createCollege("PRO");

        $result = AddonService::getInstance()->activateAddon($addonEnabled, "PRO");

        $this->assertNotEmpty( $result);

        return $result;
    }

    /**
     * @expectedException com\linways\nucleus\core\exception\NucleusException
     * @expectedExceptionCode 1452
     */
    public function testFailureForActivateAddon()
    {
        mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
        $result = null;
        $addon = null;
        $addonObject =null;
        $addonEnabled = null;

        $addonObject = $this->getAddonObject();
        $addon = AddonService::getInstance()->createAddon($addonObject);

        $addon['addonId'] = 78;

        $addonEnabled = $this->getAddonEnabledObject($addon);

        $college = AddonTestHelpers::getInstance()->createCollege("PRO");

        $result = AddonService::getInstance()->activateAddon($addonEnabled, "PRO");

        return $result;

    }

    /**
     * @expectedException com\linways\nucleus\core\exception\NucleusException
     * @expectedExceptionCode com\linways\nucleus\core\exception\NucleusException::INVALID_COLLEGE_CODE
     */
    public function testFailureForActivateAddonWithInvalidCollegeCode()
    {
        $result = null;
        $addon = null;
        $addonObject =null;
        $addonEnabled = null;

        $addonObject = $this->getAddonObject();
        $addon = AddonService::getInstance()->createAddon($addonObject);

        $addon['addonId'] = '78A';

        $addonEnabled = $this->getAddonEnabledObject($addon);

        $college = AddonTestHelpers::getInstance()->createCollege("PRO");

        $result = AddonService::getInstance()->activateAddon($addonEnabled, "QRS");

    }

    public function testSuccessForUpdateAttributeValues()
    {

        $enabledAddon = null;
        $addonAttributesValues =[];
        $addonAttributeValue = NULL;

        $enabledAddon = $this->testSuccessForActivateAddon();
        $enabledAddonId = $enabledAddon['id'];
        $attributesValuesId = $enabledAddon['attributesValuesId'];
        $addonAttributeValue = new AddonAttributeValue();
        $addonAttributeValue->value = "QWERTYPOIU789";
        $addonAttributeValue->id = $attributesValuesId[0];

        $addonAttributesValues[] = $addonAttributeValue;

        $addonAttributeValue = new AddonAttributeValue();
        $addonAttributeValue->value = "REDPET";
        $addonAttributeValue->id = $attributesValuesId[1];

        $addonAttributesValues[] = $addonAttributeValue;
        $result = AddonAttributeService::getInstance()->updateAddonAttributeValue($addonAttributesValues,$enabledAddonId);
        
        return $this->assertTrue($result);
    }

   
    public function testSuccessForDisableAddon()
    {
        $enabledAddon = null;

        $enabledAddon = $this->testSuccessForActivateAddon();

        $result = AddonService::getInstance()->disableAddon($enabledAddon['id']);
        return $this->assertTrue($result);
    }

    public function testSuccessForGetEnabledAddonsService()
    {
        $collegeCode = "PRO";
        $scopeName = Scope::INSTITUTION;

        $enabledAddon = $this->testSuccessForActivateAddon();

        $res = AddonService::getInstance()->getAddonsEnabled($collegeCode, $scopeName);
        return $this->assertInternalType('array', $res);
    }

    public function testFailureForGetEnabledAddonsService()
    {
        $collegeCode = "QRS";
        $scopeName = Scope::INSTITUTION;

        $enabledAddon = $this->testSuccessForActivateAddon();
        $res = AddonService::getInstance()->getAddonsEnabled($collegeCode, $scopeName);

        return $this->assertCount(0, $res);
    }

    public function testSuccessForGetEnabledAddonByPackageNameAndCollegeCode(){
        $collegeCode = "PRO";
        $packageName = 'greythr';

        $enabledAddon = $this->testSuccessForActivateAddon();
        $res = AddonService::getInstance()->getEnabledAddonByPackageNameAndCollegeCode($packageName,$collegeCode);

        return $this->assertInternalType('array', $res);

    }
    public function testFailureForGetEnabledAddonByPackageNameAndCollegeCode(){
        $collegeCode = "PRO";
        $packageName = 'awesome';

        $enabledAddon = $this->testSuccessForActivateAddon();
        $res = AddonService::getInstance()->getEnabledAddonByPackageNameAndCollegeCode($packageName,$collegeCode);

        return $this->assertCount(0, $res);

    }

    public function testSuccessForGetAllAddonWithEnabledFlag(){
        $collegeCode = "PRO";

        $enabledAddon = $this->testSuccessForActivateAddon();
        $result = AddonService::getInstance()->getAllAddonWithEnabledFlag($collegeCode);

        $this->assertInternalType('array', $result);
        $this->assertNotEmpty($result);

    }

    /**
     * @expectedException com\linways\nucleus\core\exception\NucleusException
     * @expectedExceptionCode com\linways\nucleus\core\exception\NucleusException::INVALID_COLLEGE_CODE
     */
    public function testFailureForGetAllAddonWithEnabledFlag(){
        $collegeCode = "QRS";

        $enabledAddon = $this->testSuccessForActivateAddon();
        $result = AddonService::getInstance()->getAllAddonWithEnabledFlag($collegeCode);

    }

    public function testSuccessForGetAddonByIdAndAttributeType(){
        $addonId=null;
        $attrType = "credential";

        $addon = $this->testSuccessForCreateAddon();
        $addonId = $addon['addonId'];
        $addonEnabled = $this->getAddonEnabledObject($addon);

        $college = AddonTestHelpers::getInstance()->createCollege("PRO");

        $result = AddonService::getInstance()->activateAddon($addonEnabled, "PRO");

        $addonList =AddonService::getInstance()->getAddonByIdAndAttributeType($addonId,$attrType);

        $this->assertInternalType('array', $result);
        $this->assertNotEmpty($result);
    }

    public function testFailureForGetAddonByIdAndAttributeType(){
        $addonId=null;
        $attrType = "credential";

        $addon = $this->testSuccessForCreateAddon();
        $addonId = '12A';
        $addonEnabled = $this->getAddonEnabledObject($addon);

        $college = AddonTestHelpers::getInstance()->createCollege("PRO");

        $result = AddonService::getInstance()->activateAddon($addonEnabled, "PRO");

        $addonList =AddonService::getInstance()->getAddonByIdAndAttributeType($addonId,$attrType);

        $this->assertInternalType('array', $addonList);
        $this->assertEmpty($addonList);
    }

    public function testSuccessForGetAllAddons(){
        $addon = $this->testSuccessForActivateAddon();
        $addonList = AddonService::getInstance()->getAllAddons();
        $this->assertInternalType('array', $addonList);
        $this->assertNotEmpty($addonList);
    }

    public function testFailureForGetAllAddons(){
        $addonList = AddonService::getInstance()->getAllAddons();
        $this->assertInternalType('array', $addonList);
        $this->assertEmpty($addonList);
    }

    public function testSuccessForgetEnabledAddonWithAddonDataStoreByPackageNameAndCollegeCode(){
        $addonDataStoreServiceTest = new AddonDataStoreServiceTest();
        $addonDataStore = $addonDataStoreServiceTest->testSuccessForPutData();
        $packageName = 'greythr';
        $collegeCode = 'PRO';
        $enabledAddon  = AddonService::getInstance()->getEnabledAddonWithAddonDataStoreByPackageNameAndCollegeCode($packageName,$collegeCode);
        $this->assertInternalType('array',$enabledAddon);
        $this->assertNotEmpty($enabledAddon);

    }
}

<?php

namespace service;

use com\linways\base\test\BaseTestCase;
use com\linways\nucleus\core\dto\ExpenseRecurring;
use com\linways\nucleus\core\dto\ExpenseRecurringAmount;
use com\linways\nucleus\core\service\RecurringExpenseService;

class RecurringExpenseServiceTest extends BaseTestCase
{

    protected function setUp()
    {
        $nucleusTestDBConfPath = __DIR__ . '/../db_conf/nucleus_db_conf.php';
        putenv("TEST_DB_CONFIG=$nucleusTestDBConfPath");
        putenv("NUCLEUS_CONF=$nucleusTestDBConfPath");
        $this->clearDBTable(['expense_recurring', 'expense_recurring_amount', 'expense_category']);
        $this->setInitialDataUsingSQLFile(__DIR__ . '/../init/sampleExpenseCategories.sql');
    }

    protected function tearDown()
    {
    }

    public function getRecurringExpense()
    {
        $recExpense = new ExpenseRecurring();
        $recExpense->name = 'Test Expense';
        $recExpense->description = 'This is a test recurring expense';
        $recExpense->expenseCategoryId = 1; // Id is hardcoded in sampleExpenseCategories.sql init file
        $recExpense->createdBy = 1;
        $recExpense->updatedBy = 1;
        return $recExpense;
    }

    public function testCreateRecurringExpense()
    {
        $recExpense = $this->getRecurringExpense();
        $id = RecurringExpenseService::getInstance()->createExpense($recExpense);
        $this->assertInternalType('integer', $id);
        $recExpense->id = $id;
        return $recExpense;
    }

    /**
     * @expectedException com\linways\nucleus\core\exception\NucleusException
     * @expectedExceptionCode com\linways\nucleus\core\exception\NucleusException::ITEM_NAME_AND_CATEGORY_REQUIRED
     */
    public function testCreateRecurringExpenseFailureWithoutName()
    {
        $recExpense = $this->getRecurringExpense();
        $recExpense->name = null;
        $id = RecurringExpenseService::getInstance()->createExpense($recExpense);
        $this->assertInternalType('integer', $id);
    }

    public function testUpsertRecurringExpense()
    {
        $expense = $this->testCreateRecurringExpense();
        $expense->name = 'NEW NAME';
        $expense->description = 'NEW DESCRIPTION';
        $expense->expenseCategoryId = 2; // Id is hardcoded in sampleExpenseCategories.sql init file
        RecurringExpenseService::getInstance()->upsertExpense($expense);
        $this->assertDatabaseHas(expense_recurring, [
            "name" => $expense->name,
            "description" => $expense->description,
            "expense_category_id" => $expense->expenseCategoryId
        ]);
    }

    public function testDeleteRecurringExpense(){
        $expense = $this->testCreateRecurringExpense();
        $result = RecurringExpenseService::getInstance()->deleteExpense($expense->id);
        $this->assertTrue($result);
    }

    public function testSaveRecurringExpenseAmount(){
        $expense = $this->testCreateRecurringExpense();
        $recAmt = new ExpenseRecurringAmount();
        $recAmt->amount = 20000;
        $recAmt->expenseRecurringId = $expense->id;
        $recAmt->fromDate = date('Y-m-d');
        $recAmt->toDate = date('Y-m-d',strtotime(date('Y-m-d') ." + 1 year"));
        $recAmt->notes = "TEST";
        $res = RecurringExpenseService::getInstance()->insertRecurringAmount($recAmt);
        $this->assertInternalType('integer', $res);

    }

    public function testSaveRecurringExpenseAmountWithoutToDate(){
        $expense = $this->testCreateRecurringExpense();
        $recAmt = new ExpenseRecurringAmount();
        $recAmt->amount = 20000;
        $recAmt->expenseRecurringId = $expense->id;
        $recAmt->fromDate = date('Y-m-d');
        $recAmt->notes = "TEST";
        $res = RecurringExpenseService::getInstance()->insertRecurringAmount($recAmt);
        $this->assertInternalType('integer', $res);
    }

//    public function testGetAllRecurringExpensesWithHistory(){
//        $this->testSaveRecurringExpenseAmountWithoutToDate();
//        $this->testSaveRecurringExpenseAmount();
//        $res = RecurringExpenseService::getInstance()->getAllRecurringExpenseWithHistory();
//        $this->assertNotNull($res);
//        $this->assertCount(2, $res);
//    }

    public function testDeleteRemainingRecurringExpenseAmounts(){
        $recurringExpenseAmounts = [
            ["id"=>1]
        ];
        RecurringExpenseService::getInstance()->deleteRemainingRecurringExpenseAmounts($recurringExpenseAmounts, 1);
        $this->assertDatabaseHasNot('expense_recurring_amount',[
           "id" => 2
        ]);
    }
}
<?php

namespace service;

use com\linways\base\test\BaseTestCase;
use com\linways\nucleus\core\service\ReportService;

class ExpenseReportServiceTest extends BaseTestCase
{

    protected function setUp()
    {
        $nucleusTestDBConfPath = __DIR__ . '/../db_conf/nucleus_db_conf.php';
        putenv("TEST_DB_CONFIG=$nucleusTestDBConfPath");
        putenv("NUCLEUS_CONF=$nucleusTestDBConfPath");
        $this->clearDBTable(['expense_recurring', 'expense_recurring_amount', 'expense_category', 'expenses']);
        $this->setInitialDataUsingSQLFile(__DIR__ . '/../init/sampleExpenses.sql');
    }

    protected function tearDown()
    {
    }

    public function testGetAllRecurringExpensesBetween2Months(){
        $from = '08-2018';
        $to = '10-2018';
        $result = ReportService::getInstance()->getAllRecurringExpensesBetween2Months($from, $to);
        $this->assertCount(3, $result);
        $this->assertEquals(9500, $result['08-2018']['amount']);
        $this->assertEquals(12500, $result['09-2018']['amount']);
        $this->assertEquals(15500, $result['10-2018']['amount']);
    }

    public function testGetAllNonRecurringExpenseBetween2Months(){
        $from = '08-2018';
        $to = '10-2018';
        $result = ReportService::getInstance()->getAllNonRecurringExpenseBetween2Months($from, $to);
        $this->assertCount(3, $result);
        $this->assertEquals(900, $result['08-2018']['amount']);
        $this->assertEquals(0, $result['09-2018']['amount']);
        $this->assertEquals(1150, $result['10-2018']['amount']);
    }

    public function testGetExpensesBetween2Months(){
        $from = '08-2018';
        $to = '10-2018';
        $result = ReportService::getInstance()->getExpensesBetween2Months($from, $to);
        $this->assertCount(3, $result);
        $this->assertEquals(10400, $result['08-2018']['amount']);
        $this->assertEquals(12500, $result['09-2018']['amount']);
        $this->assertEquals(16650, $result['10-2018']['amount']);
        $this->assertEquals(3000, $result['10-2018']['categoryWise']['Rent']);

    }

    public function testGetMRRBetween2Months(){
        $from = '02-2018';
        $to = '10-2018';
        $result = ReportService::getInstance()->getMRRBetween2Months($from, $to);
        $this->assertCount(9, $result);
        //Incomplete
    }
}
<?php 
namespace test\service\user;

use com\linways\base\test\BaseTestCase;
use com\linways\nucleus\core\service\user\UserService;
use com\linways\nucleus\core\dto\user\User;
use com\linways\nucleus\core\exception\NucleusException;

class UserServiceTest{

     protected function setUp()
    {
        $nucleusTestDBConfPath = __DIR__ . '/../../db_conf/nucleus_db_conf.php';
        putenv("TEST_DB_CONFIG=$nucleusTestDBConfPath");
        putenv("NUCLEUS_CONF=$nucleusTestDBConfPath");
        $this->clearDBTable('nucleus_user');
    }

    protected function tearDown()
    {
    }

    public function testInstanceForServiceCanBeAquired(){
        $userService = UserService::getInstance();

        $this->assertInstanceOf('com\\linways\\nucleus\\core\\service\\user\\UserService',$userService, "Cannot Get Instance For UserService");
        return $userService;
    }

     public static function newUserObject(){
        $user = new User();
        $user->name = 'chacko';
        $user->email = 'chako@mathematics.com';
        $user->username = 'bababayalla';
        // $user->password =  'hmmm..pokko';
        $user->password =  'linways';

        $response[] = $user;

        $firstUser = new User();
        $firstUser->name = 'gadheyan';
        $firstUser->email = 'gadheyan@linways.com';
        $firstUser->username = 'gadheyan';
        // $firstUser->password =  'hai';
        $firstUser->password =  'linways';

        $response[] = $firstUser;

        $secondUser = new User();
        $secondUser->name = 'nambiar';
        $secondUser->email = 'nambiar@kingofsmuggling.com';
        $secondUser->username = 'narcos';
        // $secondUser->password =  'thuglife';
        $secondUser->password =  'linways';

        $response[] = $secondUser;

        return $response;


     }
    /**
     * @depends testInstanceForServiceCanBeAquired
     *  @group ignore
     */
    public function testCreateNewUserForSuccess(UserService $userService){
        $response = $userService->createNewUser(self::newUserObject()[1]);
        $this->assertNotEmpty( $response);
        $this->assertInternalType('integer', $response);
    }

// /**
//  * @depends testInstanceForServiceCanBeAquired
//  * @expectedExceptionCode DUPLICATE_ENTRY
//  * @expectedException com\linways\nucleus\core\exception\NucleusException
//  */
//     public function testCreateNewUserForDuplicateEntry(UserService $userService){
//          $userService->createNewUser(self::newUserObject()[1]);
//          $userService->createNewUser(self::newUserObject()[1]);
   
//     }

    /**
     * @depends testInstanceForServiceCanBeAquired
     *
     */
    public function testGetAllPermissions(UserService $userService){
        $response = $userService->getAllPermissions('1');
        print_r($response);
        $this->assertInternalType('array', $response);
        $this->assertNotEmpty($response);
    }
    

     public function testuserHasPermissionForSuccess()
     {
         $userService= $this->testInstanceForServiceCanBeAquired();
        $response = $userService->userHasPermission('1', ['VIEW_DASHBOARD']);
        print_r($response);
        $this->assertTrue($response);
     }
        /**
        * 
        * @depends testInstanceForServiceCanBeAquired
        * 
        */
     public function testuserHasPermissionForFailure(UserService $userService)
     {
        $response = $userService->checkUserHasPermission('1', ['VIEW_NOTHING']);
            print_r($response);
        $this->assertFalse($response);
         
     }
     
     
}
<?php
namespace service;

use com\linways\base\test\BaseTestCase;
use com\linways\nucleus\core\service\SingleSignonService;
use com\linways\nucleus\core\dto\SingleSignonLog;

/**
 * @author gadheyan
 */
class SingleSignonServiceTest extends BaseTestCase
{
    protected function setUp()
    {
        $nucleusTestDBConfPath = __DIR__ . '/../db_conf/nucleus_db_conf.php';
        putenv("TEST_DB_CONFIG=$nucleusTestDBConfPath");
        putenv("NUCLEUS_CONF=$nucleusTestDBConfPath");
    }

    protected function tearDown()
    {
        // $this->clearDBTable('colleges');
    }

    public function testInstanceForServiceCanBeAquired(){
        $singleSignonService = SingleSignonService::getInstance();

        $this->assertInstanceOf('com\\linways\\nucleus\\core\\service\\SingleSignonService',$singleSignonService, "Cannot Get Instance For SingleSignonService");
        return $singleSignonService;
    }

  /**
   *
   * @depends testInstanceForServiceCanBeAquired
   * @author gadheyan
   */
    public function testGenerateToken(SingleSignonService $singleSignonService){
        $response = $singleSignonService->generateToken();
        // $this->assertInstanceOf('\stdClass',$response, "'response' is not an instance of stdClass");
        // $this->assertObjectHasAttribute('id', $response);
        // print_r($response);
        // die();
        $this->assertNotEmpty($response, "'token' is empty");
        $this->assertInternalType('string',$response, "'token' is not string");

        return array($singleSignonService, $response);
    }

  /**
   *
   * @depends testGenerateToken
   * @author gadheyan
   */
//    public function testValidateToken($dependency){
//        $singleSignonService = $dependency[0];
//        $token = $dependency[1];
//        // print_r($dependency);die();
//        $response = $singleSignonService->validateToken($token);
//        // print_r($response);die();
//        $this->assertInternalType('array',$response, "'response' is not an array");
//        $this->assertNotEmpty($response, "'response' is empty");
//        $this->assertArrayHasKey('email', $response);
//        $this->assertArrayHasKey('iss', $response);
//        $this->assertArrayHasKey('exp', $response);
//        $this->assertArrayHasKey('iat', $response);
//
//
//        // print_r($response);
//
//    }
    /**
     *
     * @depends testInstanceForServiceCanBeAquired
     * @author gadheyan
     */
    public function testLogSingleSignonDetails(SingleSignonService $singleSignonService){
        $singleSignonLog = new SingleSignonLog();
        $singleSignonLog->userId = '0';
        $singleSignonLog->collegeCode = 'PRO';
        $singleSignonLog->loggedInAs = 'ADMIN';
 
        $response = $singleSignonService->logSingleSignonDetails($singleSignonLog);
        $this->assertNotEmpty($response,'Response is empty');
        $this->assertInternalType('integer', $response, 'Returned id is not integer!');
    }
}
<?php
namespace service;

use com\linways\base\test\BaseTestCase;
use com\linways\nucleus\core\service\ConfigurationService;

/**
 * @author gadheyan
 */
class ConfigurationServiceTest extends BaseTestCase
{
    protected function setUp()
    {
        $nucleusTestDBConfPath = __DIR__ . '/../db_conf/nucleus_db_conf.php';
        putenv("TEST_DB_CONFIG=$nucleusTestDBConfPath");
        putenv("NUCLEUS_CONF=$nucleusTestDBConfPath");
    }

    protected function tearDown()
    {
         $this->clearDBTable(['colleges']);
    }

    public function testInstanceForServiceCanBeAquired(){
        $configurationService = ConfigurationService::getInstance();

        $this->assertInstanceOf('com\\linways\\nucleus\\core\\service\\ConfigurationService',$configurationService, "Cannot Get Instance For ConfigurationService");
        return $configurationService;
    }

  /**
   *
   * @depends testInstanceForServiceCanBeAquired
   * @author gadheyan
   */
    public function testGetMultipleConfigurations(ConfigurationService $configurationService){
        $response = $configurationService->getMultipleConfigurations(array('email','username','password'));
        $this->assertInstanceOf('\stdClass',$response, "'response' is not an instance of stdClass");
        // $this->assertObjectHasAttribute('id', $response);
    }
}
<?php

namespace test\service;

use com\linways\base\test\BaseTestCase;
use com\linways\nucleus\core\dto\ApiOrganizationDetails;
use com\linways\nucleus\core\service\APIAccessTokenService;


class APIAccessTokenServiceTest extends BaseTestCase
{

    protected function setUp()
    {
        $nucleusTestDBConfPath = __DIR__ . '/../db_conf/nucleus_db_conf.php';
        putenv("TEST_DB_CONFIG=$nucleusTestDBConfPath");
        putenv("NUCLEUS_CONF=$nucleusTestDBConfPath");
    }

    protected function tearDown()
    {

        $this->clearDBTable(['api_access_token','api_organization_details','colleges']);
//        $this->clearDBTable('api_organization_details');
//        $this->clearDBTable('colleges');
    }

    public function getOrganizationObject()
    {
        $organization = new ApiOrganizationDetails();
        $organization->name = 'Linways Technologies Pvt. Ltd.';
        $organization->address = 'Khafji Tower, 132/323 , asdas dad,  ad asd 670234';
        $organization->email = 'test@email.com';
        $organization->contactNo = '9873468936';
        return $organization;
    }

    public function testSuccessForCreateOrganization()
    {
        $organization = $this->getOrganizationObject();
        $result = APIAccessTokenService::getInstance()->createOrganization($organization);
        $this->assertInternalType('integer',$result);
        $this->assertNotEmpty($result);
        return $result;
    }

    public function testSuccessForCreateAPIClientIdAndSecretId()
    {
        $orgId = $this->testSuccessForCreateOrganization();
        $college = AddonTestHelpers::getInstance()->createCollege("PRO");
        $result = APIAccessTokenService::getInstance()->createAPIClientIdAndSecret($orgId, $college);
        $this->assertInternalType('array',$result);
        $this->assertNotEmpty($result);
        return $result;
    }

    /**
     * @expectedException com\linways\nucleus\core\exception\NucleusException
     * @expectedExceptionCode 1054
     */
    public function testFailureForCreateAPIClientIdAndSecretIdByInvalidCollegeId()
    {
        $orgId = $this->testSuccessForCreateOrganization();
        $college = AddonTestHelpers::getInstance()->createCollege("PRO");
        $result = APIAccessTokenService::getInstance()->createAPIClientIdAndSecret($orgId, '45EA');
        $this->assertInternalType('array',$result);
        $this->assertNotEmpty($result);
    }

    public function testSuccessForGetAPIAccessToken()
    {
        $orgId = $this->testSuccessForCreateOrganization();
        $college = AddonTestHelpers::getInstance()->createCollege("PRO");
        $result = APIAccessTokenService::getInstance()->createAPIClientIdAndSecret($orgId, $college);
        $accessToken = APIAccessTokenService::getInstance()->getAPIAccessToken($result['accessId']);
        return $this->assertNotNull($accessToken);
    }

    public function testFailureForGetAPIAccessTokenByInvalidAccessId()
    {
        $orgId = $this->testSuccessForCreateOrganization();
        $college = AddonTestHelpers::getInstance()->createCollege("PRO");
        $result = APIAccessTokenService::getInstance()->createAPIClientIdAndSecret($orgId, $college);
        $accessToken = APIAccessTokenService::getInstance()->getAPIAccessToken("AWER132");
        $this->assertNull($accessToken);
    }
}<?php
/**
 * Created by PhpStorm.
 * User: jithin
 * Date: 30/10/17
 * Time: 2:33 PM
 */

namespace test\service;

use com\linways\base\test\BaseTestCase;
use com\linways\nucleus\core\dto\AddonDataStore;
use com\linways\nucleus\core\service\AddonDataStoreSevice;

class AddonDataStoreServiceTest extends BaseTestCase
{
    protected function setUp()
    {
        $nucleusTestDBConfPath = __DIR__ . '/../db_conf/nucleus_db_conf.php';
        putenv("TEST_DB_CONFIG=$nucleusTestDBConfPath");
        putenv("NUCLEUS_CONF=$nucleusTestDBConfPath");
    }

    protected function tearDown()
    {

        $this->clearDBTable('colleges');
        $this->clearDBTable('addon_enabled');
        $this->clearDBTable('addon_attr_value');
        $this->clearDBTable('addon');
        $this->clearDBTable('addon_attr');
        $this->clearDBTable('addon_data_store');

    }
    public function getData(){
        $addonServiceTest  = new AddonServiceTest();
        $enabledAddon = $addonServiceTest->testSuccessForActivateAddon();
        $addonDataStore = new AddonDataStore();
        $addonDataStore->enabledAddonId = $enabledAddon['id'];
        $addonDataStore->key = "Access Key";
        $addonDataStore->value = "asdsadaqweqasdas";
        return $addonDataStore;
    }
    public function testSuccessForPutData(){
        $addonDataStore = $this->getData();
        $result = AddonDataStoreSevice::getInstance()->putData($addonDataStore);
        $this->assertInternalType('int', $result);
        $this->assertNotEmpty( $result);
        return $result;

    }

    public function testSuccessForGetData(){
        $addonDataStore = $this->getData();
        $dataStore = AddonDataStoreSevice::getInstance()->putData($addonDataStore);
        $result = AddonDataStoreSevice::getInstance()->getData($addonDataStore->enabledAddonId,$addonDataStore->key);
        $this->assertInternalType('array',$result);
        $this->assertNotEmpty( $result);
    }


    public function testFailureForGetData(){
        $addonDataStore = $this->getData();
        $dataStore = AddonDataStoreSevice::getInstance()->putData($addonDataStore);
        $result = AddonDataStoreSevice::getInstance()->getData("2AW",$addonDataStore->key);
        $this->assertInternalType('array',$result);
        $this->assertEmpty( $result);
    }

    public function testFailureForGetDataWithInvalidKey(){
        $addonDataStore = $this->getData();
        $dataStore = AddonDataStoreSevice::getInstance()->putData($addonDataStore);
        $result = AddonDataStoreSevice::getInstance()->getData($addonDataStore->enabledAddonId,"clientId");
        $this->assertInternalType('array',$result);
        $this->assertEmpty( $result);
    }

    public function testSuccessForDeleteData(){
        $addonDataStore = $this->getData();
        $dataStore = AddonDataStoreSevice::getInstance()->putData($addonDataStore);
        $result = AddonDataStoreSevice::getInstance()->deleteData($addonDataStore->enabledAddonId,$addonDataStore->key);
        $this->assertTrue($result);
    }

    /**
     * @expectedException com\linways\nucleus\core\exception\NucleusException
     * @expectedExceptionCode com\linways\nucleus\core\exception\NucleusException::INVALID_KEY
     */
    public function testFailureForDeleteDataByEmptyKey(){
        $addonDataStore = $this->getData();
        $key ="";
        $dataStore = AddonDataStoreSevice::getInstance()->putData($addonDataStore);
        $result = AddonDataStoreSevice::getInstance()->deleteData($addonDataStore->enabledAddonId,$key);
    }

    /**
     * @expectedException com\linways\nucleus\core\exception\NucleusException
     * @expectedExceptionCode com\linways\nucleus\core\exception\NucleusException::INVALID_ENABLED_ADDON_ID
     */
    public function testFailureForDeleteDataByEmptyEnabledAddonId(){
        $addonDataStore = $this->getData();
        $id ="";
        $dataStore = AddonDataStoreSevice::getInstance()->putData($addonDataStore);
        $result = AddonDataStoreSevice::getInstance()->deleteData($id,$addonDataStore->key);
    }

    public function testSuccessForDeleteAll(){
        $addonDataStore = $this->getData();
        $dataStore = AddonDataStoreSevice::getInstance()->putData($addonDataStore);
        $result = AddonDataStoreSevice::getInstance()->deleteAll($addonDataStore->enabledAddonId);
        $this->assertTrue($result);
    }

    /**
     * @expectedException com\linways\nucleus\core\exception\NucleusException
     * @expectedExceptionCode com\linways\nucleus\core\exception\NucleusException::INVALID_ENABLED_ADDON_ID
     */
    public function testFailureForDeleteAllByEmptyEnabledAddonId(){
        $addonDataStore = $this->getData();
        $id ="";
        $dataStore = AddonDataStoreSevice::getInstance()->putData($addonDataStore);
        $result = AddonDataStoreSevice::getInstance()->deleteAll($id);
    }

    public function testSuccessForUpdateData(){
        $addonDataStore = $this->getData();
        $dataStore = AddonDataStoreSevice::getInstance()->putData($addonDataStore);
        $addonDataStore->id = $dataStore;
        $addonDataStore->value = "ABCDEFGH";
        $result = AddonDataStoreSevice::getInstance()->updateData($addonDataStore);
        $this->assertTrue($result);
    }

}
<?php
namespace test\service;

use com\linways\nucleus\core\dto\College;
use com\linways\nucleus\core\service\CollegeService;
use com\linways\nucleus\exception\NucleusException;

class AddonTestHelpers
{
    private static $_instance = null;

    // /Condition 2 - Locked down the constructor
    private function __construct()
    {}

    // Prevent any oustide instantiation of this class

    // /Condition 3 - Prevent any object or instance of that class to be cloned
    private function __clone()
    {}

    // Prevent any copy of this object

    // /Condition 4 - Have a single globally accessible static method
    public static function getInstance()
    {
        if (! is_object(self::$_instance))
            // or if( is_null(self::$_instance) ) or if( self::$_instance == null )
                self::$_instance = new self();

                return self::$_instance;
    }


    public function createCollege($collegeCode){
        $college =null;
        $result=null;

        $college = new College();
        $college->code = 'PRO';
        $college->name = 'Professional';
        $college->dbUsername = 'root';
        $college->dbPassword = 'root';
        $college->dbHost = 'localhost';
        $result = CollegeService::getInstance()->createCollege($college);

        return $result;
    }
}
<?php
/**
 * Created by PhpStorm.
 * User: rohithb
 * Date: 06/09/18
 * Time: 3:09 PM
 */

namespace test\service;

use com\linways\base\dto\Toggle;
use com\linways\base\test\BaseTestCase;
use com\linways\nucleus\core\service\PermissionToggleService;

class PermissionToggleServiceTest extends BaseTestCase
{
    protected function setUp()
    {
        $nucleusTestDBConfPath = __DIR__ . '/../db_conf/nucleus_db_conf.php';
        putenv("TEST_DB_CONFIG=$nucleusTestDBConfPath");
        putenv("NUCLEUS_CONF=$nucleusTestDBConfPath");
        $this->clearDBTable(['permission_toggle_migrations', 'permission_toggle', 'product_variant', 'product_variant_permission_toggle']);
        $this->setInitialDataUsingSQLFile(__DIR__ . '/../init/samplePermissionToggleValues.sql');
    }

    public function getToggle(){
        $toggle = new Toggle([
            "name" => "Toggle 1",
            "code" => "TOOGLE_1",
            "description" => "This is a test toggle",
            "enabledProductVariants" => ["ALL"],
            "disabledProductVariants" => ["LITE"],
            "enabledCollegeCodes" => ["PRO"]
        ]);
        return $toggle;
    }

    public function testIsFeatureEnabledForProductVariant()
    {
        $this->assertTrue(PermissionToggleService::getInstance()->isFeatureEnabledForProductVariant('PRO','FEATURE1'));
        $this->assertTrue(PermissionToggleService::getInstance()->isFeatureEnabledForProductVariant('PRO','FEATURE2'));
        $this->assertTrue(PermissionToggleService::getInstance()->isFeatureEnabledForProductVariant('LITE','FEATURE1'));
        $this->assertFalse(PermissionToggleService::getInstance()->isFeatureEnabledForProductVariant('LITE','FEATURE2'));
    }

    public function testGetAllmigratedToggleVersions()
    {
        $res = PermissionToggleService::getInstance()->getAllmigratedToggleVersions();
        $this->assertCount(2, $res);
    }

    public function testCreatePermissionToggle(){
        $toggle = $this->getToggle();
        $toggle = PermissionToggleService::getInstance()->createPermissionToggle($toggle);
        $this->assertDatabaseHas('permission_toggle', [
            "name" => $toggle->name,
            "code" => $toggle->code
        ]);
    }
}
<?php

namespace test\service;

use com\linways\base\test\BaseTestCase;
use com\linways\nucleus\core\dto\College;
use com\linways\nucleus\core\service\CollegeService;

class CollegeServiceTest extends BaseTestCase
{

    protected function setUp()
    {
        $nucleusTestDBConfPath = __DIR__ . '/../db_conf/nucleus_db_conf.php';
        putenv("TEST_DB_CONFIG=$nucleusTestDBConfPath");
        putenv("NUCLEUS_CONF=$nucleusTestDBConfPath");
        $this->clearDBTable(['colleges','clients']);
    }

    protected function tearDown()
    {

    }

    public function getCollege()
    {
        $college = new College();
        $client = new ClientServiceTest();
        $college->code = 'PRO';
        $college->name = 'Linways';
        $college->hostName = 'host.linways.com';
        $college->internalHostName = 'internal.linways.com';
        $college->dbUsername = 'root';
        $college->dbPassword = 'root';
        $college->dbHost = 'localhost';
        $college->clientId = $client->testCreateClient();
        return $college;
    }

    public function testCreateCollege()
    {
        $data = array();
        $college = $this->getCollege();
        $data['clientId'] = $college->clientId;
        $result = CollegeService::getInstance()->createCollege($college);
        $data['collegeId'] = $result;
        $this->assertInternalType('integer', $result);
        return $data;
    }

    public function testSuccessForgetAllColleges()
    {
        $id = $this->testCreateCollege();
        $college = $this->getCollege();
        $result = CollegeService::getInstance()->getAllColleges();
        $this->assertEquals(1, count($result));
        $this->assertEquals($college->dbUsername, $result[0]->dbUsername);
    }

    public function testgetBillingColleges()
    {
        $this->markTestIncomplete('This test has not been implemented yet.
      since addling client id  to college is not implemented');
    }


    public function testSuccessForGetCountOfCollegesByClientId()
    {
        $collegeDetails = $this->testCreateCollege();
        $result = CollegeService::getInstance()->getCountOfCollegesByClientId($collegeDetails['clientId']);
        $this->assertInternalType('integer', $result);
        $this->assertEquals('1', $result);
    }

    public function testFailureForGetCountOfCollegesByClientId()
    {
        $collegeDetails = $this->testCreateCollege();
        $result = CollegeService::getInstance()->getCountOfCollegesByClientId(245);
        $this->assertInternalType('integer', $result);
        $this->assertEquals('0', $result);
    }

    // /**
    //  * @expectedException \com\linways\nucleus\core\exception\NucleusException
    //  * @expectedExceptionCode \com\linways\nucleus\core\exception\NucleusException::CLIENT_ID_REQUIRED
    //  */
    // public function testFailureByEmptyIdForGetCountOfCollegesByClientId()
    // {
    //     $collegeDetails = $this->testCreateCollege();
    //     $clientId = '';
    //     $result = CollegeService::getInstance()->getCountOfCollegesByClientId($clientId);
    // }

    public function testSuccessForDeleteCollege()
    {
        $collegeDetails = $this->testCreateCollege();
        $college = new College();
        $college->id = $collegeDetails['collegeId'];
        $result = CollegeService::getInstance()->deleteCollege($college);
        $this->assertInternaltype('boolean', $result);
        $this->assertEquals(true, $result);
    }

    public function testSuccessGetCollgeByCode()
    {
      $collegeDetails = $this->testCreateCollege();
      $result = CollegeService::getInstance()->getCollegeByCollegeCode('PRO');
      $this->assertEquals('Linways', $result->name);
    }

    public function testSuccessGetSubdomainByCollgeCode(){
        $id = $this->testCreateCollege();
        $host = CollegeService::getInstance()->getHostNameByCollegeCode('PRO');
        $this->assertEquals('host.linways.com', $host);
        $ihost = CollegeService::getInstance()->getHostNameByCollegeCode('PRO', true);
        $this->assertEquals('internal.linways.com', $ihost);

    }
}
<?php

namespace service;

use com\linways\base\test\BaseTestCase;
use com\linways\nucleus\core\dto\Expense;
use com\linways\nucleus\core\service\ExpenseService;
use test\service\ExpenseCategoryServiceTest;

class ExpenseServiceTest extends BaseTestCase
{

    protected function setUp()
    {
        $nucleusTestDBConfPath = __DIR__ . '/../db_conf/nucleus_db_conf.php';
        putenv("TEST_DB_CONFIG=$nucleusTestDBConfPath");
        putenv("NUCLEUS_CONF=$nucleusTestDBConfPath");
        $this->clearDBTable(['expenses','expense_category']);

    }

    protected function tearDown()
    {
    }

    public function getExpense()
    {
        $expenseCategory = new ExpenseCategoryServiceTest();
        $expense = new Expense();
        $expense->item = 'Linways';
        $expense->amount = '232';
        $expense->date = date('Y-m-d H:i:s');
        $expense->expenseCategoryId = $expenseCategory->testCreateExpenseCategory();
        return $expense;
    }

    public function testCreateExpense()
    {
        $expense = $this->getExpense();
        $result = ExpenseService::getInstance()->createExpense($expense);
        $this->assertInternalType('integer', $result);
    }

    public function testUpsertInsert()
    {
        $expense = $this->getExpense();
        $result = ExpenseService::getInstance()->upsertExpense($expense);
        $this->assertInternalType('integer', $result);
    }

    public function testUpsertUpdate()
    {
        $expense = $this->getExpense();
        $id = ExpenseService::getInstance()->createExpense($expense);;
        $expense1 = $this->getExpense();
        $expense1->id = $id;
        $expense1->item = "Edited Name";
        $result = ExpenseService::getInstance()->upsertExpense($expense1);
        $this->assertDatabaseHas('expenses', [
            "item" => $expense1->item
        ]);
    }

    public function testDeleteExpense()
    {
        $expense = $this->getExpense();
        $result = ExpenseService::getInstance()->createExpense($expense);
        $this->assertInternalType('integer', $result);
        $res = ExpenseService::getInstance()->deleteExpense($result);
        $this->assertInternalType('null', $res);
    }

    public function testGetTotalExpenseByCategoryAndMonth()
    {
        $this->markTestIncomplete('This is not implemented');
    }

    public function testAverageExpenseForSingleTenant()
    {
        $this->markTestIncomplete('This is not implemented');
    }

    public function testGetTotalIncomeFromBillingCustomers()
    {
        $this->markTestIncomplete('This is not implemented');
    }

    public function testSuccessForGetAllExpensesOfSingleMonth()
    {
        $this->testCreateExpense();
        $result = ExpenseService::getInstance()->getAllExpensesOfSingleMonth(date('Y'), date('m'));
        $this->assertInternalType('array', $result);
        $this->assertNotEmpty($result);
    }

    public function testFailureForGetAllExpensesOfSingleMonth()
    {
        $this->testCreateExpense();
        $result = ExpenseService::getInstance()->getAllExpensesOfSingleMonth(2014, 12);
        $this->assertInternalType('array', $result);
        $this->assertEmpty($result);
    }

}
<?php
/**
 * Created by PhpStorm.
 * User: jithin
 * Date: 4/1/18
 * Time: 11:25 AM
 */

namespace test\service;


use com\linways\base\test\BaseTestCase;
use com\linways\nucleus\core\dto\ExpenseCategory;
use com\linways\nucleus\core\service\ExpenseCategoryService;

class ExpenseCategoryServiceTest extends BaseTestCase
{
    protected function setUp()
    {
        $nucleusTestDBConfPath = __DIR__ . '/../db_conf/nucleus_db_conf.php';
        putenv("TEST_DB_CONFIG=$nucleusTestDBConfPath");
        putenv("NUCLEUS_CONF=$nucleusTestDBConfPath");
        $this->clearDBTable(['expenses','expense_category']);

    }

    protected function tearDown()
    {
    }

    public function getExpenseCategory()
    {
        $expenseCategory = new ExpenseCategory();
        $expenseCategory->name = 'CHT';
        return $expenseCategory;
    }

    public function testCreateExpenseCategory()
    {
        $expenseCategory = $this->getExpenseCategory();
        $result = ExpenseCategoryService::getInstance()->createExpenseCategory($expenseCategory);
        $this->assertInternalType('integer', $result);
//        $this->assertEquals('1', $result);
        return $result;
    }
}<?php

namespace test\service;

use com\linways\base\test\BaseTestCase;
use com\linways\nucleus\core\service\SchedulerService;
use Cron\CronExpression;

class SchedulerServiceTest extends BaseTestCase {
	protected function setUp() {
		$nucleusTestDBConfPath = __DIR__ .'/../db_conf/nucleus_db_conf.php';
		putenv("TEST_DB_CONFIG=$nucleusTestDBConfPath");
		putenv("NUCLEUS_CONF=$nucleusTestDBConfPath");
        $this->clearDBTable('scheduled_task');
    }

	protected function tearDown() {
	}

	public function getScheduleArgs() {
		$args         = new \stdClass();
		$args->method = 'SomeClass::SomeMethod';
		$args->params = [
			'param1' => 'value1',
			'param2' => 'value2'
		];
		$args->frequency = '0 4 * */1 *';
		$args->code      = 'UNIQUE_CODE';
		date_default_timezone_set('Asia/Kolkata');
		$startDate             = time();
		$args->nextExecutionAt = date('Y-m-d H:i:s', strtotime('+1 hour', $startDate));
		return $args;
	}

	public function testSuccessfulSchedule() {
		$schedule = $this->getScheduleArgs();
		$id       = SchedulerService::getInstance()->addTask($schedule);
		$this->assertInternalType('integer', $id);
		return $id;
	}

	public function testSuccessfulScheduleWithFrequencyOnly() {
		$schedule                  = $this->getScheduleArgs();
		$schedule->code            = null;
		$schedule->nextExecutionAt = null;
		$id                        = SchedulerService::getInstance()->addTask($schedule);
		return $this->assertInternalType('integer', $id);
	}
	public function testSuccessfulScheduleWithNextExecutionAtOnly() {
		$schedule            = $this->getScheduleArgs();
		$schedule->frequency = null;
		$schedule->code      = null;
		$id                  = SchedulerService::getInstance()->addTask($schedule);
		return $this->assertInternalType('integer', $id);
	}
	/**
	 * @expectedException InvalidArgumentException
	 */
	public function testScheduleFailureWithoutFrequencyAndNextExecutionAt() {
		$schedule                  = $this->getScheduleArgs();
		$schedule->frequency       = null;
		$schedule->code            = null;
		$schedule->nextExecutionAt = null;
		$id                        = SchedulerService::getInstance()->addTask($schedule);
	}

//	/**
//	 * @expectedException InvalidArgumentException
//	 */
//	public function testScheduleFailureWithoutmethod() {
//		$schedule         = $this->getScheduleArgs();
//		$schedule->method = null;
//		$id               = SchedulerService::getInstance()->addTask($schedule);
//	}

	/**
	 * @expectedException mysqli_sql_exception
	 * @expectedExceptionCode 1062
	 */
	public function testShouldFailOnNonUniqueCode() {
		mysqli_report(MYSQLI_REPORT_ERROR|MYSQLI_REPORT_STRICT);
		$schedule = $this->getScheduleArgs();
		SchedulerService::getInstance()->addTask($schedule);
		$schedule = $this->getScheduleArgs();
		SchedulerService::getInstance()->addTask($schedule);
	}

	public function testScheduleDeletionByIdSuccess() {
		$id  = $this->testSuccessfulSchedule();
		$res = SchedulerService::getInstance()->deleteScheduledTaskById($id);
		$this->assertDatabaseHasNot('scheduled_task', [
				'id' => $id
			]);
	}

	/**
	 * @expectedException InvalidArgumentException
	 */
	public function testScheduleDeletionFailureWithoutId() {
		SchedulerService::getInstance()->deleteScheduledTaskById(null);
	}

	public function testScheduleDeletionByCodeSuccess() {
		$schedule = $this->getScheduleArgs();
		$id       = SchedulerService::getInstance()->addTask(clone($schedule));
		$res      = SchedulerService::getInstance()->deleteScheduledTaskByCode($schedule->code);
		$this->assertDatabaseHasNot('scheduled_task', [
				'code' => $schedule->code
			]);
	}

	/**
	 * @expectedException InvalidArgumentException
	 */
	public function testScheduleDeletionFailureWithoutCode() {
		SchedulerService::getInstance()->deleteScheduledTaskByCode(null);
	}

	public function testGetScheduleById() {
		$id       = $this->testSuccessfulSchedule();
		$schedule = $this->getScheduleArgs();
		$result   = SchedulerService::getInstance()->getScheduledTaskById($id);
		$this->assertEquals($schedule->code, $result->code);
	}

	public function testCalculateAndUpdateNextExecutionAtSuccess() {
		$id       = $this->testSuccessfulSchedule();
		$schedule = SchedulerService::getInstance()->getScheduledTaskById($id);
		$cronExp  = CronExpression::factory($schedule->frequency);
		SchedulerService::getInstance()->updateNextExecution($schedule);
		$this->assertDatabaseHas('scheduled_task', [
				'id'                => $schedule->id,
				'next_execution_at' => $cronExp->getNextRunDate()->format('Y-m-d H:i:s'),
				'last_executed_at'  => $schedule->nextExecutionAt
			]);
	}

	public function testGetDueTasks() {
		$this->testSuccessfulSchedule();// next exec time at +1 hr (NOT DUE)
		$schedule                  = $this->getScheduleArgs();
		$schedule->code            = null;
		$schedule->nextExecutionAt = date('Y-m-d H:i:s');// next exec now (DUE)
		SchedulerService::getInstance()->addTask($schedule);
		$this->testSuccessfulScheduleWithFrequencyOnly();// null next exec at (NOT DUE)
		$res = SchedulerService::getInstance()->getDueTasks();
		$this->assertEquals(1, sizeof($res));
	}
}
<?php

namespace test\service;

use com\linways\base\test\BaseTestCase;
use com\linways\nucleus\core\dto\Client;
use com\linways\nucleus\core\service\ClientService;

class ClientServiceTest extends BaseTestCase
{

    protected function setUp()
    {
        $nucleusTestDBConfPath = __DIR__ . '/../db_conf/nucleus_db_conf.php';
        putenv("TEST_DB_CONFIG=$nucleusTestDBConfPath");
        putenv("NUCLEUS_CONF=$nucleusTestDBConfPath");
        $this->clearDBTable('clients');

    }

    protected function tearDown()
    {
        $this->clearDBTable('clients');

    }

    public function getClient()
    {
        $client = new Client();
        $client->name = 'Linways';
        $client->address = 'Linways technologies pvt. ltd.';
        $client->totalAnnualIncome = 125000;
        $client->serviceStartDate = date('Y-m-d H:i:s');
        $client->billingStartDate = date('Y-m-d H:i:s');
        return $client;
    }

    public function testCreateClient()
    {
        $client = $this->getClient();
        $result = ClientService::getInstance()->createClient($client);
        $this->assertInternalType('integer', $result);
        return $result;
    }

    public function testgetAllClients()
    {
        $id = $this->testCreateClient();
        $client = $this->getClient();
        $result = ClientService::getInstance()->getAllClients();
        $this->assertEquals(1, count($result));
        $this->assertEquals($client->name, $result[0]->name);
    }

    public function testUpsertInsert()
    {
        $client = $this->getClient();
        $result = ClientService::getInstance()->upsertClient($client);
        $this->assertInternalType('integer', $result);
    }

    public function testUpsertUpdate()
    {
        $client = $this->getClient();
        $id = ClientService::getInstance()->createClient($client);;
        $client1 = $this->getClient();
        $client1->id = $id;
        $client1->name = "Edited Name";
        $result = ClientService::getInstance()->upsertClient($client1);
        $this->assertDatabaseHas('clients', [
            "name" => $client1->name
        ]);
    }

    public function testDeleteClient()
    {
        $client = $this->getClient();
        $result = ClientService::getInstance()->createClient($client);
        $this->assertInternalType('integer', $result);
        $res = ClientService::getInstance()->deleteClient($result);
        $this->assertInternalType('null', $res);
    }


}
{
  "name": "linways/nucleus-core",
  "description": "Project for tenant and api access management for professional",
  "type": "project",
  "require": {
    "linways/linways-base": "^1.0.0",
    "robmorgan/phinx": "^0.9.1",
    "firebase/php-jwt": "^5.0",
    "dragonmantank/cron-expression": "^1.2"
  },
  "autoload": {
    "psr-4": {
      "com\\linways\\": "src/com/linways"
    }
  },
  "repositories": [
    {
      "type": "vcs",
      "url": "git@gitlab.com:linways/linways-base.git",
      "name": "linways-base"
    }
  ],
  "require-dev": {
    "phpunit/phpunit": "^5.7"
  },
  "autoload-dev": {
    "psr-4": {
      "test\\": "test/"
    }
  }
}
<?php
namespace com\linways\nucleus\core\dto;

use com\linways\base\dto\BaseDTO;

class Addon extends BaseDTO
{
    /**
     * 
     * @var integer
     */
    public $id;
    
    /**
     * 
     * @var string
     */
    public $name;
    
    /**
     *
     * @var string
     */
    public $description;
  
    /**
     *
     * @var string
     */
    public $scope;
    
    /**
     *
     * @var string
     */
    public $url;
    
    /**
     *
     * @var string
     */
    public $packageName;
    
    /**
     * 
     * @var AddonAttribute
     */
    public $addonAttribute;
    
    /**
     * 
     * @var College
     */
    public $college;

    /**
     * @var integer
     */
    public $enabledAddonId;

    
}

<?php
namespace com\linways\nucleus\core\dto;

use com\linways\base\dto\BaseDTO;

class AddonAttributeValue extends BaseDTO
{
    /**
     * 
     * @var integer
     */
    public $id;
    
    /**
     * 
     * @var integer
     */
    public $addonAttributeId;
    
    /**
     * 
     * @var AddonAttribute
     */
    public $addonAttribute;
    
    /**
     * 
     * @var string
     */
    public $value;
    
    /**
     * 
     * @var integer
     */
    public $addonEnabledId;
}

<?php
namespace com\linways\nucleus\core\dto;

use com\linways\base\dto\BaseDTO;

class AddonAttribute extends BaseDTO
{
    /**
     * 
     * @var integer
     */
   public $id;
   
   /**
    * 
    * @var integer
    */
   public $addonId;
   
   /**
    * 
    * @var Addon
    */
   public $addon;
   
   /**
     * 
     * @var string
     */
   public $type;
   
   /**
    * 
    * @var string
    */
   public $name;
   
   /**
    *
    * @var string
    */
   public $code;
   
   /**
    *
    * @var string
    */
   public $dataType;
   
   /**
    * 
    * @var boolean
    */
   public $required;
   
   /**
    * 
    * @var AddonAttributeValue
    */
   public $addonAttributeValue;
}

<?php
/**
 * Created by PhpStorm.
 * User: jithin
 * Date: 1/11/17
 * Time: 3:23 PM
 */

namespace com\linways\nucleus\core\dto;


use com\linways\base\dto\BaseDTO;

class ApiOrganizationDetails extends BaseDTO
{
    /**
     * @var integer
     */
    public $id;

    /**
     * @var string
     */
    public $name;

    /**
     * @var string
     */
    public $address;

    /**
     * @var string
     */
    public $contactNo;

    /**
     * @var string
     */
    public $email;

    /**
     * @var string
     */
    public $notes;

}<?php
namespace com\linways\nucleus\core\dto;

use com\linways\base\dto\BaseDTO;

class AddonEnabled extends BaseDTO
{
    /**
     * 
     * @var integer
     */
    public $id;
    
    /**
     * 
     * @var integer
     */
    public $addonId;
    
    /**
     * 
     * @var Addon
     */
    public $addon;
    
    /**
     * 
     * @var integer
     */
    public $collegeId;
    
    /**
     * 
     * @var College
     */
    public $college;
    
    /**
     * scope id is null when scope is "INSTITUTION"
     * @var string
     */
    public $scopeId;
    
    /**
     * 
     * @var [AddonAttribute]
     */
    public $addonAttributes=[];

    /**
     * @var [AddonDataStore]
     */
    public $addonDataStore =[];
}

<?php 
namespace com\linways\nucleus\core\dto\user;

use com\linways\base\dto\BaseDTO;

class User extends BaseDTO{
   
    public $id;
    public $name;
    public $email;
    public $username;
    public $password;
    public $salt;

}<?php
/**
 * Created by PhpStorm.
 * User: jithin
 * Date: 30/10/17
 * Time: 3:49 PM
 */

namespace com\linways\nucleus\core\dto;


use com\linways\base\dto\BaseDTO;

class AddonDataStore extends BaseDTO
{
    /**
     * @var integer
     */
    public $id;

    /**
     * @var integer
     */
    public $enabledAddonId;

    /**
     * @var string
     */
    public $key;

    /**
     * @var string
     */
    public $value;

}<?php
namespace com\linways\nucleus\core\dto;

use DateTime;
use com\linways\base\dto\BaseDTO;

class ExpenseRecurringAmount extends BaseDTO
{
    /**
     *
     * @var int
     */
    public $id;

    /**
     *
     * @var ExpenseRecurring
     */
    public $expenseRecurring;

    /**
     *
     * @var int
     */
    public $expenseRecurringId;

    /**
     *
     * @var fload
     */
    public $amount;

    /**
     *
     * @var DateTime
     */
    public $fromDate;

    /**
     *
     * @var DateTime
     */
    public $toDate;

    /**
     *
     * @var string
     */
    public $notes;
}
<?php
namespace com\linways\nucleus\core\dto;

use DateTime;
use com\linways\base\dto\BaseDTO;

class Expense extends BaseDTO
{
    /**
     *
     * @var int
     */
    public $id;

    /**
     *
     * @var string
     */
    public $item;

    /**
     *
     * @var float
     */
    public $amount;

    /**
     *
     * @var ExpenseCategory
     */
    public $expenseCategory;

    /**
     *
     * @var int
     */
    public $expenseCategoryId;

    /**
     *
     * @var DateTime
     */
    public $date;

}
<?php
namespace com\linways\nucleus\core\dto;

use com\linways\base\dto\BaseDTO;

/**
 * DTO for schedule
 */

class ScheduledTask extends BaseDTO {
	/**
	 *
	 * @var int
	 */
	public $id;

	/**
	 *
	 * @var string
	 */
	public $code;

	/**
	 *
	 * @var string
	 */
	public $conf;

	/**
	 *
	 * @var string
	 */
	public $params;

	/**
	 *
	 * @var string - The cron expression
	 */
	public $frequency;

	/**
	 *
	 * @var \DateTime
	 */
	public $nextExecutionAt;

	/**
	 *
	 * @var \DateTime
	 */
	public $lastExecutedAt;

	/**
	 *
	 * @var \DateTime
	 */
	public $createdAt;
}
<?php
namespace com\linways\nucleus\core\dto;

use com\linways\base\dto\BaseDTO;

class College extends BaseDTO
{
    /**
     *
     * @var int
     */
    public $id;

    /**
     *
     * @var string
     */
    public $code;

    /**
     * @var string
     */
    public $internalHostName;

    /**
     * This can be used to assign custom domain for a client
     * @var string
     */
    public $hostName;
    /**
     *
     * @var string
     */
    public $name;

    /**
     *
     * @var string
     */
    public $collegeDb;

    /**
     * @var string
     */
    public $dbUsername;
    /**
     * @var string
     */
    public $dbPassword;
    /**
     * @var string
     */
    public $dbHost;
    /**
     * @var int
     */
    public $clientId;
    /**
     * @var Client
     */
    public $client;

    /**
     *
     * @var AddonEnabled
     */
    public $addonsEnabled = [];

}
<?php

namespace com\linways\nucleus\core\dto;


use com\linways\base\dto\BaseDTO;

class ProductVariant extends BaseDTO
{
    public $id;
    public $code;
    public $name;
    public $description;
    public $isActive;
}<?php
namespace com\linways\nucleus\core\dto;

use com\linways\base\dto\BaseDTO;

class ExpenseRecurring extends BaseDTO
{
    /**
     *
     * @var int
     */
    public $id;

    /**
     *
     * @var string
     */
    public $name;

    /**
     *
     * @var string
     */
    public $description;

    /**
     *
     * @var ExpenseCategory
     */
    public $expenseCategory;

    /**
     *
     * @var int
     */
    public $expenseCategoryId;

    /**
     * @var ExpenseRecurringAmount[]
     */
    public $expenseRecurringAmount;
}
<?php
namespace com\linways\nucleus\core\dto;

use com\linways\base\dto\BaseDTO;

class ExpenseCategory extends BaseDTO
{
    /**
     *
     * @var int
     */
    public $id;

    /**
     *
     * @var string
     */
    public $name;

}
<?php
namespace com\linways\nucleus\core\dto;

use com\linways\base\dto\BaseDTO;

class SingleSignonLog extends BaseDTO{
    public $id;
    public $userId;
    public $collegeCode;
    public $loggedInAs;
    public $loginTime;
}<?php
namespace com\linways\nucleus\core\dto;

use DateTime;
use com\linways\base\dto\BaseDTO;

/**
 * Client details. (not tenant info)
 */
class Client extends BaseDTO
{
    /**
     *
     * @var int
     */
    public $id;

    /**
     *
     * @var string
     */
    public $name;

    /**
     *
     * @var string
     */
    public $address;

    /**
     *
     * @var int
     */
    public $totalAnnualIncome;

    /**
     *
     * @var DateTime
     */
    public $serviceStartDate;

    /**
     *
     * @var DateTime
     */
    public $billingStartDate;

}
<?php
/**
 * Created by PhpStorm.
 * User: rohithb
 * Date: 02/08/18
 * Time: 12:54 PM
 */

namespace com\linways\nucleus\core\constants;


class PermissionConstants
{
    const SINGLE_SIGN_ON_STAFF = "SINGLE_SIGN_ON_STAFF";
}<?php
namespace com\linways\nucleus\core\constants;

class Scope
{
    const INSTITUTION = "INSTITUTION";
}

<?php
namespace com\linways\nucleus\core\constants;

class SingleSignonConstant{
    const ADMIN = "ADMIN";
    const STAFF_ID = 'STAFF_ID';
    const STAFF_LOGIN = 'STAFF_LOGIN';
    const STAFF_EMAIL = 'STAFF_EMAIL';

    const STUDENT_ID = 'STUDENT_ID';
    const STUDENT_LOGIN = 'STUDENT_LOGIN';
    const STUDENT_EMAIL = 'STUDENT_EMAIL';
}<?php

namespace com\linways\nucleus\core\mapper;

use com\linways\base\mapper\IMapper;
use com\linways\base\mapper\Result;
use com\linways\base\mapper\ResultMap;

class ClientServiceMapper implements IMapper
{

    private $mapper = [];
    private static $_instance = null;

    private function __construct()
    {
    }

    private function __clone()
    {
    }

    public static function getInstance()
    {
        if (!is_object(self::$_instance))
            self::$_instance = new self();
        return self::$_instance;
    }

    const GET_ALL_CLIENTS = "GET_ALL_CLIENTS";
    const GET_BASIC_DETAILS_OF_ALL_CLIENTS = "GET_BASIC_DETAILS_OF_ALL_CLIENTS";

    public function getMapper()
    {
        if (empty($this->mapper)) {
            $this->mapper[self::GET_ALL_CLIENTS] = $this->getAllClients();
            $this->mapper[self::GET_BASIC_DETAILS_OF_ALL_CLIENTS] = $this->getBasicDetailsOfAllClients();
        }

        return $this->mapper;
    }

    private function getAllClients()
    {
        $mapper = null;

        $mapper = new ResultMap("getAllClients", 'com\linways\nucleus\core\dto\Client', 'id', 'id');
        $mapper->results = [];
        $mapper->results[] = new Result('id', 'id');
        $mapper->results[] = new Result('name', 'name');
        $mapper->results[] = new Result('address', 'address');
        $mapper->results[] = new Result('totalAnnualIncome', 'total_annual_income');
        $mapper->results[] = new Result('serviceStartDate', 'service_start_date');
        $mapper->results[] = new Result('billingStartDate', 'billing_start_date');
        $mapper->results[] = new Result('createdBy', 'created_by');
        $mapper->results[] = new Result('createdAt', 'created_at');
        $mapper->results[] = new Result('updatedBy', 'updated_by');
        $mapper->results[] = new Result('updatedAt', 'updated_at');

        return $mapper;
    }

    private function getBasicDetailsOfAllClients()
    {
        $mapper = null;
        $mapper = new ResultMap("getAllBasicDetailsOfClients", 'com\linways\nucleus\core\dto\Client', 'id', 'id');
        $mapper->results = [];
        $mapper->results[] = new Result('id', 'id');
        $mapper->results[] = new Result('name', 'name');
        return $mapper;
    }
}
<?php
namespace com\linways\nucleus\core\mapper;

use com\linways\base\mapper\IMapper;
use com\linways\base\mapper\Result;
use com\linways\base\mapper\ResultMap;

class SchedulerServiceMapper implements IMapper
{

    private $mapper = [];

    // /Condition 1 - Presence of a static member variable
    private static $_instance = null;

    // /Condition 2 - Locked down the constructor
    private function __construct()
    {}

    // Prevent any oustide instantiation of this class

    // /Condition 3 - Prevent any object or instance of that class to be cloned
    private function __clone()
    {}

    // Prevent any copy of this object

    // /Condition 4 - Have a single globally accessible static method
    public static function getInstance()
    {
        if (! is_object(self::$_instance)) // or if( is_null(self::$_instance) ) or if( self::$_instance == null )
            self::$_instance = new self();

        return self::$_instance;
    }

    const GET_SCHEDULE_MAPPER = "GET_SCHEDULE_MAPPER";

    public function getMapper()
    {
        if (empty($this->mapper)) {
            $this->mapper[self::GET_SCHEDULE_MAPPER] = $this->getScheduleMapper();
        }

        return $this->mapper;
    }

    private function getScheduleMapper(){
        $mapper = null;
        $mapper = new ResultMap("scheduleMapper", 'com\linways\nucleus\core\dto\ScheduledTask', 'id', 'id');
        $mapper->results[] = new Result('id', 'id');
        $mapper->results[] = new Result('code', 'code');
        $mapper->results[] = new Result('conf', 'conf');
        $mapper->results[] = new Result('params', 'params');
        $mapper->results[] = new Result('frequency', 'frequency');
        $mapper->results[] = new Result('nextExecutionAt', 'next_execution_at');
        $mapper->results[] = new Result('lastExecutedAt', 'last_executed_at');
        $mapper->results[] = new Result('createdAt', 'created_at');
        return $mapper;

    }
}
<?php
namespace com\linways\nucleus\core\mapper;

use com\linways\base\mapper\IMapper;
use com\linways\base\mapper\Result;
use com\linways\base\mapper\ResultMap;

class CollegeServiceMapper implements IMapper
{

    private $mapper = [];

    // /Condition 1 - Presence of a static member variable
    private static $_instance = null;

    // /Condition 2 - Locked down the constructor
    private function __construct()
    {}

    // Prevent any oustide instantiation of this class

    // /Condition 3 - Prevent any object or instance of that class to be cloned
    private function __clone()
    {}

    // Prevent any copy of this object

    // /Condition 4 - Have a single globally accessible static method
    public static function getInstance()
    {
        if (! is_object(self::$_instance)) // or if( is_null(self::$_instance) ) or if( self::$_instance == null )
            self::$_instance = new self();

        return self::$_instance;
    }

    const GET_ALL_COLLEGES = "GET_ALL_COLLEGES";
    const GET_BILLING_COLLEGES = "GET_BILLING_COLLEGES";

    public function getMapper()
    {
        if (empty($this->mapper)) {
          $this->mapper[self::GET_ALL_COLLEGES] = $this->getAllCollegesMapper();
            $this->mapper[self::GET_BILLING_COLLEGES] = $this->getBillingCollegesMapper();
        }

        return $this->mapper;
    }

    private function getAllCollegesMapper()
    {
        $mapper = null;
        $mapper = new ResultMap("getAllCollegesMapper", 'com\linways\nucleus\core\dto\College', 'id', 'id');
        $mapper->results = [];
        $mapper->results[] = new Result('id', 'id');
        $mapper->results[] = new Result('code', 'college_code');
        $mapper->results[] = new Result('hostName', 'host_name');
        $mapper->results[] = new Result('internalHostName', 'internal_host_name');
        $mapper->results[] = new Result('name', 'college_name');
        $mapper->results[] = new Result('collegeDb', 'college_db');
        $mapper->results[] = new Result('dbUsername', 'db_username');
        $mapper->results[] = new Result('dbPassword', 'db_password');
        $mapper->results[] = new Result('dbHost', 'db_host');
        return $mapper;
    }
    private function getClientMapper(){
      $mapper = null;
      $mapper = new ResultMap("getAllCollegesMapper", 'com\linways\nucleus\core\dto\Client', 'id', 'id');
      $mapper->results = [];
      $mapper->results[] = new Result('id', 'id');
      $mapper->results[] = new Result('name', 'name');
      $mapper->results[] = new Result('address', 'address');
      $mapper->results[] = new Result('sericeStartDate', 'service_start_date');
      $mapper->results[] = new Result('billingStartDate', 'billing_start_date');
      return $mapper;
    }

    private function getBillingCollegesMapper(){
      $mapper = null;
      $mapper = new ResultMap("getAllCollegesMapper", 'com\linways\nucleus\core\dto\College', 'id', 'id');
      $mapper->results = [];
      $mapper->results[] = new Result('id', 'id');
      $mapper->results[] = new Result('code', 'college_code');
      $mapper->results[] = new Result('name', 'college_name');
      $mapper->results[] = new Result('client', 'expenseCategory', Result::OBJECT, $this->getClientMapper());
      return $mapper;
    }
}
<?php
/**
 * Created by PhpStorm.
 * User: jithin
 * Date: 30/10/17
 * Time: 2:29 PM
 */

namespace com\linways\nucleus\core\mapper;


use com\linways\base\mapper\IMapper;
use com\linways\base\mapper\Result;
use com\linways\base\mapper\ResultMap;

class AddonDataStoreMapper implements IMapper
{
    private $mapper = [];

    // /Condition 1 - Presence of a static member variable
    private static $_instance = null;

    // /Condition 2 - Locked down the constructor
    private function __construct()
    {
    }

    // Prevent any oustide instantiation of this class

    // /Condition 3 - Prevent any object or instance of that class to be cloned
    private function __clone()
    {
    }

    // Prevent any copy of this object

    // /Condition 4 - Have a single globally accessible static method
    public static function getInstance()
    {
        if (!is_object(self::$_instance)) // or if( is_null(self::$_instance) ) or if( self::$_instance == null )
            self::$_instance = new self();

        return self::$_instance;
    }

    const GET_ADDON_DATA_STORE_MAPPER = "GET_ADDON_DATA_STORE_MAPPER";

    public function getMapper()
    {
        if (empty($this->mapper)) {
            $this->mapper[self::GET_ADDON_DATA_STORE_MAPPER] = $this->getAddonDataStoreMapper();
        }
        return $this->mapper;
    }

    protected function getAddonDataStoreMapper(){
        $mapper = null;
        $mapper = new ResultMap('getAddonDataStoreMapper','com\linways\nucleus\core\dto\AddonDataStore','id','id');
        $mapper->results[] = new Result('id','id');
        $mapper->results[] = new Result('enabledAddonId','enabled_addon_id');
        $mapper->results[] = new Result('key','key');
        $mapper->results[] = new Result('value','value');
        return$mapper;
    }
}<?php
/**
 * Created by PhpStorm.
 * User: rohithb
 * Date: 22/10/18
 * Time: 9:57 AM
 */

namespace com\linways\nucleus\core\mapper;


use com\linways\base\mapper\Result;
use com\linways\base\mapper\ResultMap;

class ProductVariantServiceMapper
{
    private $mapper = [];

    private static $_instance = null;
    private function __construct() {}
    private function __clone() {}
    public static function getInstance()
    {
        if (! is_object(self::$_instance)) // or if( is_null(self::$_instance) ) or if( self::$_instance == null )
            self::$_instance = new self();

        return self::$_instance;
    }

    const PRODUCT_VARIANT = "PRODUCT_VARIANT";

    public function getMapper()
    {
        if (empty($this->mapper)) {
            $this->mapper[self::PRODUCT_VARIANT] = $this->getProductVariantMapper();
        }
        return $this->mapper;
    }

    private function getProductVariantMapper()
    {
        $mapper = null;
        $mapper = new ResultMap("getProductVariantMapper", 'com\linways\nucleus\core\dto\ProductVariant', 'id', 'id');
        $mapper->results = [];
        $mapper->results[] = new Result('id', 'id');
        $mapper->results[] = new Result('code', 'code');
        $mapper->results[] = new Result('name', 'name');
        $mapper->results[] = new Result('description', 'description');
        $mapper->results[] = new Result('isActive', 'is_active');
        $mapper->results[] = new Result('createdBy', 'created_by');
        $mapper->results[] = new Result('createdDate', 'created_at');
        $mapper->results[] = new Result('updatedBy', 'updated_by');
        $mapper->results[] = new Result('updatedDate', 'updated_at');
        return $mapper;
    }

}<?php
namespace com\linways\nucleus\core\mapper;

use com\linways\base\mapper\IMapper;
use com\linways\base\mapper\Result;
use com\linways\base\mapper\ResultMap;

class ExpenseServiceMapper implements IMapper
{

    private $mapper = [];
    private static $_instance = null;

    private function __construct()
    {}

    private function __clone()
    {}
    public static function getInstance()
    {
        if (! is_object(self::$_instance))
            self::$_instance = new self();
        return self::$_instance;
    }

    const GET_ALL_EXPENSES = "GET_ALL_EXPENSES";
    const GET_EXPENSE_CATEGORY_MAPPER = "GET_EXPENSE_CATEGORY_MAPPER";

    public function getMapper()
    {
        if (empty($this->mapper)) {
          $this->mapper[self::GET_ALL_EXPENSES] = $this->getAllExpenses();
            $this->mapper[self::GET_EXPENSE_CATEGORY_MAPPER] = $this->getExpenseCategoryMapper();
        }

        return $this->mapper;
    }

    private function getExpenseCategoryMapper(){
      $mapper = null;

      $mapper = new ResultMap("getExpenseCategoryMapper", 'com\linways\nucleus\core\dto\ExpenseCategory', 'id', 'id');
      $mapper->results = [];
      $mapper->results[] = new Result('id', 'id');
      $mapper->results[] = new Result('name', 'name');
      return $mapper;
    }
    private function getAllExpenses()
    {
        $mapper = null;

        $mapper = new ResultMap("getAllClients", 'com\linways\nucleus\core\dto\Expense', 'id', 'id');
        $mapper->results = [];
        $mapper->results[] = new Result('id', 'id');
        $mapper->results[] = new Result('item', 'item');
        $mapper->results[] = new Result('amount', 'amount');
        $mapper->results[] = new Result('date', 'date');
        $mapper->results[] = new Result('expenseCategory', 'expenseCategory', Result::OBJECT, $this->getExpenseCategoryMapper());
        return $mapper;
    }
}
<?php

namespace com\linways\nucleus\core\mapper;

use com\linways\base\mapper\IMapper;
use com\linways\base\mapper\Result;
use com\linways\base\mapper\ResultMap;
use Klein\Request;

class AddonServiceMapper implements IMapper
{

    private $mapper = [];

    // /Condition 1 - Presence of a static member variable
    private static $_instance = null;

    // /Condition 2 - Locked down the constructor
    private function __construct()
    {
    }

    // Prevent any oustide instantiation of this class

    // /Condition 3 - Prevent any object or instance of that class to be cloned
    private function __clone()
    {
    }

    // Prevent any copy of this object

    // /Condition 4 - Have a single globally accessible static method
    public static function getInstance()
    {
        if (!is_object(self::$_instance)) // or if( is_null(self::$_instance) ) or if( self::$_instance == null )
            self::$_instance = new self();

        return self::$_instance;
    }

    const GET_ADDONS_ENABLED = "GET_ADDONS_ENABLED";
    const GET_ALL_ADDONS = "GET_ALL_ADDONS";
    const GET_ALL_ADDON_WITH_ENABLED_FLAG = "GET_ALL_ADDON_WITH_ENABLED_FLAG";
    const GET_ENABLED_ADDON_BY_PACKAGE_NAME_AND_COLLEGE_CODE = "GET_ENABLED_ADDON_BY_PACKAGE_NAME_AND_COLLEGE_CODE";
    const GET_ENABLED_ADDON_BY_ADDONID_AND_COLLEGECODE = "GET_ENABLED_ADDON_BY_ADDONID_AND_COLLEGECODE";
    const GET_ENABLED_ADDON_WITH_ADDON_DATA_STORE_MAPPER = "GET_ENABLED_ADDON_WITH_ADDON_DATA_STORE_MAPPER";

    public function getMapper()
    {
        if (empty($this->mapper)) {
            $this->mapper[self::GET_ADDONS_ENABLED] = $this->getAddonsEnabledMapper();
            $this->mapper[self::GET_ALL_ADDONS] = $this->getAllAddonsMapper();
            $this->mapper[self::GET_ENABLED_ADDON_BY_ADDONID_AND_COLLEGECODE] = $this->getAllAddonsWithEnabledAddonIdMapper();
            $this->mapper[self::GET_ALL_ADDON_WITH_ENABLED_FLAG] = $this->getAllAddonWithEnabledFlagMapper();
            $this->mapper[self::GET_ENABLED_ADDON_BY_PACKAGE_NAME_AND_COLLEGE_CODE] = $this->getEnabledAddonByPackageNameAndCollegeCodeMapper();
            $this->mapper[self::GET_ENABLED_ADDON_WITH_ADDON_DATA_STORE_MAPPER] = $this->getEnabledAddonWithDataStoreMapper();
        }

        return $this->mapper;
    }

    /**
     * Mapping ADDON with description (id, name, description, url, package_name, scope)
     * @return ResultMap|null
     */
    private function getAddonsMapper()
    {
        $mapper = null;
        $mapper = new ResultMap("getAddonsMapper", 'com\linways\nucleus\core\dto\Addon', 'id', 'id');
        $mapper->results[] = new Result('id', 'id');
        $mapper->results[] = new Result('name', 'name');
        $mapper->results[] = new Result('description', 'description');
        $mapper->results[] = new Result('url', 'url');
        $mapper->results[] = new Result('packageName', 'package_name');
        $mapper->results[] = new Result('scope', 'scope');
        return $mapper;
    }

    /**
     * Mapping ADDON with ENABLED ADDON ID. (id, name, description, url, package_name, scope, enabledAddonId)
     * @return ResultMap|null
     */
    private function getAllAddonWithEnabledFlagMapper()
    {
        $mapper = null;
        $mapper = new ResultMap("getAllAddonWithEnabledFlagMapper", 'com\linways\nucleus\core\dto\Addon', 'id', 'id');
        $mapper->results[] = new Result('id', 'id');
        $mapper->results[] = new Result('name', 'name');
        $mapper->results[] = new Result('description', 'description');
        $mapper->results[] = new Result('url', 'url');
        $mapper->results[] = new Result('packageName', 'package_name');
        $mapper->results[] = new Result('scope', 'scope');
        $mapper->results[] = new Result('enabledAddonId', 'enabledAddonId');
        return $mapper;

    }

    /**
     * Mapping ADDON only. (addonId,addonName,url, packageName, scope)
     * @return ResultMap|null
     */
    private function getAddonMapper()
    {
        $mapper = null;

        $mapper = new ResultMap("getAddonMapper", 'com\linways\nucleus\core\dto\Addon', 'id', 'addonId');
        $mapper->results = [];
        $mapper->results[] = new Result('id', 'addonId');
        $mapper->results[] = new Result('name', 'addonName');
        $mapper->results[] = new Result('url', 'url');
        $mapper->results[] = new Result('packageName', 'packageName');
        $mapper->results[] = new Result('scope', 'scope');

        return $mapper;
    }

    /**
     * Mapping ADDONs with ATTRIBUTES (id, name, description, url, package_name, scope, getAttributeMapper)
     * @return ResultMap|null
     */
    private function getAllAddonsMapper()
    {
        $mapper = null;
        $mapper = new ResultMap("getAllAddonsMapper", 'com\linways\nucleus\core\dto\Addon', 'id', 'id');
        $mapper->results[] = new Result('id', 'id');
        $mapper->results[] = new Result('name', 'name');
        $mapper->results[] = new Result('description', 'description');
        $mapper->results[] = new Result('url', 'url');
        $mapper->results[] = new Result('packageName', 'package_name');
        $mapper->results[] = new Result('scope', 'scope');
        $mapper->results[] = new Result('attributes', 'attributes', Result::OBJECT_ARRAY, $this->getAddonAttributeMapper());
        return $mapper;
    }

    /**
     * Mapping ADDONs with ENABLED ADDON ID (id, name, description, url, package_name, scope,enabledAddonId, getAttributeMapper )
     * @return ResultMap|null
     */
    private function getAllAddonsWithEnabledAddonIdMapper()
    {
        $mapper = null;
        $mapper = new ResultMap("getAllAddonsWithEnabledAddonIdMapper", 'com\linways\nucleus\core\dto\Addon', 'id', 'id');
        $mapper->results[] = new Result('id', 'id');
        $mapper->results[] = new Result('name', 'name');
        $mapper->results[] = new Result('description', 'description');
        $mapper->results[] = new Result('url', 'url');
        $mapper->results[] = new Result('packageName', 'package_name');
        $mapper->results[] = new Result('scope', 'scope');
        $mapper->results[] = new Result('enabledAddonId', 'enabledAddonId');
        $mapper->results[] = new Result('attributes', 'attributes', Result::OBJECT_ARRAY, $this->getAttributeMapper());
        return $mapper;
    }

    /**
     * Mapping ENABLED ADDONs. (enabledAddonId, scope_id, collegeId, getAddonsMapper)
     * @return ResultMap|null
     */
    private function getAddonsEnabledMapper()
    {
        $mapper = null;

        $mapper = null;
        $mapper = new ResultMap("getAddonsEnabledMapper", 'com\linways\nucleus\core\dto\AddonEnabled', 'id', 'id');
        $mapper->results = [];
        $mapper->results[] = new Result('id', 'enabledAddonId');
        $mapper->results[] = new Result('scope_id', 'scope_id');
        $mapper->results[] = new Result('collegeId', 'collegeId');
        $mapper->results[] = new Result('addon', 'addon', Result::OBJECT, $this->getAddonsMapper());
        return $mapper;
    }

    /**
     * Mapping ENABLED ADDONs.(id,scopeId,addon, getAttributeMapper)
     * @return ResultMap|null
     */
    private function getEnabledAddonByPackageNameAndCollegeCodeMapper()
    {
        $mapper = null;

        $mapper = new ResultMap("getEnabledAddonByPackageNameAndCollegeCode", 'com\linways\nucleus\core\dto\AddonEnabled', 'id', 'id');
        $mapper->results = [];
        $mapper->results[] = new Result('id', 'id');
        $mapper->results[] = new Result('scopeId', 'scopeId');
        $mapper->results[] = new Result('addon', 'addon', Result::OBJECT, $this->getAddonMapper());
        $mapper->results[] = new Result('addonAttribute', 'addonAttribute', Result::OBJECT_ARRAY, $this->getAttributeMapper());
        return $mapper;
    }

    private function getEnabledAddonWithDataStoreMapper(){
        $mapper = null;

        $mapper = new ResultMap("getEnabledAddonByPackageNameAndCollegeCode", 'com\linways\nucleus\core\dto\AddonEnabled', 'id', 'id');
        $mapper->results = [];
        $mapper->results[] = new Result('id', 'id');
        $mapper->results[] = new Result('scopeId', 'scopeId');
        $mapper->results[] = new Result('addon', 'addon', Result::OBJECT, $this->getAddonMapper());
        $mapper->results[] = new Result('addonAttribute', 'addonAttribute', Result::OBJECT_ARRAY, $this->getAttributeMapper());
        $mapper->results[] = new Result('addonDataStore', 'addonDataStore', Result::OBJECT_ARRAY, $this->getAddonDataStoreMapper());
        return $mapper;
    }

    /**
     * Mapping ADDON ATTRIBUTE only. (attributeId, attrName, attrCode, required, data_type, attrType)
     * @return ResultMap|null
     */
    public function getAddonAttributeMapper(){
        $mapper = null;

        $mapper = new ResultMap("getAddonAttributeMapper", 'com\linways\nucleus\core\dto\AddonAttribute', 'id', 'attributeId');
        $mapper->results = [];
        $mapper->results[] = new Result('id', 'attributeId');
        $mapper->results[] = new Result('name', 'attrName');
        $mapper->results[] = new Result('code', 'attrCode');
        $mapper->results[] = new Result('required', 'required');
        $mapper->results[] = new Result('dataType', 'data_type');
        $mapper->results[] = new Result('type', 'attrType');

        return $mapper;
    }

    /**
     * mapping ADDON ATTRIBUTES.(attributeId, attrName, attrCode, required, data_type, attrType, getAttributeValuesMapper)
     * @return ResultMap|null
     */
    private function getAttributeMapper()
    {
        $mapper = null;

        $mapper = new ResultMap("getAttributeMapper", 'com\linways\nucleus\core\dto\AddonAttribute', 'id', 'attributeId');
        $mapper->results = [];
        $mapper->results[] = new Result('id', 'attributeId');
        $mapper->results[] = new Result('name', 'attrName');
        $mapper->results[] = new Result('code', 'attrCode');
        $mapper->results[] = new Result('required', 'required');
        $mapper->results[] = new Result('dataType', 'data_type');
        $mapper->results[] = new Result('type', 'attrType');
        $mapper->results[] = new Result('addonAttributeValue', 'addonAttributeValue', Result::OBJECT, $this->getAttributeValuesMapper());

        return $mapper;
    }

    /**
     * Mapping ADDON ATTRIBUTE VALUES. (attributeValueId, value)
     * @return ResultMap|null
     */
    private function getAttributeValuesMapper()
    {
        $mapper = null;

        $mapper = new ResultMap('getAttributeValuesMapper', 'com\linways\nucleus\core\dto\AddonAttributeValue', 'id', 'attributeValueId');
        $mapper->results = [];
        $mapper->results[] = new Result('id', 'attributeValueId');
        $mapper->results[] = new Result('value', 'value');

        return $mapper;
    }


    /**
     * Mapping ADDON_DATA_STORE only. ( addonDataStoreId, addonDataStoreValue, addonDataStoreKey,enabledAddonId)
     * @return ResultMap|null
     */
    private function getAddonDataStoreMapper(){
        $mapper = null;

        $mapper = new ResultMap('getAddonDataStoreMapper', 'com\linways\nucleus\core\dto\AddonDataStore', 'id', 'addonDataStoreId');
        $mapper->results = [];
        $mapper->results[] = new Result('id', 'addonDataStoreId');
        $mapper->results[] = new Result('value', 'addonDataStoreValue');
        $mapper->results[] = new Result('key', 'addonDataStoreKey');
        $mapper->results[] = new Result('enabledAddonId', 'enabledAddonId');

        return $mapper;
    }

}
<?php
namespace com\linways\nucleus\core\mapper;

use com\linways\base\mapper\IMapper;
use com\linways\base\mapper\Result;
use com\linways\base\mapper\ResultMap;

class RecurringExpenseServiceMapper implements IMapper
{

    private $mapper = [];
    private static $_instance = null;

    private function __construct()
    {}

    private function __clone()
    {}
    public static function getInstance()
    {
        if (! is_object(self::$_instance))
            self::$_instance = new self();
        return self::$_instance;
    }

    const GET_RECURRING_EXPENSE_WITH_HISTORY_MAPPER = "GET_RECURRING_EXPENSE_WITH_HISTORY_MAPPER";
    const GET_RECURRING_EXPENSE_AMOUNT_MAPPER = "GET_RECURRING_EXPENSE_AMOUNT_MAPPER";

    public function getMapper()
    {
        if (empty($this->mapper)) {
            $this->mapper[self::GET_RECURRING_EXPENSE_WITH_HISTORY_MAPPER] = $this->getRecurringExpenseWithHistoryMapper();
            $this->mapper[self::GET_RECURRING_EXPENSE_AMOUNT_MAPPER] = $this->getRecurringExpenseAmountMapper();
        }

        return $this->mapper;
    }

    private function getRecurringExpenseWithHistoryMapper(){
        $mapper = null;

        $mapper = new ResultMap("getRecurringExpenseWithHistoryMapper", 'com\linways\nucleus\core\dto\ExpenseRecurring', 'id', 'id');
        $mapper->results = [];
        $mapper->results[] = new Result('id', 'id');
        $mapper->results[] = new Result('name', 'name');
        $mapper->results[] = new Result('description', 'description');
        $mapper->results[] = new Result('expenseCategoryId', 'expense_category_id');
        $mapper->results[] = new Result('expenseRecurringAmount', 'expenseRecurringAmount', Result::OBJECT_ARRAY, $this->getRecurringExpenseAmountMapper());

        return $mapper;
    }

    private function getRecurringExpenseAmountMapper(){
        $mapper = null;

        $mapper = new ResultMap("getRecurringExpenseAmountMapper", 'com\linways\nucleus\core\dto\ExpenseRecurringAmount', 'id', 'id');
        $mapper->results = [];
        $mapper->results[] = new Result('id', 'era_id');
        $mapper->results[] = new Result('amount', 'amount');
        $mapper->results[] = new Result('fromDate', 'from_date');
        $mapper->results[] = new Result('toDate', 'to_date');
        $mapper->results[] = new Result('notes', 'notes');
        return $mapper;
    }
}
<?php 
namespace com\linways\nucleus\core\utils\user;

class UserServiceUtils{



    function getSalt($length = 16) {
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $charactersLength = strlen($characters);
    $randomString = '';
    for ($i = 0; $i < $length; $i++) {
        $randomString .= $characters[rand(0, $charactersLength - 1)];
    }
    return $randomString;
}

}<?php
/**
 * Created by PhpStorm.
 * User: rohithb
 * Date: 25/08/18
 * Time: 10:06 AM
 */

namespace com\linways\nucleus\core\utils;


class DateUtils
{

    /**
     * @param $from  from month and year :  mm-YYYY
     * @param $to    to month and year : mm-YYYY
     * @return array Array of all months in between $from and $to
     */
    public static function getMonthsBetween2Dates($from ,$to){
        $output = [];
        $from = \DateTime::createFromFormat('m-Y',$from)->format('Y-m-01');
        $to = \DateTime::createFromFormat('m-Y',$to)->format('Y-m-01');
        $endCondition = date('Y-m-01', strtotime($to . ' +1 month'));
        do{
            $month = [$from, date('Y-m-t', strtotime($from))];
            $output[] = $month;
            $from = date('Y-m-d', strtotime($from . ' + 1 month'));
        }while($from != $endCondition);

        return $output;
    }
}<?php
namespace com\linways\nucleus\core\utils;


class StringUtils {
  /**
   * Convert empty string to string 'NULL'
   * @param  string $item
   * @return string
   */
    public static function stringifyNull($item){
      return $item ? "'$item'": 'NULL';
    }

}
<?php
namespace com\linways\nucleus\core\utils;

use com\linways\core\ams\professional\exception\ProfessionalException;
use Firebase\JWT\ExpiredException;
use \Firebase\JWT\JWT;

class JWTUtils {
    public static function createToken($organizationId, $collegeId){
        require getenv('NUCLEUS_CONF');
        $accessId = bin2hex(openssl_random_pseudo_bytes(10));
        // JWT specs recommend usage of short keys.
        $jwtPayload =  array(
            "aId" => $accessId,  // aid is for access Id
            "cId" => $collegeId, // College ID,
            "oId" => $organizationId,  //OrganizationID
            "iat" => time(),
        );
        return [$accessId, JWT::encode($jwtPayload, $GLOBALS['JWT_SECRET'])];
    }

    public static function decodeToken($jwt){
        require getenv('NUCLEUS_CONF');
        return JWT::decode($jwt, $GLOBALS['JWT_SECRET'], array('HS256'));
    }


    public static function createValidityToken($key, $validityInMinutes=20){
        require getenv('NUCLEUS_CONF');
        $jwtPayload =  array(
            "key" => $key,
            "iat" => time(),
            "exp" => time() + (15 *60)
        );
        return JWT::encode($jwtPayload, $GLOBALS['GENERAL_SALT']);
    }

    public static function checkValidityToken($jwt, $key){
        require getenv('NUCLEUS_CONF');
        JWT::$leeway = 20; // Tolerance of 20 seconds
        try{
            $decoded = (array) JWT::decode($jwt, $GLOBALS['GENERAL_SALT'], array('HS256'));
            if($decoded['key'] !== $key){
                throw new ProfessionalException('ACCESS_DENIED', 'A valid token is required to access this file');
            }
        }
        catch (ExpiredException $e){
            throw new ProfessionalException('ACCESS_DENIED', 'Expired Token');
        }
        catch(\Exception $e){
            throw new ProfessionalException('ACCESS_DENIED', 'A valid token is required to access this file');
        }
    }
}<?php
namespace com\linways\base\request;

/**
 * Request object for ScheduleService::schedule
 */
class ScheduleRequest extends BaseRequest
{

  /**
   *
   * @var array Should be in following format
   *  $params = [
   *    "command" => "EXECUTE SERVICE" || "EXECUTE HOOK",
   *    "taskParams" => [] Refer https://devdocs.linways.com/books/â­�-linways-ams-developer-docs/page/task-scheduler
   *    for details about taskParams
   * ]
   */
  public $params;

  /**
   *
   * @var string (Optional) Cron job schedule string representing when the task should work
   */
  public $frequency;

  /**
   *
   * @var string  (Optional) Unique code used to identify scheduled task (Useful for named tasks)
   */
  public $code;

  /**
   *
   * @var \DateTime (Optional) To set next execution time. (For one time tasks set this and leave frequency null)
   */
  public $nextExecutionAt;

}
<?php 
namespace com\linways\nucleus\core\service;

use com\linways\nucleus\core\exception\NucleusException;
use com\linways\base\util\MakeSingletonTrait;

class ConfigurationService extends BaseService {

   use MakeSingletonTrait;

/**
 * [getConfigurationByKey method to get configuration as key-value pair]
 * @author gadheyan
 * @param  [type] $key [description]
 * @return [type]      [description]
 */
    public function getConfigurationByKey($key){
        $sql = "";
        $response = null;

        $key = $this->realEscapeString($key);

        $sql = "SELECT conf_key as `key`, conf_value as value FROM configuration WHERE key='$key'";

        try {
          $response = $this->executeQueryForObject($sql);
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }

        return $response;
    }

    public function getMultipleConfigurations($key){
        $sql = "";
        $response = new \StdClass;
        $result = null;

 
        foreach ($key as $item) {
            $item = $this->realEscapeString($item);
        }
        $key = "'".implode("', '" , $key)."'";
        // print_r($key);die();

        $sql = "SELECT conf_key as `key`, conf_value as value FROM configuration WHERE conf_key IN ($key)";

        try {
          $result = $this->executeQueryForList($sql);
          foreach ($result as $configuration) {
              $response->{$configuration->key} = $configuration->value;
          }
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }

        return $response;
    }


}<?php

namespace com\linways\nucleus\core\service;

use com\linways\nucleus\core\exception\NucleusException;
use com\linways\nucleus\core\utils\JWTUtils;
use com\linways\base\util\MakeSingletonTrait;
use Exception;

class PermissionService extends BaseService
{
    use MakeSingletonTrait;

    /**
     * return permission id by permission code
     * @param string $permissionCode
     * @return integer|NULL
     * @throws NucleusException
     */
    private function getPermissionIdByPermissionCode($permissionCode)
    {
        $id = NULL;
        $permissionCode = $this->realEscapeString($permissionCode);
        $query = "SELECT id FROM api_permission WHERE code='" . $permissionCode . "'";
        try {
            $result = $this->executeQueryForObject($query);
            if ($result)
                $id = $result->id;

        } catch (Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }

        return $id;
    }

    /**
     * return apiAccessTokenId by accessId
     * @param integer $APIAccessId
     * @return integer|NULL
     * @throws NucleusException
     */
    private function getAPIAccessTokenIdByAccessId($APIAccessId)
    {
        $id = NULL;

        $APIAccessId = $this->realEscapeString($APIAccessId);
        $query = "SELECT `id` FROM `api_access_token` WHERE `access_id`='" . $APIAccessId . "'";
        try {
            $result = $this->executeQueryForObject($query);
            if ($result)
                $id = $result->id;
        } catch (Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }

        return $id;
    }

    /**
     * Insert permission to access id
     * @param integer $APIAccessId
     * @param string $permissionCode
     * @return bool
     * @throws NucleusException
     */
    public function addPermissionToAccessId($APIAccessId, $permissionCode)
    {
        $id = null;

        $APIAccessId = $this->realEscapeString($APIAccessId);
        $permissionCode = $this->realEscapeString($permissionCode);
        $permissionId = $this->getPermissionIdByPermissionCode($permissionCode);
        $APIAccessTokenId = $this->getAPIAccessTokenIdByAccessId($APIAccessId);
        if (empty($permissionId))
            throw new NucleusException(NucleusException::INVALID_PERMISSION_ID, "Invalid permission id");
        if (empty($APIAccessTokenId))
            throw new NucleusException(NucleusException::INVALID_API_ACCESS_TOKEN_ID, "Invalid api access token id");

        $query = "INSERT INTO api_access_token_permission (api_access_token_id,api_permission_id) VALUES ($APIAccessTokenId,$permissionId)";
        try {
            $result = $this->executeQueryForObject($query, true);
            $id = $result;
        } catch (Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }

        return $id;
    }

    /**
     * checking api has permission. return api_access_token_permission id
     * @param integer $apiAccessToken
     * @param string $permissionCode
     * @return integer|null
     * @throws NucleusException
     */
    public function APIHasPermission($apiAccessToken, $permissionCode)
    {
        $id = null;
        global $COLLEGE_CODE;
        $apiAccessToken = $this->realEscapeString($apiAccessToken);
        $permissionCode = $this->realEscapeString($permissionCode);

        $apiAccessToken = JWTUtils::decodeToken($apiAccessToken);
        $college = CollegeService::getInstance()->getCollegeByCollegeId($apiAccessToken->cId);

        if($COLLEGE_CODE != $college->college_code){
            throw new NucleusException(NucleusException::INVALID_COLLEGE_CODE, "Tampered or corrupted token");
        }

        $APIAccessTokenId = $this->getAPIAccessTokenIdByAccessId($apiAccessToken->aId);
        $permissionId = $this->getPermissionIdByPermissionCode($permissionCode);

        if (empty($permissionId))
            throw new NucleusException(NucleusException::INVALID_PERMISSION_ID, "Invalid permission id");
        if (empty($APIAccessTokenId))
            throw new NucleusException(NucleusException::INVALID_API_ACCESS_TOKEN_ID, "Invalid api access token id");
        $query = "SELECT id FROM api_access_token_permission WHERE api_access_token_id=$APIAccessTokenId AND api_permission_id=$permissionId";
        try {
            $result = $this->executeQueryForObject($query);
            if ($result){
                $id = $result->id;
                return true;
            }
        } catch (Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
        return false;
    }
}<?php

namespace com\linways\nucleus\core\service;

use com\linways\nucleus\core\dto\ExpenseRecurring;
use com\linways\nucleus\core\dto\ExpenseRecurringAmount;
use com\linways\nucleus\core\exception\NucleusException;
use com\linways\nucleus\core\mapper\RecurringExpenseServiceMapper;
use com\linways\base\util\MakeSingletonTrait;

class RecurringExpenseService extends BaseService
{
    use MakeSingletonTrait;

    private function __construct(){
        $this->mapper = RecurringExpenseServiceMapper::getInstance()->getMapper();
    }

    /**
     * @param ExpenseRecurring $recurringExpense
     * @return null|int The id of the created recurring expense
     * @throws NucleusException
     */
    public function createExpense($recurringExpense)
    {
        $id = null;
        $recurringExpense = $this->realEscapeObject($recurringExpense);

        if (empty($recurringExpense->name) || empty($recurringExpense->expenseCategoryId)) {
            throw new NucleusException(NucleusException::ITEM_NAME_AND_CATEGORY_REQUIRED, "ITEM NAME AND CATEGORY REQUIRED");
        }

        $query = "INSERT INTO `expense_recurring` (`name`,`description`,`expense_category_id`, `created_by`, `created_at`, `updated_by`, `updated_at`)
        VALUES ('$recurringExpense->name', 
        '$recurringExpense->description', 
        '$recurringExpense->expenseCategoryId',
        '$recurringExpense->createdBy',
        UTC_TIMESTAMP(),
        '$recurringExpense->updatedBy',
        UTC_TIMESTAMP()
        )";
        try {
            $id = $this->executeQueryForObject($query, true);
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
        return $id;
    }

    /**
     * @param $recurringExpense
     * @return null|int id of the updated/created record
     * @throws NucleusException
     */
    public function upsertExpense($recurringExpense)
    {
        if ($recurringExpense->id) {
            $recurringExpense = $this->realEscapeObject($recurringExpense);

            $query = "UPDATE `expense_recurring` SET
          `name`='$recurringExpense->name',
          `description`='$recurringExpense->description',
          `expense_category_id`= $recurringExpense->expenseCategoryId,
          `updated_by`= '$recurringExpense->updatedBy',
          `updated_at`= UTC_TIMESTAMP() where `id` = $recurringExpense->id";
            try {
                $this->executeQueryForObject($query, true);
            } catch (\Exception $e) {
                throw new NucleusException($e->getCode(), $e->getMessage());
            }
            return $recurringExpense->id;
        } else {
            //if ID does not exists calls create method
            return $this->createExpense($recurringExpense);
        }
    }

    /**
     * @param $recurringExpenseId
     * @return bool  true fif deletion is successful
     * @throws NucleusException
     */
    public function deleteExpense($recurringExpenseId)
    {
        $recurringExpenseId = $this->realEscapeString($recurringExpenseId);
        if (empty($recurringExpenseId)) {
            throw new NucleusException(NucleusException::ID_REQUIRED, "Recurring expense id is required");
        }
        $query = "DELETE FROM `expense_recurring` where `id`=$recurringExpenseId";
        try {
            $this->executeQuery($query);
            return true;
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
    }


    public function insertRecurringAmount($expenseAmount){
        $expenseAmount = $this->realEscapeObject($expenseAmount);

        $expenseAmount->toDate = $expenseAmount->toDate ? "'$expenseAmount->toDate'": 'NULL';
        if (empty($expenseAmount->expenseRecurringId) || empty($expenseAmount->amount || empty($expenseAmount->fromDate))) {
            throw new NucleusException(NucleusException::EXPENSE_ID_AMOUNT_START_DATE_REQUIRED, "EXPENSE ID, AMOUNT AND START_DATE REQUIRED");
        }
        $query = "INSERT INTO `expense_recurring_amount` ( `expense_recurring_id`, `amount`, `from_date`, `to_date`, `notes`, `created_by`, `created_at`, `updated_by`, `updated_at`)
              VALUES (
              $expenseAmount->expenseRecurringId,
              $expenseAmount->amount, 
              '$expenseAmount->fromDate', 
              $expenseAmount->toDate, 
              '$expenseAmount->notes', 
              '$expenseAmount->createdBy',
               UTC_TIMESTAMP(),
              '$expenseAmount->updatedBy',
               UTC_TIMESTAMP())";

        try{
            $id = $this->executeQueryForObject($query, true);
            return $id;
        }catch(\Exception $e){
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
    }


    public function upsertRecurringAmount($expenseAmount)
    {
        $expenseAmount = (object)$expenseAmount;
        if ($expenseAmount->id) {
            $expenseAmount = $this->realEscapeObject($expenseAmount);
            $expenseAmount->toDate = $expenseAmount->toDate ? "'$expenseAmount->toDate'": 'NULL';

            $query = "UPDATE `expense_recurring_amount` SET
          `expense_recurring_id`='$expenseAmount->expenseRecurringId',
          `amount`='$expenseAmount->amount',
          `from_date`= '$expenseAmount->fromDate',
          `to_date`= $expenseAmount->toDate,
          `updated_by`= '$expenseAmount->updatedBy',
          `updated_at`= UTC_TIMESTAMP() where `id` = $expenseAmount->id";
            try {
                $this->executeQueryForObject($query, true);
            } catch (\Exception $e) {
                throw new NucleusException($e->getCode(), $e->getMessage());
            }
            return $expenseAmount->id;
        } else {
            //if ID does not exists calls create method
            return $this->insertRecurringAmount($expenseAmount);
        }
    }

    public function deleteRemainingRecurringExpenseAmounts($expenseAmounts, $recurringExpenseId){
        $recurringExpenseId = $this->realEscapeObject($recurringExpenseId);
        $expenseAmountIds = array_column($expenseAmounts, 'id');
        $expenseAmountIds = $this->realEscapeObject($expenseAmountIds);
        if(!empty($expenseAmountIds)){
            $expenseAmountIds = implode(", ", $expenseAmountIds);
            $query = "delete from expense_recurring_amount where id not in ($expenseAmountIds) and expense_recurring_id=$recurringExpenseId";
            $this->executeQuery($query);
            return true;
        }
    }

    public function getRecurringExpenseWithHistory($expenseId){
        $expenseId = $this->realEscapeString($expenseId);

        $query = "select 
              er.id, er.name, er.description, 
              er.expense_category_id, era.id as era_id, era.amount, 
              DATE_FORMAT(date(era.from_date),'%d-%m-%Y') as from_date, DATE_FORMAT(date(era.to_date),'%d-%m-%Y') as to_date, era.notes 
              from expense_recurring er inner join expense_recurring_amount era 
              on era.expense_recurring_id = er.id ";
        if($expenseId)
            $query .= "WHERE er.id = $expenseId ";
        $query .= "ORDER BY era.from_date DESC";

        $recExpenses = $this->executeQueryForList($query, $this->mapper[RecurringExpenseServiceMapper::GET_RECURRING_EXPENSE_WITH_HISTORY_MAPPER] );
        foreach ($recExpenses as $expense){
            $expense->currentAmount = $this->getCurrentAmountForRecurringExpense($expense->id);
            if(!$expense->currentAmount) $expense->currentAmount=[];
        }
        if($expenseId)
            return $recExpenses[0];
        return $recExpenses;
    }

    /**
     * return current amount for a given recurring expense
     * @param $expenseId
     * @return Object|ExpenseRecurringAmount
     * @throws NucleusException
     * @throws \Exception
     */
    public function getCurrentAmountForRecurringExpense($expenseId){
        $expenseId = $this->realEscapeString($expenseId);
        if(empty($expenseId))
            throw new NucleusException(NucleusException::ID_REQUIRED, "Recurring expense id is required");

        $query = "select 
            id, amount, from_date as fromDate, to_date as toDate, notes 
            from expense_recurring_amount 
            where DATE(NOW())>= from_date
            and (DATE(NOW())<= to_date or to_date is null) and expense_recurring_id = $expenseId";
        return $this->executeQueryForObject($query);
    }
}
<?php

namespace com\linways\nucleus\core\service;

use com\linways\nucleus\core\dto\Addon;
use com\linways\nucleus\core\dto\AddonEnabled;
use com\linways\nucleus\core\exception\NucleusException;
use com\linways\nucleus\core\mapper\AddonServiceMapper;
use com\linways\base\util\MakeSingletonTrait;

class AddonService extends BaseService
{
    use MakeSingletonTrait;
    private $mapper = [];

    private function __construct()
    {
        $this->mapper = AddonServiceMapper::getInstance()->getMapper();
    }

    /**
     * creating addon entry to the database
     *
     * @param Addon $addon
     * @throws NucleusException
     * @return array $data
     */
    public function createAddon($addon)
    {
        $data = [];

        $addon->name = $this->realEscapeString($addon->name);
        $addon->description = $this->realEscapeString($addon->description);
        $addon->url = $this->realEscapeString($addon->url);
        $addon->packageName = $this->realEscapeString($addon->packageName);
        $addon->scope = $this->realEscapeString($addon->scope);

        $query = "INSERT INTO `addon` (
                    `name`,
                    `description`,
                    `url`,
                    `package_name`,
                    `scope`,
                    `updated_date`,
                    `created_date`) VALUES (' $addon->name ',
                    '$addon->description',
                    '$addon->url',
                    '$addon->packageName',
                    '$addon->scope',
                    utc_timestamp(),
                    utc_timestamp())";

        $result = $this->executeQueryForObject($query, true);
        $data['addonId'] = $result;
        $res = AddonAttributeService::getInstance()->createAddonAttribute($addon->addonAttribute, $result);
        $data['attributesId'] = $res;

        return $data;
    }

    public function updateAddon($addon)
    {
        $status = false;

        // TODO

        return status;
    }

    public function deleteAddon($addonId)
    {
        $status = false;

        // TODO

        return $status;
    }

    /**
     * return college id of corresponding college code
     * @param string $collegeCode
     * @throws \Exception
     * @return integer|NULL
     */
    private function getCollegeIdByCollegeCode($collegeCode)
    {
        $id = NULL;
        if (empty($collegeCode))
            return $id;
        $query = "SELECT id FROM `colleges` WHERE `college_code`='$collegeCode'";
        $result = $this->executeQueryForObject($query);
        if ($result)
            $id = $result->id;

        return $id;
    }

    /**
     * Activating addon by addon id and college code
     * @param AddonEnabled $addonEnabled
     * @param string $collegeCode
     * @throws NucleusException
     * @return array
     */
    public function activateAddon($addonEnabled, $collegeCode)

    {
        $enabledAddon = [];
        $collegeId = 0;

        $collegeCode = $this->realEscapeString($collegeCode);
        $addonEnabled->scopeId = $this->realEscapeString($addonEnabled->scopeId);
        $addonEnabled->addonId = $this->realEscapeString($addonEnabled->addonId);

        if (empty($addonEnabled->scopeId))
            throw new NucleusException(NucleusException::INVALID_SCOPE_ID, "Invalid/empty scope id");

        if (empty($addonEnabled->addonId))
            throw new NucleusException(NucleusException::INVALID_ADDON_ID, "Invalid/empty addon id");

        if (empty($collegeCode))
//            throw new NucleusException(NucleusException::INVALID_COLLEGE_CODE, "Invalid/empty college code");
            throw new \InvalidArgumentException("TEST MESSAGE");
        $collegeId = $this->getCollegeIdByCollegeCode($collegeCode);

        if (empty($collegeId))
            throw new NucleusException(NucleusException::INVALID_COLLEGE_CODE, "Invalid/empty college code");

        $query = "INSERT INTO `addon_enabled` (`college_id`,
                `addon_id`,
                `scope_id`,
                `created_date`,
                `updated_date`) VALUES ('$collegeId',
                '$addonEnabled->addonId',
                '$addonEnabled->scopeId',
                utc_timestamp(),
                utc_timestamp())";
        try {

            $res = $this->executeQueryForObject($query, true);
            $enabledAddon['id'] = $res;
            $addonAttributes = $addonEnabled->addonAttributes;
            $result = AddonAttributeService::getInstance()->createAddonAttributeValue($addonAttributes, $res);
            $enabledAddon['attributesValuesId'] = $result;
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }


        return $enabledAddon;
    }

    public function updateCredentials($updateDetails)
    {
        $status = false;

        // TODO

        return $status;
    }

    /**
     * Disable addon by enabled addon id
     * @param $enabledAddonId
     * @return bool
     * @throws \Exception
     */
    public function disableAddon($enabledAddonId)
    {
        $status = false;

        $enabledAddonId = $this->realEscapeString($enabledAddonId);
        $query = "DELETE FROM `addon_enabled` WHERE `id` ='$enabledAddonId' ";

        $result = AddonAttributeService::getInstance()->deleteAddonAttributeValues($enabledAddonId);
        $res = $this->executeQueryForObject($query, true);
        $status = true;
        return $status;
    }

    /**
     * Method for getting enabled addons of a college
     *
     * @param string $collegeCode
     * @param string $scopeName
     * @param int $scopeId
     * @throws \Exception
     * @return [Addon]
     */
    public function getAddonsEnabled($collegeCode, $scopeName, $scopeId = NULL)
    {
        $addons = [];

        $collegeCode = $this->realEscapeString($collegeCode);
        $scopeName = $this->realEscapeString($scopeName);
        $scopeId = $this->realEscapeString($scopeId);

        $query = "SELECT a.*, ae.id AS enabledAddonId, ae.scope_id ,ae.college_id AS collegeId FROM
                 addon_enabled  ae INNER JOIN addon a ON ae.addon_id=a.id
                 INNER JOIN colleges c ON c.id=ae.college_id WHERE a.id>0";

        if (empty($collegeCode)) {
            throw new NucleusException(NucleusException::INVALID_COLLEGE_CODE, "Invalid college code");
        } else {
            $query .= " AND c.college_code='$collegeCode' ";
        }
        if (!empty($scopeName)) {
            $query .= " AND a.scope='$scopeName' ";
        }
        if (!empty($scopeId)) {
            $query .= " AND ae.scope_id='$scopeId' ";
        }
        try {
            $addons = $this->executeQueryForList($query, $this->mapper[AddonServiceMapper::GET_ADDONS_ENABLED]);
        } catch (\Exception $e) {

            throw new NucleusException($e->getCode(), $e->getMessage());
        }

        return $addons;
    }

    /**\
     * returns all addons
     * @throws NucleusException
     * @return [Addon]
     */
    public function getAllAddons()
    {
        $addons = [];

        $query = "SELECT a.*, aa.id AS attributeId, aa.type AS attrType, aa.name AS attrName,
                  aa.code AS attrCode, aa.required , aa.data_type
                  FROM addon a LEFT JOIN addon_attr aa ON aa.addon_id=a.id WHERE a.id>0";

        try {
            $addons = $this->executeQueryForList($query, $this->mapper[AddonServiceMapper::GET_ALL_ADDONS]);
        } catch (\Exception $e) {

            throw new NucleusException($e->getCode(), $e->getMessage());
        }

        return $addons;
    }

    /**
     * returns a particular addon by addon id and type.
     * @param integer $addonId
     * @param string $attrType (optional)
     * @return Addon
     * @throws NucleusException
     */
    public function getAddonByIdAndAttributeType($addonId, $attrType = NULL)
    {
        $addons = [];

        $addonId = $this->realEscapeString($addonId);
        $attrType = $this->realEscapeString($attrType);

        $query = "SELECT a.*, aa.id AS attributeId, aa.type AS attrType, aa.name AS attrName,
                  aa.code AS attrCode, aa.required, aa.data_type
                  FROM addon a LEFT JOIN addon_attr aa ON aa.addon_id=a.id WHERE a.id>0";
        if (empty($addonId))
            throw new NucleusException(NucleusException::INVALID_ADDON_ID, "Invalid addon id");
        else
            $query .= " AND a.id='$addonId'";

        if ($attrType)
            $query .= " AND (aa.type='$attrType' OR aa.type is null)";


        try {
            $addons = $this->executeQueryForList($query, $this->mapper[AddonServiceMapper::GET_ALL_ADDONS]);
        } catch (\Exception $e) {

            throw new NucleusException($e->getCode(), $e->getMessage());
        }

        return $addons;
    }

    public function getEnabledAddonByAddonIdAndCollegeCode($addonId,$collegeCode,$attrType=NULL){
        $addons=[];

        $addonId = $this->realEscapeString($addonId);
        $attrType = $this->realEscapeString($attrType);
        $collegeCode = $this->realEscapeString($collegeCode);

        $collegeId = $this->getCollegeIdByCollegeCode($collegeCode);

        if(empty($collegeId))
            throw new NucleusException(NucleusException::INVALID_COLLEGE_CODE,"Invalid college code");

        $query = "SELECT a.*, aa.id AS attributeId, aa.type AS attrType, aa.name AS attrName, aa.code AS attrCode,
                  aa.required AS required, ae.id as enabledAddonId, aa.data_type, aav.id as attributeValueId ,aav.value FROM addon a
                  LEFT JOIN addon_enabled ae ON ae.addon_id = a.id LEFT JOIN addon_attr aa ON aa.addon_id=a.id
                  LEFT JOIN addon_attr_value aav ON aav.addon_attr_id = aa.id WHERE a.id>0 AND (aav.enabled_addon_id = ae.id OR aav.enabled_addon_id is null)";
        if($addonId)
            $query.=" AND ae.addon_id = '$addonId'";
        else
            throw new NucleusException(NucleusException::INVALID_ADDON_ID,"Invalid Addon Id");

        if($collegeId)
            $query.=" AND ae.college_id = '$collegeId'";

        if($attrType)
            $query .= " AND (aa.type='$attrType' OR aa.type is null)";
        try{
            $addons = $this->executeQueryForList($query, $this->mapper[AddonServiceMapper::GET_ENABLED_ADDON_BY_ADDONID_AND_COLLEGECODE]);
        }catch (\Exception $e){
            throw new NucleusException($e->getCode(),$e->getMessage());
        }
        return $addons;
    }

    /**
     * Returns all available addons with enabled addon id ,if addon is enabled for the given college
     * @param string $collegeCode
     * @return [Addon]
     * @throws NucleusException
     */
    public function getAllAddonWithEnabledFlag($collegeCode)
    {

        $addons = [];

        $collegeCode = $this->realEscapeString($collegeCode);

        $collegeId = $this->getCollegeIdByCollegeCode($collegeCode);
        if (empty($collegeId)) {
            throw new NucleusException(NucleusException::INVALID_COLLEGE_CODE, "Invalid college code");
        }
//        (CASE WHEN ae.addon_id IS NULL THEN 0 ELSE 1 END) AS isEnabled

        $query = "SELECT a.*, ae.id as enabledAddonId
                  FROM addon  a LEFT JOIN addon_enabled ae ON ae.addon_id = a.id AND
                  ae.college_id = '$collegeId'";

        try {
            $addons = $this->executeQueryForList($query, $this->mapper[AddonServiceMapper::GET_ALL_ADDON_WITH_ENABLED_FLAG]);
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }

        return $addons;
    }

    /**
     * return enabled addon by package name and college code
     * @param string $packageName
     * @param string $collegeCode
     * @return [AddonEnabled]
     * @throws NucleusException
     */
    public function getEnabledAddonByPackageNameAndCollegeCode($packageName, $collegeCode)
    {
        $addon = NULL;

        $collegeCode = $this->realEscapeString($collegeCode);
        $packageName = $this->realEscapeString($packageName);

        $collegeId = $this->getCollegeIdByCollegeCode($collegeCode);
        if (empty($collegeId)) {
            throw new NucleusException(NucleusException::INVALID_COLLEGE_CODE, "Invalid college code");
        }

        $query = "SELECT ae.id, ae.scope_id as scopeId, a.id as addonId, a.name as addonName, a.url,
                  a.scope , a.package_name as packageName ,aa.type as attrType, aa.id as attributeId,
                  aa.name as attrName, aa.code as attrCode, aa.required, aa.data_type, aav.id as attributeValueId,
                  aav.value FROM addon_enabled ae INNER JOIN addon a ON ae.addon_id=a.id
                  LEFT JOIN addon_attr aa ON aa.addon_id=ae.addon_id LEFT JOIN addon_attr_value
                  aav ON aav.addon_attr_id = aa.id WHERE a.package_name='$packageName' AND ae.college_id='$collegeId' AND (aav.enabled_addon_id = ae.id OR aav.enabled_addon_id is null)";
        try {
            $addon = $this->executeQueryForList($query, $this->mapper[AddonServiceMapper::GET_ENABLED_ADDON_BY_PACKAGE_NAME_AND_COLLEGE_CODE]);

        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }

        return $addon;
    }

    public function getEnabledAddonWithAddonDataStoreByPackageNameAndCollegeCode($packageName,$collegeCode){
        $addon = NULL;

        $collegeCode = $this->realEscapeString($collegeCode);
        $packageName = $this->realEscapeString($packageName);

        $collegeId = $this->getCollegeIdByCollegeCode($collegeCode);
        if (empty($collegeId)) {
            throw new NucleusException(NucleusException::INVALID_COLLEGE_CODE, "Invalid college code");
        }

        $query = "SELECT ae.id, ae.scope_id as scopeId, a.id as addonId, a.name as addonName, a.url,
                  a.scope ,a.package_name as packageName ,aa.type as attrType, aa.id as attributeId,
                  aa.name as attrName, aa.code as attrCode, aa.required, aa.data_type,
                  aav.id as attributeValueId, aav.value, ads.id as addonDataStoreId,
                  ads.enabled_addon_id as enabledAddonId, ads.value as addonDataStoreValue,
                  ads.key as addonDataStoreKey FROM addon_enabled ae INNER JOIN addon a
                  ON ae.addon_id=a.id LEFT JOIN addon_attr aa ON aa.addon_id=ae.addon_id
                  LEFT JOIN addon_attr_value aav ON aav.addon_attr_id = aa.id LEFT JOIN addon_data_store ads
                  ON ads.enabled_addon_id = ae.id WHERE a.package_name='$packageName' AND
                  ae.college_id='$collegeId' AND (aav.enabled_addon_id = ae.id OR aav.enabled_addon_id is null)";
        try {
            $addon = $this->executeQueryForList($query, $this->mapper[AddonServiceMapper::GET_ENABLED_ADDON_WITH_ADDON_DATA_STORE_MAPPER]);

        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }

        return $addon;
    }

}
<?php 
namespace com\linways\nucleus\core\service\user;


use com\linways\nucleus\core\dto\user\User;
use com\linways\nucleus\core\utils\user\UserServiceUtils;
use com\linways\nucleus\core\service\BaseService;
use com\linways\nucleus\core\exception\NucleusException;

class UserService extends BaseService{

    private static $_instance = null;

    // /Condition 2 - Locked down the constructor
    private function __construct()
    {}

    // Prevent any oustide instantiation of this class
    
    // /Condition 3 - Prevent any object or instance of that class to be cloned
    private function __clone()
    {}

    // Prevent any copy of this object
    
    // /Condition 4 - Have a single globally accessible static method
    public static function getInstance()
    {
        if (! is_object(self::$_instance))
            // or if( is_null(self::$_instance) ) or if( self::$_instance == null )
            self::$_instance = new self();
        
        return self::$_instance;
    }


/**
 * [createNewUser description]
 * @param  User   $user [description]
 * @return [type]       [description]
 * @author gadheyan
 */
    public function createNewUser(User $user){
        $sql = "";

        $user->salt = UserServiceUtils::getSalt();
        $user->password = md5($user->password.$user->salt);


        $user = $this->realEscapeObject($user);

        $sql = "INSERT INTO `nucleus_user` (`name`, `email`, `username`, `password`, `salt`, `created_by`, `created_date`, `updated_by`, `updated_date`) VALUES ('$user->name', '$user->email', '$user->username', '$user->password', '$user->salt', '$user->createdBy', UTC_TIMESTAMP(), '$user->updatedBy', UTC_TIMESTAMP());";

        try {
            $index = $this->executeQueryForObject($sql, true);
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
        return $index;
    }

    public function changePassword($userId, $newPassword){
        $userId= $this->realEscapeString($userId);
        $newPassword = $this->realEscapeString($newPassword);
        $user = $this->getUserById($userId);
        $newPassword = md5($newPassword.$user->salt);
        $query = "update nucleus_user set password ='$newPassword' where id=$userId";
        return $this->executeQueryForObject($query, true);
    }

    public function checkPassword($userId, $currentPassword){
        $userId= $this->realEscapeString($userId);
        $currentPassword = $this->realEscapeString($currentPassword);
        $user = $this->getUserById($userId);
        $calculatedCurrentPassword = md5($currentPassword.$user->salt);
        if( $calculatedCurrentPassword !== $user->password ){
            throw new NucleusException('WRONG_CURRENT_PASSWORD', 'Wrong Current Password');
        }else{
            return true;
        }
    }

    public  function getUserById($userId){
        $userId = $this->realEscapeString($userId);
        $query = "select * from nucleus_user where id = $userId";
        return $this->executeQueryForObject($query);
    }
/**
 * [login description]
 * @param  User   $user [description]
 * @return [type]       [description]
 * @author gadheyan
 */
    public function login(User $user){
         $sql = "";
         $result = null;
         $user = $this->realEscapeObject($user);
         $sql = "SELECT id, name, email, username FROM nucleus_user WHERE (email = '$user->email' OR username = '$user->username') AND password = md5(concat('$user->password', salt));";
        try {
            $result = $this->executeQueryForObject($sql);
                    $_SESSION['userId'] = '1';
        // $_SESSION['userId'] = $result['id'];
        $_SESSION['email'] = $result->email;
        $_SESSION['name'] = $result->name;
        $_SESSION['username'] = $result->username;
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
        return $result;
    }


/**
 * [getAllPermissions description]
 * @param  [type] $userId [description]
 * @return [type]         [description]
 * @author gadheyan
 */
    public function getAllPermissions($userId){
        $sql = "";
        $permissions = [];
        $response = [];

        $userId = $this->realEscapeString($userId);

        $sql = "SELECT 
                    pr.name as name
                FROM
                    nucleus_user nu
                        INNER JOIN
                    user_permission up ON nu.id = up.nucleus_user_id
                        AND nu.id = '$userId'
                        INNER JOIN
                    permission pr ON pr.id = up.permission_id 
                UNION 
                SELECT 
                    pr.name
                FROM
                    nucleus_user nu
                        INNER JOIN
                    user_group ug ON nu.id = ug.nucleus_user_id
                        AND nu.id = '$userId'
                        INNER JOIN
                    group_permission gp ON gp.group_id = ug.group_id
                        INNER JOIN
                    permission pr ON pr.id = gp.permission_id;";


        try {
           $permissions = $this->executeQueryForList($sql); 
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
        foreach ($permissions as $permission) {
            $response[] = $permission->name;
        }
        return $response;

    }


    public function userHasPermission($userId, $permissions){
        
        $userPermissions = $this->getAllPermissions($userId);
        foreach ($permissions as $permission) {
            if(!in_array($permission, $userPermissions)){
                return false;
            }
        }
        return true;

    }




}<?php

namespace com\linways\nucleus\core\service;

use com\linways\nucleus\core\dto\ExpenseCategory;
use com\linways\nucleus\core\exception\NucleusException;
use com\linways\nucleus\core\mapper\ExpenseServiceMapper;
use com\linways\base\util\MakeSingletonTrait;
use com\linways\nucleus\core\utils\StringUtils;
use Phinx\Util\Util;

class ExpenseCategoryService extends BaseService
{

    use MakeSingletonTrait;
    private function __construct()
    {
        $this->mapper = ExpenseServiceMapper::getInstance()->getMapper();
    }

    /**
     * returns all expenses in the database
     * @return array array of client object
     * @throws NucleusException
     */
    public function getAllExpenseCategories()
    {
        $expenseCategories = [];
        $query = "SELECT * from `expense_category`";
        try {
            $expenseCategories = $this->executeQueryForList($query, $this->mapper[ExpenseServiceMapper::GET_EXPENSE_CATEGORY_MAPPER]);
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
        return $expenseCategories;
    }

    /**
     * @param ExpenseCategory $expenseCategory
     * @return null|\stdClass
     * @throws NucleusException
     */
    public function createExpenseCategory($expenseCategory)
    {
        $id = null;
        $expenseCategory = $this->realEscapeObject($expenseCategory);

        /**
         * Remove below code after include user management
         */
        $expenseCategory->createdBy = $expenseCategory->createdBy ? $expenseCategory->createdBy : 0;
        $expenseCategory->updatedBy = $expenseCategory->updatedBy ? $expenseCategory->updatedBy : 0;

        if (empty($expenseCategory->name))
            throw new NucleusException(NucleusException::INVALID_EXPENSE_CATEGORY_NAME, "Category name is required");
        $query = "INSERT INTO `expense_category`
                  (`name`, `created_by`, `created_at`, `updated_by`, `updated_at`) 
                  VALUES ('" . $expenseCategory->name . "',$expenseCategory->createdBy,UTC_TIMESTAMP(),
                  $expenseCategory->updatedBy,UTC_TIMESTAMP()) ";
        try {
            $id = $this->executeQueryForObject($query, true);

        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }

        return $id;
    }


}
<?php
namespace com\linways\nucleus\core\service;

use com\linways\nucleus\core\service\ConfigurationService;
use com\linways\nucleus\core\exception\NucleusException;
use com\linways\base\util\MakeSingletonTrait;
use \Firebase\JWT\JWT;
use com\linways\nucleus\core\dto\SingleSignonLog;

class SingleSignonService extends BaseService {
    use MakeSingletonTrait;

    const USER_EMAIL_FOR_SINGLE_SIGN_ON_AS_ADMIN = "USER_EMAIL_FOR_SINGLE_SIGN_ON_AS_ADMIN";
    const SHARED_KEY_FOR_SINGLE_SIGN_ON = "SHARED_KEY_FOR_SINGLE_SIGN_ON";//stored in configuration table in nucleus database.("vkn wrote pitamahan")

    /**
     * [generateToken generate a jwt for authentication.]
     * @return [string] [json web token]
     */
    public function generateToken(){
        $key = "";
        $token = "";
        $email = "";
        $credentials = null;

        $credentials = ConfigurationService::getInstance()->getMultipleConfigurations(array(self::USER_EMAIL_FOR_SINGLE_SIGN_ON_AS_ADMIN, self::SHARED_KEY_FOR_SINGLE_SIGN_ON));
        $token = array(
        "email" => $credentials->{ self::USER_EMAIL_FOR_SINGLE_SIGN_ON_AS_ADMIN }, // email address of logged-in user
        "iss" => "nucleus.linways.com",
        "exp" => time() + (3 *60), // expires 3 minutes from now
        "iat" => time()
        );

        return JWT::encode( $token, $credentials->{self::SHARED_KEY_FOR_SINGLE_SIGN_ON} );
    }


/**
 * [validateToken description]
 * @author gadheyan
 * @param  [string] $token [description]
 * @return [array]        [description]
 */
    public function validateToken($token){
        $credentials = null;

        $credentials = ConfigurationService::getInstance()->getMultipleConfigurations(array(self::SHARED_KEY_FOR_SINGLE_SIGN_ON));

        return (array) JWT::decode($token, $credentials->{self::SHARED_KEY_FOR_SINGLE_SIGN_ON}, array('HS256'));

    }


    public function logSingleSignonDetails(SingleSignonLog $singleSignonLog){
        $sql = "";
        $response = NULL;
        $singleSignonLog = $this->realEscapeObject($singleSignonLog);

        $sql = "INSERT INTO `single_signon_log` (`user_id`, `college_code`, `logged_in_as`, `login_time`) VALUES ('$singleSignonLog->userId', '$singleSignonLog->collegeCode', '$singleSignonLog->loggedInAs', UTC_TIMESTAMP());";
        try {
            $response = $this->executeQueryForObject($sql, true);
        } catch (\Exception $e) {
             throw new NucleusException($e->getCode(), $e->getMessage());
        }
        return $response;
    }
}
<?php

namespace com\linways\nucleus\core\service;


use com\linways\base\util\MakeSingletonTrait;
use com\linways\nucleus\core\mapper\ProductVariantServiceMapper;

class ProductVariantService extends BaseService
{
    use MakeSingletonTrait;
    private function __construct()
    {
        $this->mapper = ProductVariantServiceMapper::getInstance()->getMapper();
    }

    /**
     * Returns all active product packages
     * @return \com\linways\nucleus\core\dto\ProductVariant[]
     * @throws \Exception
     */
    public function getAllActiveProductVariant(){
        $query = "SELECT id, code, name, description, created_at, updated_at from product_variant where is_active=1";
        return $this->executeQueryForList($query, $this->mapper[ProductVariantServiceMapper::PRODUCT_VARIANT]);
    }

}<?php
namespace com\linways\nucleus\core\service;

use com\linways\nucleus\core\dto\AddonAttribute;
use com\linways\nucleus\core\exception\NucleusException;
use com\linways\base\util\MakeSingletonTrait;

class AddonAttributeService extends BaseService
{
   use MakeSingletonTrait;

    /**
     *
     * @param AddonAttribute $addonAttributes
     * @param integer $addonId
     * @throws NucleusException
     * @return [integer]|NULL
     */
    public function createAddonAttribute($addonAttributes, $addonId)
    {
        $id = [];
        
        $addonId = $this->realEscapeString($addonId);
        
        try {
            foreach ($addonAttributes as $addonAttribute) {
                $addonAttribute->type = $this->realEscapeString($addonAttribute->type);
                $addonAttribute->name = $this->realEscapeString($addonAttribute->name);
                $addonAttribute->code = $this->realEscapeString($addonAttribute->code);
                $addonAttribute->required = $this->realEscapeString($addonAttribute->required);
                $addonAttribute->dataType = $this->realEscapeString($addonAttribute->dataType);
                
                if (empty($addonAttribute->type) || empty($addonAttribute->type) || empty($addonAttribute->name) || empty($addonAttribute->code))
                    throw new NucleusException("Invalid or empty fields");
                
                $addonAttribute->dataType = empty($addonAttribute->dataType) ? NULL : $addonAttribute->dataType;
                $addonAttribute->required = empty($addonAttribute->required) ? NULL : $addonAttribute->required;
                
                $query = "INSERT INTO `addon_attr` (`type`,
                `name`,
                `code`,
                `addon_id`,
                `required`,
                `data_type`,
                `created_date`,
                `updated_date`) VALUES ('$addonAttribute->type',
                '$addonAttribute->name',
                '$addonAttribute->code',
                '$addonId',
                '$addonAttribute->required',
                '$addonAttribute->dataType',
                utc_timestamp(),
                utc_timestamp())";
                
                $result = $this->executeQueryForObject($query, true);
                $id[] = $result;
            }
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode, $e->getMessage);
        }
        
        return $id;
    }

    /**
     *
     * @param AddonAttribute $addonAttribute
     * @param integer $addonId
     * @return boolean
     */
    public function updateAddonAttribute($addonAttribute, $addonId)
    {
        $status = false;
        
        // TODO
        
        return $status;
    }

    /**
     *
     * @param integer $addonAttributeId
     * @return boolean
     */
    public function deleteAddonAttribute($addonAttributeId)
    {
        $status = false;
        
        // TODO
        
        return $status;
    }

    /**
     *
     * @param integer $addonAttributeId
     * @param integer $addonId
     * @param string $code
     * @param string $type
     * @param string $dataType
     * @return array
     */
    public function getAddonAttributes($addonAttributeId, $addonId, $code = NULL, $type = NULL, $dataType = NULL)
    {
        $addonAttributes = [];
        
        return $addonAttributes = [];
    }

    /**
     *
     * @param [AddonAttributeValue] $addonAttrValue
     * @param integer $addonEnbledId
     * @throws NucleusException
     * @return boolean
     */
    public function createAddonAttributeValue($addonAttrValue, $addonEnbledId)
    {
        $id = [];
        $sql = NULL;
        $addonEnbledId = $this->realEscapeString($addonEnbledId);
        $query = "INSERT INTO `addon_attr_value` (`enabled_addon_id`,`addon_attr_id`,`value`,`created_date`,`updated_date`) VALUES('$addonEnbledId',";
        
        try {
            foreach ($addonAttrValue as $attributeValue) {
                $attributeValue->value = $this->realEscapeString($attributeValue->value);
                $attributeValue->addonAttributeId = $this->realEscapeString($attributeValue->addonAttributeId);
                $sql = $query;
                $sql = $sql . "'$attributeValue->addonAttributeId','$attributeValue->value',utc_timestamp(),utc_timestamp())";
                $result = $this->executeQueryForObject($sql, true);
                $id[] = $result;
            }
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
        return $id;
    }

    /**
     *
     * @param [AddonAttributeValue] $addonAttrValue
     * @throws NucleusException
     * @return boolean
     */
    public function updateAddonAttributeValue($addonAttributeValues,$enabledAddonid)
    {
        $status = false;
        $enabledAddonid = $this->realEscapeString($enabledAddonid);
        $query = "UPDATE `addon_attr_value` SET ";
        $sql = NULL;
        
        try {
            foreach ($addonAttributeValues as $attributeValue) {
                $attributeValue->value = $this->realEscapeString($attributeValue->value);
                $attributeValue->id = $this->realEscapeString($attributeValue->id);
                $sql = $query;
                $sql = $sql . "`value`='$attributeValue->value',
                            `updated_date`=utc_timestamp()
                            WHERE `id`='$attributeValue->id' AND enabled_addon_id = '$enabledAddonid'";
                $result = $this->executeQueryForObject($sql, true);
            }
            $status = true;
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
        return $status;
    }

    /**
     *
     * @param integer $enabledAddonId
     * @throws NucleusException
     * @return boolean
     */
    public function deleteAddonAttributeValues($enabledAddonId)
    {
        $status = false;
        $enabledAddonId = $this->realEscapeString($enabledAddonId);
        
        $query = "DELETE FROM `addon_attr_value` WHERE `enabled_addon_id`='$enabledAddonId'";
        try {
            $result = $this->executeQueryForObject($query, true);
            $status = true;
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode, $e->getMessage);
        }
        return $status;
    }

    /**
     * return addon attribute id by addon attribute code and addon id
     *
     * @param string $code
     * @param integer $addonId
     * @return integer
     * @throws NucleusException
     */
    public function getAttributeIdByCodeAndAddonId($code, $addonId)
    {
        $id = NULL;
        
        $query = "SELECT id FROM `addon_attr` WHERE  `id`>0";
        if (empty($code))
            throw new NucleusException(NucleusException::INVALID_ATTRIBUTE_CODE, "Invalid attribte code");
        else
            $query .= " AND `code`='$code'";
        
        if (empty($addonId))
            throw new NucleusException(NucleusException::INVALID_ADDON_ID, "Invalid Addon Id");
        else
            $query .= " AND `addon_id`='$addonId'";
        
        try {
            $result = $this->executeQueryForObject($query);
            $id = $result->id;
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
        
        return $id;
    }
}

<?php
namespace com\linways\nucleus\core\service;


use com\linways\nucleus\core\exception\NucleusException;
use com\linways\base\util\MakeSingletonTrait;

class PermissionToggleService extends BaseService
{
    use MakeSingletonTrait;

    /**
     * Checks whether a feature is enabled for a particular productVariant
     * @param $productVariant - Product variant code. (Eg: LITE, PROFESSIONAL etc)
     * @param $feature = Code of the  permission toggle
     * @return bool true if feature is enabled else false;
     * @throws NucleusException
     * @throws \Exception
     */
    public function isFeatureEnabledForProductVariant($productVariant, $feature){
        $productVariant = $this->realEscapeString($productVariant);
        $feature = $this->realEscapeString($feature);
        if(empty($productVariant) || empty($feature)){
            throw new NucleusException(NucleusException::PRODUCT_VARIANT_AND_FEATURE_CODE_REQUIRED, "PRODUCT_VARIANT_AND_FEATURE_CODE_REQUIRED");
        }
        $query = "select exists( 
              SELECT pvpt.id from product_variant_permission_toggle pvpt 
              inner join product_variant pv on pvpt.product_variant_id = pv.id 
              where pv.code='$productVariant' and pvpt.permission_toggle_code='$feature') as isEnabled";
        return $this->executeQueryForObject($query)->isEnabled? true: false;
    }

    /**
     * Returns an array of already migrated toggles.
     * @return int[]
     * @throws \Exception
     */
    public function getAllmigratedToggleVersions(){
        $query = "select version from permission_toggle_migrations";
        $results = $this->executeQueryForList($query);
        return array_map(function($o){ return $o->version;}, $results);
    }

    /**
     * @param \com\linways\base\dto\Toggle $toggle
     * @return object  $toggle with id
     * @throws \Exception
     */
    public function createPermissionToggle($toggle){
        $toggle = $this->realEscapeObject($toggle);
        $query = "INSERT INTO `permission_toggle` (`code`, `name`, `parent_toggle_code`, `description`, `is_active`, `created_at`, `updated_at`)
          VALUES ( '$toggle->code', '$toggle->name', '$toggle->parentToggleCode', '$toggle->description', TRUE , NOW(), NOW());";
        $toggle->id = $this->executeQueryForObject($query, true);
        return $toggle;
    }

    public function enableToggleForProductVariant($toggleCode, $productVariantCode){
        $toggleCode = $this->realEscapeString($toggleCode);
        $productVariantCode = $this->realEscapeString($productVariantCode);
        $query = "INSERT INTO `product_variant_permission_toggle` (`product_variant_id`, `permission_toggle_code`, `created_by`, `created_at`, `updated_by`, `updated_at`)
            VALUES ((SELECT id from product_variant WHERE code='$productVariantCode'), '$toggleCode', 0, NOW(), NULL, NULL)";
        return $this->executeQueryForObject($query, true);
    }

    public function insertPermissionToggleMigration($version, $name){
        $version = $this->realEscapeString($version);
        $name = $this->realEscapeString($name);
        $query = "INSERT INTO `permission_toggle_migrations` (`version`, `migration_name`, `migrated_at`)
          VALUES ($version, '$name', NOW())";
        return $this->executeQueryForObject($query, true);
    }

}<?php

namespace com\linways\nucleus\core\service;

use com\linways\nucleus\core\dto\Expense;
use com\linways\nucleus\core\mapper\ExpenseServiceMapper;
use com\linways\nucleus\core\exception\NucleusException;
use com\linways\base\util\MakeSingletonTrait;

class ExpenseService extends BaseService
{

    use MakeSingletonTrait;
    private function __construct()
    {
        $this->mapper = ExpenseServiceMapper::getInstance()->getMapper();
    }

    /**
     * returns all expenses in the database
     * @return array array of client object
     * @throws NucleusException
     */
    public function getAllExpenses()
    {
        $expenses = [];
        $query = "SELECT e.*, ec.id as categoryId, ec.name as categoryName
        FROM `expenses` as e inner join `expense_category` as ec
        ON e.expense_category_id = ec.id";
        try {
            $expenses = $this->executeQueryForList($query, $this->mapper[ExpenseServiceMapper::GET_ALL_CLIENTS]);
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
        return $expenses;
    }


    /**
     *
     * @param Expense $expense
     * @throws NucleusException
     * @return int | NULL
     */
    public function createExpense($expense)
    {
        $id = null;
        $expense = $this->realEscapeObject($expense);

        if (empty($expense->item) || empty($expense->amount)) {
            throw new NucleusException("ITEM NAME AND AMOUNT IS REQUIRED", "ITEM NAME AND AMOUNT IS REQUIRED");
        }

        $query = "INSERT INTO `expenses` (`item`,`amount`,`expense_category_id`, `date`)
        VALUES ('$expense->item', '$expense->amount', '$expense->expenseCategoryId', '$expense->date')";
        try {
            $id = $this->executeQueryForObject($query, true);
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
        return $id;
    }

    /**
     * Updates a client if exists otherwise creates a new client
     * @param  Expense $expense client object
     * @return int | NucleusException    id of the updated item
     * @throws NucleusException
     */
    public function upsertExpense($expense)
    {
        if ($expense->id) {
            $expense = $this->realEscapeObject($expense);

            if (empty($expense->item) && empty($expense->amount)) {

                if (!empty($expense->id)) {
                    return $this->deleteExpense($expense->id);
                } else {
                    throw new NucleusException(NucleusException::CLIENT_NAME_REQUIRED, "Expense item, amount and date is required");
                }
            }

            $query = "UPDATE `expenses` SET
          `item`='$expense->item',
          `amount`='$expense->amount',
          `expense_category_id`= $expense->expenseCategoryId,
          `date`= '$expense->date' where `id` = $expense->id";
            try {
                $this->executeQueryForObject($query, true);
            } catch (\Exception $e) {
                throw new NucleusException($e->getCode(), $e->getMessage());
            }
            return $expense->id;
        } else {
            //if ID does not exists calls create method
            return $this->createExpense($expense);
        }
    }

    /**
     * Delete a client using clientID
     * @param  int $expenseId id of the client to be deleted
     * @return  null | NucleusException    True if successfully executed.
     * @throws NucleusException
     */
    public function deleteExpense($expenseId)
    {
        $expenseId = $this->realEscapeString($expenseId);
        if (empty($expenseId)) {
            throw new NucleusException(NucleusException::CLIENT_ID_REQUIRED, "Expense id is required");
        }
        $query = "DELETE FROM `expenses` where `id`=$expenseId";
        try {
            $this->executeQuery($query);
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
        return null;
    }

    /**
     * Return total expenses by category for a given month and year
     * @param string $date
     * @return object     with amount and category
     * @throws NucleusException
     */
    public function getTotalExpenseByCategoryAndMonth($date)
    {
        $expenses = [];
        $date = $this->realEscapeString($date);
        $year = date("Y", strtotime($date));
        $month = date("m", strtotime($date));
        $query = "SELECT SUM(ex.amount) AS amount, ec.name FROM
        expenses AS ex INNER JOIN expense_category AS ec ON
        ex.expense_category_id = ec.id WHERE
        YEAR(date)='" . $year . "' AND MONTH(date)='" . $month . "' group by ex.expense_category_id";
        try {
            $expenses = $this->executeQueryForList($query);
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
        return $expenses;
    }

    /**
     * returns total income from billing customers
     * @param string $date
     * @return mixed
     * @throws NucleusException
     */
    public function getTotalIncomeFromBillingCustomers($date)
    {
        $date = $this->realEscapeString($date);
        $endDate = date("Y-m-d", strtotime($date));
        $query = "SELECT SUM(total_annual_income) AS annualIncome
        FROM clients WHERE  DATE(billing_start_date)<='" . $endDate . "'";
        try {
            $income = $this->executeQueryForObject($query);
            return $income->annualIncome;
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
    }

    /**
     * Calculate average expense for single tenant
     * @param string $date
     * @return \stdClass
     * @throws NucleusException
     */
    public function averageExpenseForSingleTenant($date)
    {
        $numberOfBillingTenants = count(CollegeService::getInstance()->getBillingColleges($date));
        $categoryWiseExpenses = $this->getTotalExpenseByCategoryAndMonth($date);
        $annualIncome = $this->getTotalIncomeFromBillingCustomers($date);
        $averageAnnualIncomePerTenant = $annualIncome / $numberOfBillingTenants;
        $averageAnnualExpensePerTenant = 0;
        foreach ($categoryWiseExpenses as $category) {
            //converting monthly expense to annual expense per tenant
            $category->amount = ($category->amount / $numberOfBillingTenants) * 12;
            $averageAnnualExpensePerTenant += $category->amount;
        }
        $response = new \stdClass();
        $response->averageAnnualIncomePerTenant = $averageAnnualIncomePerTenant;
        $response->averageAnnualExpensePerTenant = $averageAnnualExpensePerTenant;
        $response->numberOfBillingTenants = $numberOfBillingTenants;
        $response->categoryWiseExpenses = $categoryWiseExpenses;
        return $response;

    }

    /**
     * Returns all expenses in a month
     * @param integer $year
     * @param integer $month
     * @return null|Object
     * @throws NucleusException
     * @throws \Exception
     */
    public function getAllExpensesOfSingleMonth($year, $month)
    {
        $data = null;
        $year = $this->realEscapeString($year);
        $month = $this->realEscapeString($month);
        if (empty($month) || empty($year))
            throw new NucleusException(NucleusException::INVALID_DATE, "Date required");

        $query = "SELECT e.id,e.item,e.amount, DATE(e.date) as date, ec.name as category FROM `expenses` e 
                  INNER JOIN `expense_category` ec ON e.expense_category_id=ec.id WHERE
                  YEAR(e.date)=$year AND MONTH(e.date)=$month";

        try {
            $data = $this->executeQueryForList($query);
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }

        return $data;
    }
}
<?php
/**
 * Created by PhpStorm.
 * User: jithin
 * Date: 30/10/17
 * Time: 2:23 PM
 */

namespace com\linways\nucleus\core\service;


use com\linways\nucleus\core\dto\AddonDataStore;
use com\linways\nucleus\core\exception\NucleusException;
use com\linways\nucleus\core\mapper\AddonDataStoreMapper;
use com\linways\base\util\MakeSingletonTrait;
use Exception;

class AddonDataStoreSevice extends BaseService
{
    use MakeSingletonTrait;
    private function __construct()
    {
        $this->mapper = AddonDataStoreMapper::getInstance()->getMapper();
    }

    /**
     * returns addon data store details
     * @param integer $enabledAddonId
     * @param string $key
     * @return AddonDataStore $data
     * @throws NucleusException
     */
    public function getData($enabledAddonId, $key)
    {
        $data = NULL;

        $enabledAddonId = $this->realEscapeString($enabledAddonId);
        $key = $this->realEscapeString($key);

        $query = "SELECT * FROM `addon_data_store` WHERE `id`>0";

        if(empty($key))
            throw new NucleusException(NucleusException::INVALID_KEY,"Invalid Key value");
        else
            $query .= " AND `key` = '$key'";

        if(empty($enabledAddonId))
            throw new NucleusException(NucleusException::INVALID_ENABLED_ADDON_ID,"Invalid enabled addon id");
        else
            $query.=" AND `enabled_addon_id` = '$enabledAddonId'";
        try {
            $data = $this->executeQueryForList($query, $this->mapper[AddonDataStoreMapper::GET_ADDON_DATA_STORE_MAPPER]);
        } catch (Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }

        return $data;
    }

    /**
     * Insert details into addon_data_store table.
     * @param AddonDataStore $addonDataStore
     * @return integer
     * @throws NucleusException
     */
    public function putData($addonDataStore)
    {
        $result = NULL;

        $addonDataStore->value = $this->realEscapeString($addonDataStore->value);
        $addonDataStore->key = $this->realEscapeString($addonDataStore->key);
        $addonDataStore->enabledAddonId = $this->realEscapeString($addonDataStore->enabledAddonId);

        $query = "INSERT INTO `addon_data_store` (`id`, `enabled_addon_id`, `key`, `value`, `created_by`, 
                  `created_date`, `updated_by`, `updated_date`) VALUES (NULL, '$addonDataStore->enabledAddonId', 
                  '$addonDataStore->key', '$addonDataStore->value',NULL, utc_timestamp(),NULL, utc_timestamp())";

        try {
            $result = $this->executeQueryForObject($query, true);
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
        return $result;
    }

    /**
     * Deleting data from addon_data_store
     * @param integer $enabledAddonId
     * @param string $key
     * @return bool
     * @throws NucleusException
     */
    public function deleteData($enabledAddonId, $key)
    {
        $status = false;
        $enabledAddonId = $this->realEscapeString($enabledAddonId);
        $key = $this->realEscapeString($key);
        if (empty($enabledAddonId))
            throw new NucleusException(NucleusException::INVALID_ENABLED_ADDON_ID, "Invalid enabled addon id");
        if (empty($key))
            throw new NucleusException(NucleusException::INVALID_KEY, "Invalid key");
        $query = "DELETE FROM `addon_data_store` WHERE `enabled_addon_id`='$enabledAddonId' AND `key`='$key'";

        try {
            $result = $this->executeQueryForObject($query, true);
            $status = true;
        } catch (Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }

        return $status;
    }

    /**
     * Deleting all addon_store_data by enabled addon id
     * @param integer $enabledAddonId
     * @return boolean $status
     * @throws NucleusException
     */
    public function deleteAll($enabledAddonId)
    {
        $status = false;
        $enabledAddonId = $this->realEscapeString($enabledAddonId);
        if (empty($enabledAddonId))
            throw new NucleusException(NucleusException::INVALID_ENABLED_ADDON_ID, "Invalid enabled addon id");

        $query = "DELETE FROM `addon_data_store` WHERE `enabled_addon_id`='$enabledAddonId'";
        try {
            $result = $this->executeQueryForObject($query, true);
            $status = true;
        } catch (Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }

        return $status;
    }

    /**
     * updating addon_data_store fields.
     * @param AddonDataStore $addonDataStore
     * @return bool
     * @throws NucleusException
     */
    public function updateData($addonDataStore){
        $status = false;
        $addonDataStore->id = $this->realEscapeString($addonDataStore->id);
        $addonDataStore->key = $this->realEscapeString($addonDataStore->key);
        $query = "UPDATE `addon_data_store` SET `value` = '$addonDataStore->value',
                  `updated_date` = utc_timestamp() WHERE `id` ='$addonDataStore->id'";
        try{
            $this->executeQueryForObject($query,true);
            $status = true;
        }catch(Exception $e){
            throw new NucleusException($e->getCode(),$e->getMessage());
        }
        return $status;
    }

    /**
     * return addon data store value by enabled addon id and key
     * @param integer $enabledAddonId
     * @param string $key
     * @return null|Object
     * @throws NucleusException
     */
    public function getAddonDataStoreValueByKeyAndEnabledAddonId($enabledAddonId,$key){
        $data = null;
        $enabledAddonId = $this->realEscapeString($enabledAddonId);
        $key = $this->realEscapeString($key);
        $query = "SELECT value FROM `addon_data_store` WHERE `id`>";
        if(empty($key))
            throw new NucleusException(NucleusException::INVALID_KEY,"Invalid Key value");
        else
            $query .= " AND `key` = '$key'";

        if(empty($enabledAddonId))
            throw new NucleusException(NucleusException::INVALID_ENABLED_ADDON_ID,"Invalid enabled addon id");
        else
            $query.=" AND `enabled_addon_id` = '$enabledAddonId'";
        try{
            $data = $this->executeQueryForObject($query,true);
        }catch (Exception $e){
            throw new NucleusException($e->getCode(),$e->getMessage());
        }

        return $data;
    }


}<?php

namespace com\linways\nucleus\core\service;

use com\linways\nucleus\core\dto\ApiOrganizationDetails;
use com\linways\nucleus\core\exception\NucleusException;
use com\linways\nucleus\core\utils\JWTUtils;
use com\linways\base\util\MakeSingletonTrait;
use Exception;

class APIAccessTokenService extends BaseService
{
    use MakeSingletonTrait;

    /**Create organization
     * @param ApiOrganizationDetails $organization
     * @return integer|null
     * @throws NucleusException
     */
    public function createOrganization($organization)
    {
        $id =null;
        $organization->notes = isset($organization->notes) ? $this->realEscapeString($organization->notes) : null;
        $organization->name = $this->realEscapeString($organization->name);
        $organization->address = $this->realEscapeString($organization->address);
        $organization->contactNo = $this->realEscapeString($organization->contactNo);
        $organization->email = $this->realEscapeString($organization->email);

        $query = "INSERT INTO api_organization_details(name ,address, email, contactNo, notes) 
                  VALUES ('$organization->name','$organization->address','$organization->email', 
                  '$organization->contactNo','$organization->notes')";
        try {
            $result = $this->executeQueryForObject($query, true);
            $id = $result;
        } catch (Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }

        return $id;
    }

    /**
     * creating client id and secret id for api
     * @param integer $organizationId
     * @param integer $collegeId
     * @return array
     * @throws NucleusException
     */
    public function createAPIClientIdAndSecret($organizationId, $collegeId)
    {
        $data = [];
        list($accessId, $accessToken) = JWTUtils::createToken($organizationId, $collegeId);
        $query = "INSERT INTO api_access_token (access_id, access_token, college_id, api_organization_details_id, issued_at, is_active) 
		VALUES ('$accessId', '$accessToken', $collegeId, $organizationId, NOW(), true)";
        try {
            $res = $this->executeQueryForObject($query, true);
            $data = [
                'accessId' => $accessId,
                'accessToken' => $accessToken
            ];
        } catch (Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }

        return $data;
    }

    /**
     * return api access token
     * @param integer$accessId
     * @return string|null
     * @throws NucleusException
     */
    public function getAPIAccessToken($accessId)
    {
        $token = null;
        $accessId = $this->realEscapeString($accessId);
        if (empty($accessId))
            throw new NucleusException(NucleusException::INVALID_ACCESS_ID, "Invalid access id");

        $query = "SELECT access_token from api_access_token where access_id='$accessId'";
        try {
            $result = $this->executeQueryForObject($query);
            $token = $result->access_token;
        } catch (Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }

        return $token;
    }
}<?php
namespace com\linways\nucleus\core\service;

use com\linways\base\connection\IMySqlConnector;
use com\linways\base\connection\MySqlConnector;
use com\linways\base\connection\MySqlQuery;
use com\linways\nucleus\core\exception\NucleusException;

class BaseService extends MySqlQuery implements IMySqlConnector {
	/**
	 * @var MySqlConnector
	 */
	public $mySqlConnector;

	/**
	 * Establish a MYSQL Connection
	 * If connection and db properties not set this method
	 * help to establish the connection.
	 * This will be called from MySqlQuery or also you can call externally
	 */
	public function establishConnection() {
		$this->mySqlConnector = MySqlConnector::getInstance();
		/**
		 * This condition is mandatory to check multiple database connections.
		 * If not added then connection will be reused from the first project ie projectA service
		 * invoked first then mysqlconnection will established based on the projectA DB configuration
		 * after that if we invoke projectB service mysqlConnection will be resused from ProjectA bz of
		 * singleton instance of mysqlconnection
		 * to avoid this situation we need to set 'instanceof' attribute value in setDBConnectionProperties() method
		 */
		if ($this->mySqlConnector->instanceOf != null && !$this->mySqlConnector->instanceOf instanceof BaseService) {
			$this->mySqlConnector->closeConnection();
		}

		if (!$this->mySqlConnector->connection) {

			$this->setDBConnectionProperties();
		}
		$this->connection = $this->mySqlConnector->getConnection();
	}
	/**
	 * Set DB Connection properties
	 */
	protected function setDBConnectionProperties() {

		// check DB configurations
		$dbconfigFilePath = getenv('NUCLEUS_CONF');

		//check file configured correctly
		if (empty($dbconfigFilePath)) {
			throw new NucleusException(NucleusException::DB_CONFIG_FILE_PATH_NOT_DEFINED_IN_SERVER, "Database config for nucleus is not found. Please set env `NUCLEUS_CONF`");
		}
		//include db configuration file to load connection properties
		require $dbconfigFilePath;
		// global  $DB_HOST,$DB_USER,$DB_PASSWD,$DB_NAME;
		if (empty($DB_NUCLEUS_HOST) || empty($DB_NUCLEUS_USER) || empty($DB_NUCLEUS_PASSWD) || empty($DB_NUCLEUS_NAME)) {
			throw new NucleusException(NucleusException::DB_CONFIG_NOT_SET, "Nucleus  DB configuration not set properly. Please set DB_NUCLEUS_HOST, DB_NUCLEUS_USER, DB_NUCLEUS_PASSWD and DB_NUCLEUS_NAME");
		}

		//Set DB Connection properties
		$this->mySqlConnector->DB_HOST   = $DB_NUCLEUS_HOST;
		$this->mySqlConnector->DB_USER   = $DB_NUCLEUS_USER;
		$this->mySqlConnector->DB_PASSWD = $DB_NUCLEUS_PASSWD;
		$this->mySqlConnector->DB_NAME   = $DB_NUCLEUS_NAME;

		$this->mySqlConnector->instanceOf = $this;
	}
}
<?php

namespace com\linways\nucleus\core\service;

use com\linways\base\util\MakeSingletonTrait;
use com\linways\nucleus\core\utils\StringUtils;
use com\linways\nucleus\core\dto\Client;
use com\linways\nucleus\core\exception\NucleusException;
use com\linways\nucleus\core\mapper\ClientServiceMapper;
use Symfony\Component\Config\Definition\Exception\Exception;

class ClientService extends BaseService
{
    use MakeSingletonTrait;

    private function __construct()
    {
        $this->mapper = ClientServiceMapper::getInstance()->getMapper();
    }

    /**
     *
     * @param Client $client
     * @throws NucleusException
     * @return int | NULL
     */
    public function createClient($client)
    {
        $id = null;
        $client = $this->realEscapeObject($client);

        if (empty($client->name)) {
            throw new NucleusException(NucleusException::CLIENT_NAME_REQUIRED, "Client name is required");
        }

        $query = "INSERT INTO `clients` (`name`,
        `address`,`total_annual_income`, `service_start_date`,
        `billing_start_date`, `created_by`, `created_at`, `updated_by`, `updated_at`)
        VALUES ('$client->name',
        '$client->address'," .
            StringUtils::stringifyNull($client->totalAnnualIncome) . "," .
            StringUtils::stringifyNull($client->serviceStartDate) . "," .
            StringUtils::stringifyNull($client->billingStartDate) . ", 0, UTC_TIMESTAMP(), 0, UTC_TIMESTAMP())";
        try {
            $id = $this->executeQueryForObject($query, true);
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
        return $id;
    }

    /**
     * return all client details
     * @param null $date
     * @return array|Object
     * @throws NucleusException
     */
    public function getAllClients($date = NULL)
    {
        $clients = [];
        $date = $this->realEscapeString($date);
        $endDate = date("Y-m-d", strtotime($date));
        $query = "SELECT * FROM `clients`";
        if ($date) {
            $query .= "WHERE service_start_date<='" . $endDate . "'";
        }
        try {
            $clients = $this->executeQueryForList($query, $this->mapper[ClientServiceMapper::GET_ALL_CLIENTS]);
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }

        return $clients;
    }

    /**
     * Updates a client if exists otherwise creates a new client
     * @param  Client $client client object
     * @return int | NucleusException    id of the updated item
     * @throws NucleusException
     */
    public function upsertClient($client)
    {
        if ($client->id) {
            $client = $this->realEscapeObject($client);
            if (empty($client->name)) {
                if (!empty($client->id) && empty($client->address) && empty($client->totalAnnualIncome) && empty($client->serviceStartDate) && empty($client->billingStartDate)) {
                    return $this->deleteClient($client->id);
                } else {
                    throw new NucleusException(NucleusException::CLIENT_NAME_REQUIRED, "Client name is required");
                }
            }

            $query = "UPDATE `clients` SET
          `name`='$client->name',
          `address`='$client->address',
          `total_annual_income`=" . StringUtils::stringifyNull($client->totalAnnualIncome) . ",
          `service_start_date`=" . StringUtils::stringifyNull($client->serviceStartDate) . ",
          `billing_start_date`=" . StringUtils::stringifyNull($client->billingStartDate) . ",
          `updated_by`=0,
          `updated_at`=UTC_TIMESTAMP() where `id`=$client->id";

            try {
                $this->executeQueryForObject($query, true);
            } catch (\Exception $e) {
                throw new NucleusException($e->getCode(), $e->getMessage());
            }
            return $client->id;
        } else {
            //if ID does not exists calls create method
            return $this->createClient($client);
        }
    }

    /**
     * Delete a client using clientID
     * @param  int $clientId id of the client to be deleted
     * @return  null | NucleusException    True if successfully executed.
     * @throws NucleusException
     */
    public function deleteClient($clientId)
    {
        $clientId = $this->realEscapeString($clientId);
        if (empty($clientId)) {
            throw new NucleusException(NucleusException::CLIENT_ID_REQUIRED, "Client id is required");
        }
        if (CollegeService::getInstance()->getCountOfCollegesByClientId($clientId)) {
            throw new NucleusException(NucleusException::NOT_EMPTY_CLIENT, "This client contain active colleges. Remove colleges and try again");
        }
        $query = "DELETE FROM `clients` where `id`=$clientId";
        try {
            $this->executeQuery($query);
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
        return null;
    }

    /**
     * Returns client details by client id
     * @param  integer $clientId
     * @return \stdClass Object
     * @throws NucleusException
     */
    public function getClientById($clientId)
    {
        $data = null;
        $clientId = $this->realEscapeString($clientId);
        if (empty($clientId)) {
            throw new NucleusException(NucleusException::CLIENT_ID_REQUIRED . "Client id is required");
        }
        $query = 'SELECT * FROM `clients` WHERE id=' . $clientId;
        try {
            $data = $this->executeQueryForObject($query);
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
        return $data;

    }

    public function getBasicDetailsOfAllClients()
    {
        $clients = null;
        $query = "SELECT id,name FROM `clients` ORDER BY name";
        try {
            $clients = $this->executeQueryForList($query, $this->mapper[ClientServiceMapper::GET_BASIC_DETAILS_OF_ALL_CLIENTS]);
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
        return $clients;
    }

    /**
     * Return count of clients and average trial period
     * @throws NucleusException
     */
    public function getCountOfClientsAndAverageTrialPeriod($date)
    {
        $data = null;
        $date = $this->realEscapeString($date);
        $endDate = date("Y-m-d", strtotime($date));

        $query = "SELECT COUNT(id) AS count, AVG(DATEDIFF(`billing_start_date`,`service_start_date`)) 
                  as averageDate FROM `clients` 
                  WHERE service_start_date<='" . $endDate . "' AND billing_start_date<='" . $endDate . "'";

        try {
            $data = $this->executeQueryForObject($query);
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
        return $data;
    }

    /**
     * Return number clients on trial and number of tenants in single client
     * @return null
     * @throws NucleusException
     */
    public function getClientsOnTrialAndCountOfTenantsInSingleClient($date)
    {
        $result = null;
        $date = $this->realEscapeString($date);
        $endDate = date("Y-m-d", strtotime($date));
        try {
            $query = "SELECT COUNT(DISTINCT c.id) as clientCount, COUNT(DISTINCT cg.id) as tenantCount FROM 
                      `clients` c INNER JOIN colleges cg ON cg.client_id=c.id WHERE 
                      DATE(c.service_start_date)<='" . $endDate . "' AND (
                      c.billing_start_date IS NULL OR DATE(c.billing_start_date)>'" . $endDate . "')";
            $result = $this->executeQueryForObject($query);
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
        return $result;
    }


}
<?php

namespace com\linways\nucleus\core\service;


use com\linways\nucleus\core\utils\DateUtils;
use com\linways\base\util\MakeSingletonTrait;

class ReportService extends  BaseService
{
    use MakeSingletonTrait;

    /**
     * @param $from  From month and year in mm-YYYY
     * @param $to    To month and year mm-YYYY
     * @return array
     * @throws \Exception
     */
    public function getAllRecurringExpensesBetween2Months($from , $to){
        $this->realEscapeString($from);
        $this->realEscapeString($to);
        $months = DateUtils::getMonthsBetween2Dates($from, $to);
        $output = [];
        foreach ($months as $month){
            $query = "select ec.name as category, sum(era.amount) as amount
                from expense_recurring_amount era inner join expense_recurring er 
                on era.expense_recurring_id = er.id inner join expense_category ec on er.expense_category_id = ec.id
                where from_date <= '$month[0]'  and (to_date >= '$month[1]'  or to_date is null) group by ec.id";
            $monthlyExpenses=   $this->executeQueryForList($query);
            $output[date('m-Y',strtotime($month[0]))] = $this->processMontlyExpenseCategoryWise($monthlyExpenses);;
        }
        return $output;
    }


    public function getAllNonRecurringExpenseBetween2Months($from, $to){
        $this->realEscapeString($from);
        $this->realEscapeString($to);
        $months = DateUtils::getMonthsBetween2Dates($from, $to);
        $output = [];
        foreach ($months as $month){
            $query = "select ec.name as category ,sum(e.amount) as amount 
                from expenses e inner join expense_category ec on e.expense_category_id = ec.id 
                where e.date>='$month[0]' and e.date <= '$month[1]' group by ec.id";
            $monthlyExpenses=   $this->executeQueryForList($query);
            $output[date('m-Y',strtotime($month[0]))] = $this->processMontlyExpenseCategoryWise($monthlyExpenses);
        }
        return $output;
    }

    private function processMontlyExpenseCategoryWise($monthlyExpenses){
        $output = [
            "amount" => 0,
            "categoryWise" => []
        ];
        foreach ($monthlyExpenses as $expense){
            $output["amount"] += $expense->amount;
            $output["categoryWise"][$expense->category] = $expense->amount;
        }
        return $output;
    }

    public function getExpensesBetween2Months($from, $to){
        $this->realEscapeString($from);
        $this->realEscapeString($to);
        $totalExpenses = [];
        $recurringExpenses = $this->getAllRecurringExpensesBetween2Months($from, $to);
        $nonRecurringExpenses = $this->getAllNonRecurringExpenseBetween2Months($from, $to);
        foreach ($recurringExpenses as $month => $expenses){
            $totalExpenses[$month] =[
                "amount" => $expenses['amount'] + $nonRecurringExpenses[$month]['amount'],
                "categoryWise" => []
            ];
            $categoryNames = array_keys(array_merge($expenses['categoryWise'], $nonRecurringExpenses[$month]['categoryWise']));
            foreach ($categoryNames as $category){
                $totalExpenses[$month]['categoryWise'][$category] =  $expenses['categoryWise'][$category] +  $nonRecurringExpenses[$month]['categoryWise'][$category];
            }
        }
        return $totalExpenses;
    }

    public function getMRRBetween2Months($from, $to){
        $this->realEscapeString($from);
        $this->realEscapeString($to);
        $months = DateUtils::getMonthsBetween2Dates($from, $to);
        $output = [];
        foreach ($months as $month){
            $arr = ExpenseService::getInstance()->getTotalIncomeFromBillingCustomers($month[1]);
            $output[date('m-Y',strtotime($month[0]))] = round($arr/12);
        }
        return $output;
    }
}<?php
namespace com\linways\nucleus\core\service;

use com\linways\nucleus\core\exception\SchedulerException;
use com\linways\nucleus\core\mapper\SchedulerServiceMapper;
use com\linways\base\util\MakeSingletonTrait;
use Cron\CronExpression;

/**
 * Services for managing scheduled tasks.
 */

class SchedulerService extends BaseService {

	use MakeSingletonTrait;
	private function __construct() {
		$this->mapper = SchedulerServiceMapper::getInstance()->getMapper();
	}


	/**
	 * getConfigArrayForAMS - get all configuration file paths (required for linways professional) form the environment and  pushes into an array
	 * All files in this array will be included when executing scheduled task to get college context.
	 *
	 * @return string - json encoded array of configuration files
	 */
	private function getConfigArrayForAMS() {
		$conf = [];
		if (getenv('AMS_CONFIG')) {
		    $conf["AMS_CONFIG"] = getenv('AMS_CONFIG');
		}
		if (getenv('DB_PROFESIONAL_CONFIG')) {
		    $conf["DB_PROFESIONAL_CONFIG"] = getenv('DB_PROFESIONAL_CONFIG');
		}
		if (getenv('NUCLEUS_CONF')) {
		    $conf["NUCLEUS_CONF"] = getenv('NUCLEUS_CONF');
		}

		return json_encode($conf);

	}

	/**
	 * schedule - Schedules a task to a future date.
	 *
	 * @param ScheduleRequest $scheduleRequest
	 * @return int id of the created schedule
	 */
	public function addTask($scheduleRequest) {
		//TODO : Add validations
		//args = $method, $params=[], $frequency=null, $code=null, $nextExecutionAt=null
		if (empty($scheduleRequest->frequency) && empty($scheduleRequest->nextExecutionAt)) {
			throw new \InvalidArgumentException("frequency and/or nextExecutionAt not specified.");
		}
// 		if (empty($scheduleRequest->method)) {
// 			throw new \InvalidArgumentException("Method is required.");
// 		}

		if ($scheduleRequest->frequency && !CronExpression::isValidExpression($scheduleRequest->frequency)) {
			throw new SchedulerException(SchedulerException::INVALID_FREQUENCY_STRING, "frequency is not a valid cron expression");
		}
		$scheduleRequest->nextExecutionAt = empty($scheduleRequest->nextExecutionAt)?'NULL':"'$scheduleRequest->nextExecutionAt'";
		$scheduleRequest->code            = empty($scheduleRequest->code)?'NULL':"'$scheduleRequest->code'";

// 		$scheduleRequest->param['method'] = $scheduleRequest->method;
		$scheduleRequest->conf            = $this->getConfigArrayForAMS();
		$scheduleRequest->params          = json_encode($scheduleRequest->params);
		if(strlen($scheduleRequest->params) > 65535){
		    throw new SchedulerException(SchedulerException::STRING_IS_TOO_LONG, "length of params cannot be greater than allowable limit of TEXT");
		}
		$sql                              = "INSERT INTO scheduled_task ( code, conf, params, frequency, next_execution_at, created_at)
        VALUES ($scheduleRequest->code, '$scheduleRequest->conf', '$scheduleRequest->params', '$scheduleRequest->frequency', $scheduleRequest->nextExecutionAt, NOW())";
		return $this->executeQueryForObject($sql, true);
	}

	/**
	 * Returns Schedule item by given id
	 * @param  int $id id of the schedule item
	 * @return ScheduledTask
	 */
	public function getScheduledTaskById($id) {
		//TODO validation

		$sql = "SELECT * FROM scheduled_task WHERE id=$id";
		return $this->executeQueryForObject($sql, FALSE, $this->mapper[SchedulerServiceMapper::GET_SCHEDULE_MAPPER]);
	}

	/**
	 * Returns Schedule item for a given unique code
	 * @param  string $code Unique code of the scheduled task
	 * @return ScheduledTask
	 */
	public function getScheduledTaskByCode($code) {
		//TODO validation

		$sql = "SELECT * FROM scheduled_task WHERE code=$code";
		return $this->executeQueryForObject($sql, FALSE, $this->mapper[SchedulerServiceMapper::GET_SCHEDULE_MAPPER]);
	}

	/**
	 * delete a schedule using id
	 * @param  int $id id of the schedule item in the database
	 * @return bool    true is deletion is successful else returns false
	 */
	public function deleteScheduledTaskById($id) {
		//     TODO : Add validations
		if (empty($id)) {
			throw new \InvalidArgumentException("id of the schedule is required.");
		}

		$sql = "DELETE from scheduled_task WHERE id=$id";
		$this->executeQueryForObject($sql, true);
		return true;
	}

	/**
	 * Delete a schedule using unique code
	 * @param  string $code unique code for the schedule item to be deleted
	 * @return bool         true is deletion is successful else returns false
	 */
	public function deleteScheduledTaskByCode($code) {
		//     TODO : Add valcodeations
		if (empty($code)) {
			throw new \InvalidArgumentException("id of the schedule is required.");
		}

		$sql = "DELETE from scheduled_task WHERE code='$code'";
		$this->executeQueryForObject($sql, true);
		return true;
	}

	/**
	 * Calculate next execution timestamp using frequency string(cron job scheduler string)
	 * and updates nextExecutionAt and lastUpdatedAt fields
	 * @param  ScheduledTask $scheduledTask  Scheduled task object
	 * @return true|SchedulerException     [description]
	 */
	public function updateNextExecution($scheduledTask) {
	    if($scheduledTask->frequency){
	        $cronExp         = CronExpression::factory($scheduledTask->frequency);
	        $nextExecutionAt = $cronExp->getNextRunDate()->format('Y-m-d H:i:s');
	    }else {
	        $nextExecutionAt = null;
	    }
		
		$nextExecutionAt = empty($nextExecutionAt)?'NULL':"'$nextExecutionAt'";

		$sql = "UPDATE scheduled_task SET
              next_execution_at=$nextExecutionAt,
              last_executed_at='$scheduledTask->nextExecutionAt'
              WHERE id=$scheduledTask->id";
		return $this->executeQuery($sql, TRUE);
	}

	/**
	 * returns all tasks whose next_execution_at is in the past.
	 * Since we are setting next_execution_at as NULL if we are done with a
	 * Task, unnecessary tasks will not come. Since we are running all tasks
	 * as async chances for overlap of tasks are less.
	 * @return SchedulerException[]
	 */
	public function getDueTasks() {
		$sql = "SELECT * FROM scheduled_task WHERE next_execution_at <= NOW()";
		return $this->executeQueryForList($sql, $this->mapper[SchedulerServiceMapper::GET_SCHEDULE_MAPPER]);
	}
}
<?php

namespace com\linways\nucleus\core\service;

use com\linways\nucleus\core\dto\College;
use com\linways\nucleus\core\exception\NucleusException;
use com\linways\nucleus\core\mapper\CollegeServiceMapper;
use com\linways\base\util\MakeSingletonTrait;
use com\linways\nucleus\core\utils\StringUtils;
use Symfony\Component\Config\Definition\Exception\Exception;

class CollegeService extends BaseService
{
    use MakeSingletonTrait;
    private function __construct()
    {
        $this->mapper = CollegeServiceMapper::getInstance()->getMapper();
    }

    /**
     *
     * @param College $college
     * @throws NucleusException
     * @return integer|NULL
     */
    public function createCollege($college)
    {
        $id = null;
        $college->code = $this->realEscapeString($college->code);
        $college->collegeDb = $this->realEscapeString($college->collegeDb);
        $college->name = $this->realEscapeString($college->name);
        $college->hostName = $this->realEscapeString($college->hostName);
        $college->internalHostName = $this->realEscapeString($college->internalHostName);
        $college->dbUsername = $this->realEscapeString($college->dbUsername);
        $college->dbPassword = $this->realEscapeString($college->dbPassword);
        $college->clientId = $college->clientId ? $this->realEscapeString($college->clientId): 'NULL';
        $college->createdBy = $this->realEscapeString($college->createdBy);
        $college->updatedBy = $this->realEscapeString($college->updatedBy);
        $college->dbHost = $college->dbHost ? $this->realEscapeString($college->dbHost) : 'localhost';
        if (empty($college->dbUsername) ||
            empty($college->dbPassword) ||
            empty($college->name)||
            empty($college->hostName) ||
            empty($college->internalHostName) ||
            empty($college->code)) {
            throw new NucleusException(NucleusException::DB_USERNAME_AND_PASSWORD_REQUIRED, "College code, name ,DB username and DBpassword is required");
        }

        $query = "INSERT INTO colleges (`college_code`,`host_name`, `internal_host_name`,`college_name`,`college_db`, `db_username`, `db_password`, `db_host`,`client_id`,`created_by`,`created_at`,`updated_by`,`updated_at`)
                VALUES ('$college->code',
                '$college->hostName',
                '$college->internalHostName',
                '$college->name',
                '$college->collegeDb',
                '$college->dbUsername',
                '$college->dbPassword',
                '$college->dbHost',
                $college->clientId,
                '$college->createdBy',
                UTC_TIMESTAMP(),
                '$college->updatedBy',
                UTC_TIMESTAMP()

              )";
        try {
            $id = $this->executeQueryForObject($query, true);
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
        return $id;
    }

    /**
     * Returns  all Colleges/tenants
     * @return College[]
     * @throws NucleusException
     */
    public function getAllColleges()
    {
        $colleges = [];
        $query = "SELECT * FROM colleges WHERE isActive=1";
        try {
            // $colleges = $this->executeQueryForList($query, $this->mapper[CollegeServiceMapper::GET_ALL_COLLEGES]);
            $colleges = $this->executeQueryForList($query, $this->mapper[CollegeServiceMapper::GET_ALL_COLLEGES]);
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
        return $colleges;
    }

    /**
     * returns all colleges under billing (Does not inclues the one in trial)
     * @return College[]
     * @throws NucleusException
     */
    public function getBillingColleges($date)
    {
        $colleges = [];
        $date = $this->realEscapeString($date);
        $endDate = date("Y-m-d", strtotime($date));
        $query = "SELECT clg.id, clg.college_code, clg.college_name, cli.id AS client_id, cli.service_start_date, cli.billing_start_date
                  FROM colleges AS clg INNER JOIN clients AS cli ON clg.client_id = cli.id WHERE
                  DATE(cli.billing_start_date)<='" . $endDate . "' AND clg.isActive=1";
        try {
            $colleges = $this->executeQueryForList($query, $this->mapper[CollegeServiceMapper::GET_BILLING_COLLEGES]);
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
        return $colleges;
    }

    /**
     * returns number of colleges available in client
     * @param integer $clientId
     * @return int
     * @throws NucleusException
     */
    public function getCountOfCollegesByClientId($clientId)
    {
        $data = 0;
        $clientId = $this->realEscapeString($clientId);
        if (empty($clientId))
            throw new NucleusException(NucleusException::CLIENT_ID_REQUIRED, "Client id required");

        $query = "SELECT COUNT(id) as count FROM `colleges` WHERE client_id=$clientId";
        try {
            $result = $this->executeQueryForObject($query);
            if ($result)
                $data = $result->count;
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
        return (int)$data;
    }

    /**
     * Returns college details by college id
     * @param $collegeId
     * @return null|\stdClass
     * @throws NucleusException
     */
    public function getCollegeByCollegeId($collegeId)
    {
        $data = null;
        $collegeId = $this->realEscapeString($collegeId);
        if (empty($collegeId))
            throw new NucleusException(NucleusException::TENANT_ID_IS_REQUIRED, "Tenant id is required");
        $query = "SELECT * FROM `colleges` WHERE id=$collegeId";
        try {
            $data = $this->executeQueryForObject($query);
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
        return $data;
    }

    /**
     * Returns college details by college code
     * @param  string $collegeCode [description]
     * @return null|\stdClass
     * @throws NucleusException
     */
    public function getCollegeByCollegeCode($collegeCode)
    {
        $data = null;
        $collegeCode = $this->realEscapeString($collegeCode);
        if (empty($collegeCode))
            throw new NucleusException(NucleusException::COLLEGE_CODE_IS_REQUIRED, "College code is required");
        $query = "SELECT * FROM `colleges` WHERE college_code='$collegeCode'";
        try {
            $data = $this->executeQueryForObject($query, false, $this->mapper[CollegeServiceMapper::GET_ALL_COLLEGES]);
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
        return $data;
    }

    /**
     * @param College $college
     * @return mixed
     * @throws NucleusException
     */
    public function upsertCollege($college)
    {
        if ($college->id) {
            $college = $this->realEscapeObject($college);
            if (empty($college->name)) {
                if (!empty($college->id) && empty($college->code) && empty($college->collegeDb) && empty($college->dbPassword) && empty($college->dbHost) && empty($college->dbUserName)) {
                    return $this->deleteCollege($college);
                } else {
                    throw new NucleusException(NucleusException::REQUIRED_FIELDS_MISSING, "Empty fields submitted");
                }
            }
            $query = "UPDATE `colleges` SET
                      `college_code`='" . $college->code . "',
                      `host_name`='" . $college->hostName . "',
                      `internal_host_name`='" . $college->internalHostName . "',
                      `college_name`='" . $college->name . "',
                      `college_db`='" . $college->collegeDb . "',
                      `db_username`='" . $college->dbUsername . "',
                      `db_password`='" . $college->dbPassword . "',
                      `db_host`='" . $college->dbHost . "',
                      `client_id`='" . $college->clientId . "',
                      `updated_by`=" . StringUtils::stringifyNull($college->updatedBy) . ",
                      `updated_at`=UTC_TIMESTAMP()
                      WHERE `id`='" . $college->id . "'";
            try {
                $this->executeQueryForObject($query, true);
            } catch (\Exception $e) {
                throw new NucleusException($e->getCode(), $e->getMessage());
            }
            return $college->id;
        } else {
            //if ID does not exists calls create method
            return $this->createCollege($college);
        }
    }

    /**
     * Deleting college/tenant by resetI isActive flag
     * @param College $college
     * @return bool true|false
     * @throws NucleusException
     */
    public function deleteCollege($college)
    {
        $status = false;
        $college = $this->realEscapeObject($college);
        if (empty($college->id))
            throw new NucleusException(NucleusException::TENANT_ID_IS_REQUIRED, "Tenant id is required");
        $query = "UPDATE `colleges` SET
                  `isActive`=0,
                  `updated_by`= " . StringUtils::stringifyNull($college->updatedBy) . ",
                  `updated_at`= UTC_TIMESTAMP()
                  WHERE id=$college->id";
        try {
            $this->executeQuery($query);
            $status = true;
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
        return $status;
    }

    public function getHostNameByCollegeCode($collegeCode, $internal=false){
        $collegeCode = $this->realEscapeString($collegeCode);

        if (empty($collegeCode))
            throw new NucleusException(NucleusException::COLLEGE_CODE_IS_REQUIRED, "College code is required");

        $dbField = $internal? 'internal_host_name': 'host_name';
        $query = "SELECT $dbField FROM `colleges` WHERE college_code='$collegeCode'";

        try {
            $data = $this->executeQueryForObject($query);
        } catch (\Exception $e) {
            throw new NucleusException($e->getCode(), $e->getMessage());
        }
        return $internal ? $data->internal_host_name: $data->host_name;
    }

}
<?php
namespace com\linways\nucleus\core\exception;

use com\linways\base\exception\CoreException;

class TaskQueueWorkerException extends CoreException {
	const CLASS_NOT_FOUND           = "CLASS_NOT_FOUND";
	const METHOD_NOT_FOUND_IN_CLASS = "METHOD_NOT_FOUND_IN_CLASS";
}

?><?php
namespace com\linways\nucleus\core\exception;

use com\linways\base\exception\CoreException;

class SchedulerException extends CoreException
{
    const INVALID_FREQUENCY_STRING = "INVALID_FREQUENCY_STRING";
    const STRING_IS_TOO_LONG = "STRING_IS_TOO_LONG";
}
<?php

namespace com\linways\nucleus\core\exception;

use com\linways\base\exception\CoreException;

class NucleusException extends CoreException
{
    const INVALID_SCOPE = "INVALID_SCOPE";
    const INVALID_SCOPE_ID = "INVALID_SCOPE_ID";
    const INVALID_ADDON_ID = "INVALID_ADDON_ID";
    const INVALID_COLLEGE_CODE = "INVALID_COLLEGE_CODE";
    const DB_CONFIG_FILE_PATH_NOT_DEFINED_IN_SERVER = "DB_CONFIG_FILE_PATH_NOT_DEFINED_IN_SERVER";
    const INVALID_ATTRIBUTE_CODE = "INVALID_ATTRIBUTE_CODE";
    const DB_CONFIG_NOT_SET = "DB_CONFIG_NOT_SET";
    const INVALID_ENABLED_ADDON_ID = "INVALID_ENABLED_ADDON_ID";
    const INVALID_KEY = "INVALID_KEY";
    const INVALID_PERMISSION_ID = "INVALID_PERMISSION_ID";
    const INVALID_API_ACCESS_TOKEN_ID = "INVALID_API_ACCESS_TOKEN_ID";
    const INVALID_ACCESS_ID = "INVALID_ACCESS_ID";
    const DB_USERNAME_AND_PASSWORD_REQUIRED = "DB_USERNAME_AND_PASSWORD_REQUIRED";
    const CLIENT_NAME_REQUIRED = "CLIENT_NAME_REQUIRED";
    const CLIENT_ID_REQUIRED = "CLIENT_ID_REQUIRED";
    const NOT_EMPTY_CLIENT = "NOT_EMPTY_CLIENT";
    const TENANT_ID_IS_REQUIRED = "TENANT_ID_IS_REQUIRED";
    const COLLEGE_CODE_IS_REQUIRED = "COLLEGE_CODE_IS_REQUIRED";
    const REQUIRED_FIELDS_MISSING ="REQUIRED_FIELDS_MISSING";
    const INVALID_DATE = "INVALID_DATE";
    const INVALID_EXPENSE_CATEGORY_NAME_ad = "INVALID_EXPENSE_CATEGORY_NAME";
    const ITEM_NAME_AND_CATEGORY_REQUIRED = "ITEM_NAME_AND_CATEGORY_REQUIRED";
    const ID_REQUIRED = 'ID_REQUIRED';
    const EXPENSE_ID_AMOUNT_START_DATE_REQUIRED = 'EXPENSE_ID_AMOUNT_START_DATE_REQUIRED';
    const PRODUCT_VARIANT_AND_FEATURE_CODE_REQUIRED = 'PRODUCT_VARIANT_AND_FEATURE_CODE_REQUIRED';
    const CANNOT_ADD_ALL_FOR_BOTH_ENABLED_AND_DISABLED_PRODUCT_VARIANTS = "CANNOT_ADD_ALL_FOR_BOTH_ENABLED_AND_DISABLED_PRODUCT_VARIANTS";
    const CANNOT_USE_ALL_ALONG_WITH_OTHER_PRODUCT_VARIANTS = "CANNOT_USE_ALL_ALONG_WITH_OTHER_PRODUCT_VARIANTS";
    const INVALID_PRODUCT_VARIANT = "INVALID_PRODUCT_VARIANT";
    const FAILED_TO_MIGRATE_TOGGLE = "FAILED_TO_MIGRATE_TOGGLE";
}
{
    "name": "mtdowling/cron-expression",
    "type": "library",
    "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due",
    "keywords": ["cron", "schedule"],
    "license": "MIT",
    "authors": [{
        "name": "Michael Dowling",
        "email": "mtdowling@gmail.com",
        "homepage": "https://github.com/mtdowling"
    }],
    "require": {
        "php": ">=5.3.2"
    },
    "require-dev": {
        "phpunit/phpunit": "~4.0|~5.0"
    },
    "autoload": {
        "psr-4": {
            "Cron\\": "src/Cron/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Tests\\": "tests/Cron/"
        }
    }
}<?php

namespace Cron;

use InvalidArgumentException;

/**
 * CRON field factory implementing a flyweight factory
 * @link http://en.wikipedia.org/wiki/Cron
 */
class FieldFactory
{
    /**
     * @var array Cache of instantiated fields
     */
    private $fields = array();

    /**
     * Get an instance of a field object for a cron expression position
     *
     * @param int $position CRON expression position value to retrieve
     *
     * @return FieldInterface
     * @throws InvalidArgumentException if a position is not valid
     */
    public function getField($position)
    {
        if (!isset($this->fields[$position])) {
            switch ($position) {
                case 0:
                    $this->fields[$position] = new MinutesField();
                    break;
                case 1:
                    $this->fields[$position] = new HoursField();
                    break;
                case 2:
                    $this->fields[$position] = new DayOfMonthField();
                    break;
                case 3:
                    $this->fields[$position] = new MonthField();
                    break;
                case 4:
                    $this->fields[$position] = new DayOfWeekField();
                    break;
                case 5:
                    $this->fields[$position] = new YearField();
                    break;
                default:
                    throw new InvalidArgumentException(
                        $position . ' is not a valid position'
                    );
            }
        }

        return $this->fields[$position];
    }
}
<?php

namespace Cron;

use DateTime;

/**
 * Day of month field.  Allows: * , / - ? L W
 *
 * 'L' stands for "last" and specifies the last day of the month.
 *
 * The 'W' character is used to specify the weekday (Monday-Friday) nearest the
 * given day. As an example, if you were to specify "15W" as the value for the
 * day-of-month field, the meaning is: "the nearest weekday to the 15th of the
 * month". So if the 15th is a Saturday, the trigger will fire on Friday the
 * 14th. If the 15th is a Sunday, the trigger will fire on Monday the 16th. If
 * the 15th is a Tuesday, then it will fire on Tuesday the 15th. However if you
 * specify "1W" as the value for day-of-month, and the 1st is a Saturday, the
 * trigger will fire on Monday the 3rd, as it will not 'jump' over the boundary
 * of a month's days. The 'W' character can only be specified when the
 * day-of-month is a single day, not a range or list of days.
 *
 * @author Michael Dowling <mtdowling@gmail.com>
 */
class DayOfMonthField extends AbstractField
{
    /**
     * Get the nearest day of the week for a given day in a month
     *
     * @param int $currentYear  Current year
     * @param int $currentMonth Current month
     * @param int $targetDay    Target day of the month
     *
     * @return \DateTime Returns the nearest date
     */
    private static function getNearestWeekday($currentYear, $currentMonth, $targetDay)
    {
        $tday = str_pad($targetDay, 2, '0', STR_PAD_LEFT);
        $target = DateTime::createFromFormat('Y-m-d', "$currentYear-$currentMonth-$tday");
        $currentWeekday = (int) $target->format('N');

        if ($currentWeekday < 6) {
            return $target;
        }

        $lastDayOfMonth = $target->format('t');

        foreach (array(-1, 1, -2, 2) as $i) {
            $adjusted = $targetDay + $i;
            if ($adjusted > 0 && $adjusted <= $lastDayOfMonth) {
                $target->setDate($currentYear, $currentMonth, $adjusted);
                if ($target->format('N') < 6 && $target->format('m') == $currentMonth) {
                    return $target;
                }
            }
        }
    }

    public function isSatisfiedBy(DateTime $date, $value)
    {
        // ? states that the field value is to be skipped
        if ($value == '?') {
            return true;
        }

        $fieldValue = $date->format('d');

        // Check to see if this is the last day of the month
        if ($value == 'L') {
            return $fieldValue == $date->format('t');
        }

        // Check to see if this is the nearest weekday to a particular value
        if (strpos($value, 'W')) {
            // Parse the target day
            $targetDay = substr($value, 0, strpos($value, 'W'));
            // Find out if the current day is the nearest day of the week
            return $date->format('j') == self::getNearestWeekday(
                $date->format('Y'),
                $date->format('m'),
                $targetDay
            )->format('j');
        }

        return $this->isSatisfied($date->format('d'), $value);
    }

    public function increment(DateTime $date, $invert = false)
    {
        if ($invert) {
            $date->modify('previous day');
            $date->setTime(23, 59);
        } else {
            $date->modify('next day');
            $date->setTime(0, 0);
        }

        return $this;
    }

    /**
     * Validates that the value is valid for the Day of the Month field
     * Days of the month can contain values of 1-31, *, L, or ? by default. This can be augmented with lists via a ',',
     * ranges via a '-', or with a '[0-9]W' to specify the closest weekday.
     *
     * @param string $value
     * @return bool
     */
    public function validate($value)
    {
        // Allow wildcards and a single L
        if ($value === '?' || $value === '*' || $value === 'L') {
            return true;
        }

        // If you only contain numbers and are within 1-31
        if ((bool) preg_match('/^\d{1,2}$/', $value) && ($value >= 1 && $value <= 31)) {
            return true;
        }

        // If you have a -, we will deal with each of your chunks
        if ((bool) preg_match('/-/', $value)) {
            // We cannot have a range within a list or vice versa
            if ((bool) preg_match('/,/', $value)) {
                return false;
            }

            $chunks = explode('-', $value);
            foreach ($chunks as $chunk) {
                if (!$this->validate($chunk)) {
                    return false;
                }
            }

            return true;
        }

        // If you have a comma, we will deal with each value
        if ((bool) preg_match('/,/', $value)) {
            // We cannot have a range within a list or vice versa
            if ((bool) preg_match('/-/', $value)) {
                return false;
            }

            $chunks = explode(',', $value);
            foreach ($chunks as $chunk) {
                if (!$this->validate($chunk)) {
                    return false;
                }
            }

            return true;
        }

        // If you contain a /, we'll deal with it
        if ((bool) preg_match('/\//', $value)) {
            $chunks = explode('/', $value);
            foreach ($chunks as $chunk) {
                if (!$this->validate($chunk)) {
                    return false;
                }
            }
            return true;
        }

        // If you end in W, make sure that it has a numeric in front of it
        if ((bool) preg_match('/^\d{1,2}W$/', $value)) {
            return true;
        }

        return false;
    }
}
<?php

namespace Cron;

use DateTime;


/**
 * Year field.  Allows: * , / -
 */
class YearField extends AbstractField
{
    public function isSatisfiedBy(DateTime $date, $value)
    {
        return $this->isSatisfied($date->format('Y'), $value);
    }

    public function increment(DateTime $date, $invert = false)
    {
        if ($invert) {
            $date->modify('-1 year');
            $date->setDate($date->format('Y'), 12, 31);
            $date->setTime(23, 59, 0);
        } else {
            $date->modify('+1 year');
            $date->setDate($date->format('Y'), 1, 1);
            $date->setTime(0, 0, 0);
        }

        return $this;
    }

    public function validate($value)
    {
        return (bool) preg_match('/^[\*,\/\-0-9]+$/', $value);
    }
}
<?php

namespace Cron;
use DateTime;

/**
 * CRON field interface
 */
interface FieldInterface
{
    /**
     * Check if the respective value of a DateTime field satisfies a CRON exp
     *
     * @param DateTime $date  DateTime object to check
     * @param string   $value CRON expression to test against
     *
     * @return bool Returns TRUE if satisfied, FALSE otherwise
     */
    public function isSatisfiedBy(DateTime $date, $value);

    /**
     * When a CRON expression is not satisfied, this method is used to increment
     * or decrement a DateTime object by the unit of the cron field
     *
     * @param DateTime $date   DateTime object to change
     * @param bool     $invert (optional) Set to TRUE to decrement
     *
     * @return FieldInterface
     */
    public function increment(DateTime $date, $invert = false);

    /**
     * Validates a CRON expression for a given field
     *
     * @param string $value CRON expression value to validate
     *
     * @return bool Returns TRUE if valid, FALSE otherwise
     */
    public function validate($value);
}
<?php

namespace Cron;

/**
 * Abstract CRON expression field
 */
abstract class AbstractField implements FieldInterface
{
    /**
     * Check to see if a field is satisfied by a value
     *
     * @param string $dateValue Date value to check
     * @param string $value     Value to test
     *
     * @return bool
     */
    public function isSatisfied($dateValue, $value)
    {
        if ($this->isIncrementsOfRanges($value)) {
            return $this->isInIncrementsOfRanges($dateValue, $value);
        } elseif ($this->isRange($value)) {
            return $this->isInRange($dateValue, $value);
        }

        return $value == '*' || $dateValue == $value;
    }

    /**
     * Check if a value is a range
     *
     * @param string $value Value to test
     *
     * @return bool
     */
    public function isRange($value)
    {
        return strpos($value, '-') !== false;
    }

    /**
     * Check if a value is an increments of ranges
     *
     * @param string $value Value to test
     *
     * @return bool
     */
    public function isIncrementsOfRanges($value)
    {
        return strpos($value, '/') !== false;
    }

    /**
     * Test if a value is within a range
     *
     * @param string $dateValue Set date value
     * @param string $value     Value to test
     *
     * @return bool
     */
    public function isInRange($dateValue, $value)
    {
        $parts = array_map('trim', explode('-', $value, 2));

        return $dateValue >= $parts[0] && $dateValue <= $parts[1];
    }

    /**
     * Test if a value is within an increments of ranges (offset[-to]/step size)
     *
     * @param string $dateValue Set date value
     * @param string $value     Value to test
     *
     * @return bool
     */
    public function isInIncrementsOfRanges($dateValue, $value)
    {
        $parts = array_map('trim', explode('/', $value, 2));
        $stepSize = isset($parts[1]) ? (int) $parts[1] : 0;

        if ($stepSize === 0) {
            return false;
        }

        if (($parts[0] == '*' || $parts[0] === '0')) {
            return (int) $dateValue % $stepSize == 0;
        }

        $range = explode('-', $parts[0], 2);
        $offset = $range[0];
        $to = isset($range[1]) ? $range[1] : $dateValue;
        // Ensure that the date value is within the range
        if ($dateValue < $offset || $dateValue > $to) {
            return false;
        }

        if ($dateValue > $offset && 0 === $stepSize) {
          return false;
        }

        for ($i = $offset; $i <= $to; $i+= $stepSize) {
            if ($i == $dateValue) {
                return true;
            }
        }

        return false;
    }

    /**
     * Returns a range of values for the given cron expression
     *
     * @param string $expression The expression to evaluate
     * @param int $max           Maximum offset for range
     *
     * @return array
     */
    public function getRangeForExpression($expression, $max)
    {
        $values = array();

        if ($this->isRange($expression) || $this->isIncrementsOfRanges($expression)) {
            if (!$this->isIncrementsOfRanges($expression)) {
                list ($offset, $to) = explode('-', $expression);
                $stepSize = 1;
            }
            else {
                $range = array_map('trim', explode('/', $expression, 2));
                $stepSize = isset($range[1]) ? $range[1] : 0;
                $range = $range[0];
                $range = explode('-', $range, 2);
                $offset = $range[0];
                $to = isset($range[1]) ? $range[1] : $max;
            }
            $offset = $offset == '*' ? 0 : $offset;
            for ($i = $offset; $i <= $to; $i += $stepSize) {
                $values[] = $i;
            }
            sort($values);
        }
        else {
            $values = array($expression);
        }

        return $values;
    }

}
<?php

namespace Cron;

use DateTime;


/**
 * Minutes field.  Allows: * , / -
 */
class MinutesField extends AbstractField
{
    public function isSatisfiedBy(DateTime $date, $value)
    {
        return $this->isSatisfied($date->format('i'), $value);
    }

    public function increment(DateTime $date, $invert = false, $parts = null)
    {
        if (is_null($parts)) {
            if ($invert) {
                $date->modify('-1 minute');
            } else {
                $date->modify('+1 minute');
            }
            return $this;
        }

        $parts = strpos($parts, ',') !== false ? explode(',', $parts) : array($parts);
        $minutes = array();
        foreach ($parts as $part) {
            $minutes = array_merge($minutes, $this->getRangeForExpression($part, 59));
        }

        $current_minute = $date->format('i');
        $position = $invert ? count($minutes) - 1 : 0;
        if (count($minutes) > 1) {
            for ($i = 0; $i < count($minutes) - 1; $i++) {
                if ((!$invert && $current_minute >= $minutes[$i] && $current_minute < $minutes[$i + 1]) ||
                    ($invert && $current_minute > $minutes[$i] && $current_minute <= $minutes[$i + 1])) {
                    $position = $invert ? $i : $i + 1;
                    break;
                }
            }
        }

        if ((!$invert && $current_minute >= $minutes[$position]) || ($invert && $current_minute <= $minutes[$position])) {
            $date->modify(($invert ? '-' : '+') . '1 hour');
            $date->setTime($date->format('H'), $invert ? 59 : 0);
        }
        else {
            $date->setTime($date->format('H'), $minutes[$position]);
        }

        return $this;
    }

    public function validate($value)
    {
        return (bool) preg_match('/^[\*,\/\-0-9]+$/', $value);
    }
}
<?php

namespace Cron;

use DateTime;

/**
 * Month field.  Allows: * , / -
 */
class MonthField extends AbstractField
{
    public function isSatisfiedBy(DateTime $date, $value)
    {
        // Convert text month values to integers
        $value = str_ireplace(
            array(
                'JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN',
                'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'
            ),
            range(1, 12),
            $value
        );

        return $this->isSatisfied($date->format('m'), $value);
    }

    public function increment(DateTime $date, $invert = false)
    {
        if ($invert) {
            $date->modify('last day of previous month');
            $date->setTime(23, 59);
        } else {
            $date->modify('first day of next month');
            $date->setTime(0, 0);
        }

        return $this;
    }

    public function validate($value)
    {
        return (bool) preg_match('/^[\*,\/\-0-9A-Z]+$/', $value);
    }
}
<?php

namespace Cron;
use DateTime;
use DateTimeZone;


/**
 * Hours field.  Allows: * , / -
 */
class HoursField extends AbstractField
{
    public function isSatisfiedBy(DateTime $date, $value)
    {
        return $this->isSatisfied($date->format('H'), $value);
    }

    public function increment(DateTime $date, $invert = false, $parts = null)
    {
        // Change timezone to UTC temporarily. This will
        // allow us to go back or forwards and hour even
        // if DST will be changed between the hours.
        if (is_null($parts) || $parts == '*') {
            $timezone = $date->getTimezone();
            $date->setTimezone(new DateTimeZone('UTC'));
            if ($invert) {
                $date->modify('-1 hour');
            } else {
                $date->modify('+1 hour');
            }
            $date->setTimezone($timezone);

            $date->setTime($date->format('H'), $invert ? 59 : 0);
            return $this;
        }

        $parts = strpos($parts, ',') !== false ? explode(',', $parts) : array($parts);
        $hours = array();
        foreach ($parts as $part) {
            $hours = array_merge($hours, $this->getRangeForExpression($part, 23));
        }

        $current_hour = $date->format('H');
        $position = $invert ? count($hours) - 1 : 0;
        if (count($hours) > 1) {
            for ($i = 0; $i < count($hours) - 1; $i++) {
                if ((!$invert && $current_hour >= $hours[$i] && $current_hour < $hours[$i + 1]) ||
                    ($invert && $current_hour > $hours[$i] && $current_hour <= $hours[$i + 1])) {
                    $position = $invert ? $i : $i + 1;
                    break;
                }
            }
        }

        $hour = $hours[$position];
        if ((!$invert && $date->format('H') >= $hour) || ($invert && $date->format('H') <= $hour)) {
            $date->modify(($invert ? '-' : '+') . '1 day');
            $date->setTime($invert ? 23 : 0, $invert ? 59 : 0);
        }
        else {
            $date->setTime($hour, $invert ? 59 : 0);
        }

        return $this;
    }

    public function validate($value)
    {
        return (bool) preg_match('/^[\*,\/\-0-9]+$/', $value);
    }
}
<?php

namespace Cron;

use DateTime;
use DateTimeImmutable;
use DateTimeZone;
use Exception;
use InvalidArgumentException;
use RuntimeException;

/**
 * CRON expression parser that can determine whether or not a CRON expression is
 * due to run, the next run date and previous run date of a CRON expression.
 * The determinations made by this class are accurate if checked run once per
 * minute (seconds are dropped from date time comparisons).
 *
 * Schedule parts must map to:
 * minute [0-59], hour [0-23], day of month, month [1-12|JAN-DEC], day of week
 * [1-7|MON-SUN], and an optional year.
 *
 * @link http://en.wikipedia.org/wiki/Cron
 */
class CronExpression
{
    const MINUTE = 0;
    const HOUR = 1;
    const DAY = 2;
    const MONTH = 3;
    const WEEKDAY = 4;
    const YEAR = 5;

    /**
     * @var array CRON expression parts
     */
    private $cronParts;

    /**
     * @var FieldFactory CRON field factory
     */
    private $fieldFactory;

    /**
     * @var int Max iteration count when searching for next run date
     */
    private $maxIterationCount = 1000;

    /**
     * @var array Order in which to test of cron parts
     */
    private static $order = array(self::YEAR, self::MONTH, self::DAY, self::WEEKDAY, self::HOUR, self::MINUTE);

    /**
     * Factory method to create a new CronExpression.
     *
     * @param string $expression The CRON expression to create.  There are
     *                           several special predefined values which can be used to substitute the
     *                           CRON expression:
     *
     *      `@yearly`, `@annually` - Run once a year, midnight, Jan. 1 - 0 0 1 1 *
     *      `@monthly` - Run once a month, midnight, first of month - 0 0 1 * *
     *      `@weekly` - Run once a week, midnight on Sun - 0 0 * * 0
     *      `@daily` - Run once a day, midnight - 0 0 * * *
     *      `@hourly` - Run once an hour, first minute - 0 * * * *
     * @param FieldFactory $fieldFactory Field factory to use
     *
     * @return CronExpression
     */
    public static function factory($expression, FieldFactory $fieldFactory = null)
    {
        $mappings = array(
            '@yearly' => '0 0 1 1 *',
            '@annually' => '0 0 1 1 *',
            '@monthly' => '0 0 1 * *',
            '@weekly' => '0 0 * * 0',
            '@daily' => '0 0 * * *',
            '@hourly' => '0 * * * *'
        );

        if (isset($mappings[$expression])) {
            $expression = $mappings[$expression];
        }

        return new static($expression, $fieldFactory ?: new FieldFactory());
    }

    /**
     * Validate a CronExpression.
     *
     * @param string $expression The CRON expression to validate.
     *
     * @return bool True if a valid CRON expression was passed. False if not.
     * @see \Cron\CronExpression::factory
     */
    public static function isValidExpression($expression)
    {
        try {
            self::factory($expression);
        } catch (InvalidArgumentException $e) {
            return false;
        }

        return true;
    }

    /**
     * Parse a CRON expression
     *
     * @param string       $expression   CRON expression (e.g. '8 * * * *')
     * @param FieldFactory $fieldFactory Factory to create cron fields
     */
    public function __construct($expression, FieldFactory $fieldFactory)
    {
        $this->fieldFactory = $fieldFactory;
        $this->setExpression($expression);
    }

    /**
     * Set or change the CRON expression
     *
     * @param string $value CRON expression (e.g. 8 * * * *)
     *
     * @return CronExpression
     * @throws \InvalidArgumentException if not a valid CRON expression
     */
    public function setExpression($value)
    {
        $this->cronParts = preg_split('/\s/', $value, -1, PREG_SPLIT_NO_EMPTY);
        if (count($this->cronParts) < 5) {
            throw new InvalidArgumentException(
                $value . ' is not a valid CRON expression'
            );
        }

        foreach ($this->cronParts as $position => $part) {
            $this->setPart($position, $part);
        }

        return $this;
    }

    /**
     * Set part of the CRON expression
     *
     * @param int    $position The position of the CRON expression to set
     * @param string $value    The value to set
     *
     * @return CronExpression
     * @throws \InvalidArgumentException if the value is not valid for the part
     */
    public function setPart($position, $value)
    {
        if (!$this->fieldFactory->getField($position)->validate($value)) {
            throw new InvalidArgumentException(
                'Invalid CRON field value ' . $value . ' at position ' . $position
            );
        }

        $this->cronParts[$position] = $value;

        return $this;
    }

    /**
     * Set max iteration count for searching next run dates
     *
     * @param int $maxIterationCount Max iteration count when searching for next run date
     *
     * @return CronExpression
     */
    public function setMaxIterationCount($maxIterationCount)
    {
        $this->maxIterationCount = $maxIterationCount;
        
        return $this;
    }

    /**
     * Get a next run date relative to the current date or a specific date
     *
     * @param string|\DateTime $currentTime      Relative calculation date
     * @param int              $nth              Number of matches to skip before returning a
     *                                           matching next run date.  0, the default, will return the current
     *                                           date and time if the next run date falls on the current date and
     *                                           time.  Setting this value to 1 will skip the first match and go to
     *                                           the second match.  Setting this value to 2 will skip the first 2
     *                                           matches and so on.
     * @param bool             $allowCurrentDate Set to TRUE to return the current date if
     *                                           it matches the cron expression.
     *
     * @return \DateTime
     * @throws \RuntimeException on too many iterations
     */
    public function getNextRunDate($currentTime = 'now', $nth = 0, $allowCurrentDate = false)
    {
        return $this->getRunDate($currentTime, $nth, false, $allowCurrentDate);
    }

    /**
     * Get a previous run date relative to the current date or a specific date
     *
     * @param string|\DateTime $currentTime      Relative calculation date
     * @param int              $nth              Number of matches to skip before returning
     * @param bool             $allowCurrentDate Set to TRUE to return the
     *                                           current date if it matches the cron expression
     *
     * @return \DateTime
     * @throws \RuntimeException on too many iterations
     * @see \Cron\CronExpression::getNextRunDate
     */
    public function getPreviousRunDate($currentTime = 'now', $nth = 0, $allowCurrentDate = false)
    {
        return $this->getRunDate($currentTime, $nth, true, $allowCurrentDate);
    }

    /**
     * Get multiple run dates starting at the current date or a specific date
     *
     * @param int              $total            Set the total number of dates to calculate
     * @param string|\DateTime $currentTime      Relative calculation date
     * @param bool             $invert           Set to TRUE to retrieve previous dates
     * @param bool             $allowCurrentDate Set to TRUE to return the
     *                                           current date if it matches the cron expression
     *
     * @return array Returns an array of run dates
     */
    public function getMultipleRunDates($total, $currentTime = 'now', $invert = false, $allowCurrentDate = false)
    {
        $matches = array();
        for ($i = 0; $i < max(0, $total); $i++) {
            try {
                $matches[] = $this->getRunDate($currentTime, $i, $invert, $allowCurrentDate);
            } catch (RuntimeException $e) {
                break;
            }
        }

        return $matches;
    }

    /**
     * Get all or part of the CRON expression
     *
     * @param string $part Specify the part to retrieve or NULL to get the full
     *                     cron schedule string.
     *
     * @return string|null Returns the CRON expression, a part of the
     *                     CRON expression, or NULL if the part was specified but not found
     */
    public function getExpression($part = null)
    {
        if (null === $part) {
            return implode(' ', $this->cronParts);
        } elseif (array_key_exists($part, $this->cronParts)) {
            return $this->cronParts[$part];
        }

        return null;
    }

    /**
     * Helper method to output the full expression.
     *
     * @return string Full CRON expression
     */
    public function __toString()
    {
        return $this->getExpression();
    }

    /**
     * Determine if the cron is due to run based on the current date or a
     * specific date.  This method assumes that the current number of
     * seconds are irrelevant, and should be called once per minute.
     *
     * @param string|\DateTime $currentTime Relative calculation date
     *
     * @return bool Returns TRUE if the cron is due to run or FALSE if not
     */
    public function isDue($currentTime = 'now')
    {
        if ('now' === $currentTime) {
            $currentDate = date('Y-m-d H:i');
            $currentTime = strtotime($currentDate);
        } elseif ($currentTime instanceof DateTime) {
            $currentDate = clone $currentTime;
            // Ensure time in 'current' timezone is used
            $currentDate->setTimezone(new DateTimeZone(date_default_timezone_get()));
            $currentDate = $currentDate->format('Y-m-d H:i');
            $currentTime = strtotime($currentDate);
        } elseif ($currentTime instanceof DateTimeImmutable) {
            $currentDate = DateTime::createFromFormat('U', $currentTime->format('U'));
            $currentDate->setTimezone(new DateTimeZone(date_default_timezone_get()));
            $currentDate = $currentDate->format('Y-m-d H:i');
            $currentTime = strtotime($currentDate);
        } else {
            $currentTime = new DateTime($currentTime);
            $currentTime->setTime($currentTime->format('H'), $currentTime->format('i'), 0);
            $currentDate = $currentTime->format('Y-m-d H:i');
            $currentTime = $currentTime->getTimeStamp();
        }

        try {
            return $this->getNextRunDate($currentDate, 0, true)->getTimestamp() == $currentTime;
        } catch (Exception $e) {
            return false;
        }
    }

    /**
     * Get the next or previous run date of the expression relative to a date
     *
     * @param string|\DateTime $currentTime      Relative calculation date
     * @param int              $nth              Number of matches to skip before returning
     * @param bool             $invert           Set to TRUE to go backwards in time
     * @param bool             $allowCurrentDate Set to TRUE to return the
     *                                           current date if it matches the cron expression
     *
     * @return \DateTime
     * @throws \RuntimeException on too many iterations
     */
    protected function getRunDate($currentTime = null, $nth = 0, $invert = false, $allowCurrentDate = false)
    {
        if ($currentTime instanceof DateTime) {
            $currentDate = clone $currentTime;
        } elseif ($currentTime instanceof DateTimeImmutable) {
            $currentDate = DateTime::createFromFormat('U', $currentTime->format('U'));
            $currentDate->setTimezone($currentTime->getTimezone());
        } else {
            $currentDate = new DateTime($currentTime ?: 'now');
            $currentDate->setTimezone(new DateTimeZone(date_default_timezone_get()));
        }

        $currentDate->setTime($currentDate->format('H'), $currentDate->format('i'), 0);
        $nextRun = clone $currentDate;
        $nth = (int) $nth;

        // We don't have to satisfy * or null fields
        $parts = array();
        $fields = array();
        foreach (self::$order as $position) {
            $part = $this->getExpression($position);
            if (null === $part || '*' === $part) {
                continue;
            }
            $parts[$position] = $part;
            $fields[$position] = $this->fieldFactory->getField($position);
        }

        // Set a hard limit to bail on an impossible date
        for ($i = 0; $i < $this->maxIterationCount; $i++) {

            foreach ($parts as $position => $part) {
                $satisfied = false;
                // Get the field object used to validate this part
                $field = $fields[$position];
                // Check if this is singular or a list
                if (strpos($part, ',') === false) {
                    $satisfied = $field->isSatisfiedBy($nextRun, $part);
                } else {
                    foreach (array_map('trim', explode(',', $part)) as $listPart) {
                        if ($field->isSatisfiedBy($nextRun, $listPart)) {
                            $satisfied = true;
                            break;
                        }
                    }
                }

                // If the field is not satisfied, then start over
                if (!$satisfied) {
                    $field->increment($nextRun, $invert, $part);
                    continue 2;
                }
            }

            // Skip this match if needed
            if ((!$allowCurrentDate && $nextRun == $currentDate) || --$nth > -1) {
                $this->fieldFactory->getField(0)->increment($nextRun, $invert, isset($parts[0]) ? $parts[0] : null);
                continue;
            }

            return $nextRun;
        }

        // @codeCoverageIgnoreStart
        throw new RuntimeException('Impossible CRON expression');
        // @codeCoverageIgnoreEnd
    }
}
<?php

namespace Cron;

use DateTime;
use InvalidArgumentException;


/**
 * Day of week field.  Allows: * / , - ? L #
 *
 * Days of the week can be represented as a number 0-7 (0|7 = Sunday)
 * or as a three letter string: SUN, MON, TUE, WED, THU, FRI, SAT.
 *
 * 'L' stands for "last". It allows you to specify constructs such as
 * "the last Friday" of a given month.
 *
 * '#' is allowed for the day-of-week field, and must be followed by a
 * number between one and five. It allows you to specify constructs such as
 * "the second Friday" of a given month.
 */
class DayOfWeekField extends AbstractField
{
    public function isSatisfiedBy(DateTime $date, $value)
    {
        if ($value == '?') {
            return true;
        }

        // Convert text day of the week values to integers
        $value = $this->convertLiterals($value);

        $currentYear = $date->format('Y');
        $currentMonth = $date->format('m');
        $lastDayOfMonth = $date->format('t');

        // Find out if this is the last specific weekday of the month
        if (strpos($value, 'L')) {
            $weekday = str_replace('7', '0', substr($value, 0, strpos($value, 'L')));
            $tdate = clone $date;
            $tdate->setDate($currentYear, $currentMonth, $lastDayOfMonth);
            while ($tdate->format('w') != $weekday) {
                $tdateClone = new DateTime();
                $tdate = $tdateClone
                    ->setTimezone($tdate->getTimezone())
                    ->setDate($currentYear, $currentMonth, --$lastDayOfMonth);
            }

            return $date->format('j') == $lastDayOfMonth;
        }

        // Handle # hash tokens
        if (strpos($value, '#')) {
            list($weekday, $nth) = explode('#', $value);

            // 0 and 7 are both Sunday, however 7 matches date('N') format ISO-8601
            if ($weekday === '0') {
                $weekday = 7;
            }

            // Validate the hash fields
            if ($weekday < 0 || $weekday > 7) {
                throw new InvalidArgumentException("Weekday must be a value between 0 and 7. {$weekday} given");
            }
            if ($nth > 5) {
                throw new InvalidArgumentException('There are never more than 5 of a given weekday in a month');
            }
            // The current weekday must match the targeted weekday to proceed
            if ($date->format('N') != $weekday) {
                return false;
            }

            $tdate = clone $date;
            $tdate->setDate($currentYear, $currentMonth, 1);
            $dayCount = 0;
            $currentDay = 1;
            while ($currentDay < $lastDayOfMonth + 1) {
                if ($tdate->format('N') == $weekday) {
                    if (++$dayCount >= $nth) {
                        break;
                    }
                }
                $tdate->setDate($currentYear, $currentMonth, ++$currentDay);
            }

            return $date->format('j') == $currentDay;
        }

        // Handle day of the week values
        if (strpos($value, '-')) {
            $parts = explode('-', $value);
            if ($parts[0] == '7') {
                $parts[0] = '0';
            } elseif ($parts[1] == '0') {
                $parts[1] = '7';
            }
            $value = implode('-', $parts);
        }

        // Test to see which Sunday to use -- 0 == 7 == Sunday
        $format = in_array(7, str_split($value)) ? 'N' : 'w';
        $fieldValue = $date->format($format);

        return $this->isSatisfied($fieldValue, $value);
    }

    public function increment(DateTime $date, $invert = false)
    {
        if ($invert) {
            $date->modify('-1 day');
            $date->setTime(23, 59, 0);
        } else {
            $date->modify('+1 day');
            $date->setTime(0, 0, 0);
        }

        return $this;
    }

    public function validate($value)
    {
        $value = $this->convertLiterals($value);

        foreach (explode(',', $value) as $expr) {
            if (!preg_match('/^(\*|[0-7](L?|#[1-5]))([\/\,\-][0-7]+)*$/', $expr)) {
                return false;
            }
        }

        return true;
    }

    private function convertLiterals($string)
    {
        return str_ireplace(
            array('SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT'),
            range(0, 6),
            $string
        );
    }
}
{
    "name": "webmozart/assert",
    "description": "Assertions to validate method input/output with nice error messages.",
    "keywords": [
        "assert",
        "check",
        "validate"
    ],
    "license": "MIT",
    "authors": [
        {
            "name": "Bernhard Schussek",
            "email": "bschussek@gmail.com"
        }
    ],
    "require": {
        "php": "^5.3.3 || ^7.0",
        "symfony/polyfill-ctype": "^1.8"
    },
    "require-dev": {
        "phpunit/phpunit": "^4.6",
        "sebastian/version": "^1.0.1"
    },
    "extra": {
        "branch-alias": {
            "dev-master": "1.3-dev"
        }
    },
    "autoload": {
        "psr-4": {
            "Webmozart\\Assert\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Webmozart\\Assert\\Tests\\": "tests/"
        }
    }
}
<?php

/*
 * This file is part of the webmozart/assert package.
 *
 * (c) Bernhard Schussek <bschussek@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Webmozart\Assert;

use ArrayAccess;
use BadMethodCallException;
use Closure;
use Countable;
use Exception;
use InvalidArgumentException;
use Throwable;
use Traversable;

/**
 * Efficient assertions to validate the input/output of your methods.
 *
 * @method static void nullOrString($value, $message = '')
 * @method static void nullOrStringNotEmpty($value, $message = '')
 * @method static void nullOrInteger($value, $message = '')
 * @method static void nullOrIntegerish($value, $message = '')
 * @method static void nullOrFloat($value, $message = '')
 * @method static void nullOrNumeric($value, $message = '')
 * @method static void nullOrNatural($value, $message = '')
 * @method static void nullOrBoolean($value, $message = '')
 * @method static void nullOrScalar($value, $message = '')
 * @method static void nullOrObject($value, $message = '')
 * @method static void nullOrResource($value, $type = null, $message = '')
 * @method static void nullOrIsCallable($value, $message = '')
 * @method static void nullOrIsArray($value, $message = '')
 * @method static void nullOrIsTraversable($value, $message = '')
 * @method static void nullOrIsArrayAccessible($value, $message = '')
 * @method static void nullOrIsCountable($value, $message = '')
 * @method static void nullOrIsIterable($value, $message = '')
 * @method static void nullOrIsInstanceOf($value, $class, $message = '')
 * @method static void nullOrNotInstanceOf($value, $class, $message = '')
 * @method static void nullOrIsInstanceOfAny($value, $classes, $message = '')
 * @method static void nullOrIsEmpty($value, $message = '')
 * @method static void nullOrNotEmpty($value, $message = '')
 * @method static void nullOrTrue($value, $message = '')
 * @method static void nullOrFalse($value, $message = '')
 * @method static void nullOrIp($value, $message = '')
 * @method static void nullOrIpv4($value, $message = '')
 * @method static void nullOrIpv6($value, $message = '')
 * @method static void nullOrEq($value, $value2, $message = '')
 * @method static void nullOrNotEq($value,$value2,  $message = '')
 * @method static void nullOrSame($value, $value2, $message = '')
 * @method static void nullOrNotSame($value, $value2, $message = '')
 * @method static void nullOrGreaterThan($value, $value2, $message = '')
 * @method static void nullOrGreaterThanEq($value, $value2, $message = '')
 * @method static void nullOrLessThan($value, $value2, $message = '')
 * @method static void nullOrLessThanEq($value, $value2, $message = '')
 * @method static void nullOrRange($value, $min, $max, $message = '')
 * @method static void nullOrOneOf($value, $values, $message = '')
 * @method static void nullOrContains($value, $subString, $message = '')
 * @method static void nullOrNotContains($value, $subString, $message = '')
 * @method static void nullOrNotWhitespaceOnly($value, $message = '')
 * @method static void nullOrStartsWith($value, $prefix, $message = '')
 * @method static void nullOrStartsWithLetter($value, $message = '')
 * @method static void nullOrEndsWith($value, $suffix, $message = '')
 * @method static void nullOrRegex($value, $pattern, $message = '')
 * @method static void nullOrNotRegex($value, $pattern, $message = '')
 * @method static void nullOrAlpha($value, $message = '')
 * @method static void nullOrDigits($value, $message = '')
 * @method static void nullOrAlnum($value, $message = '')
 * @method static void nullOrLower($value, $message = '')
 * @method static void nullOrUpper($value, $message = '')
 * @method static void nullOrLength($value, $length, $message = '')
 * @method static void nullOrMinLength($value, $min, $message = '')
 * @method static void nullOrMaxLength($value, $max, $message = '')
 * @method static void nullOrLengthBetween($value, $min, $max, $message = '')
 * @method static void nullOrFileExists($value, $message = '')
 * @method static void nullOrFile($value, $message = '')
 * @method static void nullOrDirectory($value, $message = '')
 * @method static void nullOrReadable($value, $message = '')
 * @method static void nullOrWritable($value, $message = '')
 * @method static void nullOrClassExists($value, $message = '')
 * @method static void nullOrSubclassOf($value, $class, $message = '')
 * @method static void nullOrInterfaceExists($value, $message = '')
 * @method static void nullOrImplementsInterface($value, $interface, $message = '')
 * @method static void nullOrPropertyExists($value, $property, $message = '')
 * @method static void nullOrPropertyNotExists($value, $property, $message = '')
 * @method static void nullOrMethodExists($value, $method, $message = '')
 * @method static void nullOrMethodNotExists($value, $method, $message = '')
 * @method static void nullOrKeyExists($value, $key, $message = '')
 * @method static void nullOrKeyNotExists($value, $key, $message = '')
 * @method static void nullOrCount($value, $key, $message = '')
 * @method static void nullOrMinCount($value, $min, $message = '')
 * @method static void nullOrMaxCount($value, $max, $message = '')
 * @method static void nullOrIsList($value, $message = '')
 * @method static void nullOrIsMap($value, $message = '')
 * @method static void nullOrCountBetween($value, $min, $max, $message = '')
 * @method static void nullOrUuid($values, $message = '')
 * @method static void nullOrThrows($expression, $class = 'Exception', $message = '')
 * @method static void allString($values, $message = '')
 * @method static void allStringNotEmpty($values, $message = '')
 * @method static void allInteger($values, $message = '')
 * @method static void allIntegerish($values, $message = '')
 * @method static void allFloat($values, $message = '')
 * @method static void allNumeric($values, $message = '')
 * @method static void allNatural($values, $message = '')
 * @method static void allBoolean($values, $message = '')
 * @method static void allScalar($values, $message = '')
 * @method static void allObject($values, $message = '')
 * @method static void allResource($values, $type = null, $message = '')
 * @method static void allIsCallable($values, $message = '')
 * @method static void allIsArray($values, $message = '')
 * @method static void allIsTraversable($values, $message = '')
 * @method static void allIsArrayAccessible($values, $message = '')
 * @method static void allIsCountable($values, $message = '')
 * @method static void allIsIterable($values, $message = '')
 * @method static void allIsInstanceOf($values, $class, $message = '')
 * @method static void allNotInstanceOf($values, $class, $message = '')
 * @method static void allIsInstanceOfAny($values, $classes, $message = '')
 * @method static void allNull($values, $message = '')
 * @method static void allNotNull($values, $message = '')
 * @method static void allIsEmpty($values, $message = '')
 * @method static void allNotEmpty($values, $message = '')
 * @method static void allTrue($values, $message = '')
 * @method static void allFalse($values, $message = '')
 * @method static void allIp($values, $message = '')
 * @method static void allIpv4($values, $message = '')
 * @method static void allIpv6($values, $message = '')
 * @method static void allEq($values, $value2, $message = '')
 * @method static void allNotEq($values,$value2,  $message = '')
 * @method static void allSame($values, $value2, $message = '')
 * @method static void allNotSame($values, $value2, $message = '')
 * @method static void allGreaterThan($values, $value2, $message = '')
 * @method static void allGreaterThanEq($values, $value2, $message = '')
 * @method static void allLessThan($values, $value2, $message = '')
 * @method static void allLessThanEq($values, $value2, $message = '')
 * @method static void allRange($values, $min, $max, $message = '')
 * @method static void allOneOf($values, $values, $message = '')
 * @method static void allContains($values, $subString, $message = '')
 * @method static void allNotContains($values, $subString, $message = '')
 * @method static void allNotWhitespaceOnly($values, $message = '')
 * @method static void allStartsWith($values, $prefix, $message = '')
 * @method static void allStartsWithLetter($values, $message = '')
 * @method static void allEndsWith($values, $suffix, $message = '')
 * @method static void allRegex($values, $pattern, $message = '')
 * @method static void allNotRegex($values, $pattern, $message = '')
 * @method static void allAlpha($values, $message = '')
 * @method static void allDigits($values, $message = '')
 * @method static void allAlnum($values, $message = '')
 * @method static void allLower($values, $message = '')
 * @method static void allUpper($values, $message = '')
 * @method static void allLength($values, $length, $message = '')
 * @method static void allMinLength($values, $min, $message = '')
 * @method static void allMaxLength($values, $max, $message = '')
 * @method static void allLengthBetween($values, $min, $max, $message = '')
 * @method static void allFileExists($values, $message = '')
 * @method static void allFile($values, $message = '')
 * @method static void allDirectory($values, $message = '')
 * @method static void allReadable($values, $message = '')
 * @method static void allWritable($values, $message = '')
 * @method static void allClassExists($values, $message = '')
 * @method static void allSubclassOf($values, $class, $message = '')
 * @method static void allInterfaceExists($values, $message = '')
 * @method static void allImplementsInterface($values, $interface, $message = '')
 * @method static void allPropertyExists($values, $property, $message = '')
 * @method static void allPropertyNotExists($values, $property, $message = '')
 * @method static void allMethodExists($values, $method, $message = '')
 * @method static void allMethodNotExists($values, $method, $message = '')
 * @method static void allKeyExists($values, $key, $message = '')
 * @method static void allKeyNotExists($values, $key, $message = '')
 * @method static void allCount($values, $key, $message = '')
 * @method static void allMinCount($values, $min, $message = '')
 * @method static void allMaxCount($values, $max, $message = '')
 * @method static void allCountBetween($values, $min, $max, $message = '')
 * @method static void allIsList($values, $message = '')
 * @method static void allIsMap($values, $message = '')
 * @method static void allUuid($values, $message = '')
 * @method static void allThrows($expressions, $class = 'Exception', $message = '')
 *
 * @since  1.0
 *
 * @author Bernhard Schussek <bschussek@gmail.com>
 */
class Assert
{
    public static function string($value, $message = '')
    {
        if (!is_string($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a string. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function stringNotEmpty($value, $message = '')
    {
        static::string($value, $message);
        static::notEq($value, '', $message);
    }

    public static function integer($value, $message = '')
    {
        if (!is_int($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an integer. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function integerish($value, $message = '')
    {
        if (!is_numeric($value) || $value != (int) $value) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an integerish value. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function float($value, $message = '')
    {
        if (!is_float($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a float. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function numeric($value, $message = '')
    {
        if (!is_numeric($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a numeric. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function natural($value, $message = '')
    {
        if (!is_int($value) || $value < 0) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a non-negative integer. Got %s',
                static::valueToString($value)
            ));
        }
    }

    public static function boolean($value, $message = '')
    {
        if (!is_bool($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a boolean. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function scalar($value, $message = '')
    {
        if (!is_scalar($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a scalar. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function object($value, $message = '')
    {
        if (!is_object($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an object. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function resource($value, $type = null, $message = '')
    {
        if (!is_resource($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a resource. Got: %s',
                static::typeToString($value)
            ));
        }

        if ($type && $type !== get_resource_type($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a resource of type %2$s. Got: %s',
                static::typeToString($value),
                $type
            ));
        }
    }

    public static function isCallable($value, $message = '')
    {
        if (!is_callable($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a callable. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function isArray($value, $message = '')
    {
        if (!is_array($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an array. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function isTraversable($value, $message = '')
    {
        @trigger_error(
            sprintf(
                'The "%s" assertion is deprecated. You should stop using it, as it will soon be removed in 2.0 version. Use "isIterable" or "isInstanceOf" instead.',
                __METHOD__
            ),
            E_USER_DEPRECATED
        );

        if (!is_array($value) && !($value instanceof Traversable)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a traversable. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function isArrayAccessible($value, $message = '')
    {
        if (!is_array($value) && !($value instanceof ArrayAccess)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an array accessible. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function isCountable($value, $message = '')
    {
        if (!is_array($value) && !($value instanceof Countable)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a countable. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function isIterable($value, $message = '')
    {
        if (!is_array($value) && !($value instanceof Traversable)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an iterable. Got: %s',
                static::typeToString($value)
            ));
        }
    }

    public static function isInstanceOf($value, $class, $message = '')
    {
        if (!($value instanceof $class)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an instance of %2$s. Got: %s',
                static::typeToString($value),
                $class
            ));
        }
    }

    public static function notInstanceOf($value, $class, $message = '')
    {
        if ($value instanceof $class) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an instance other than %2$s. Got: %s',
                static::typeToString($value),
                $class
            ));
        }
    }

    public static function isInstanceOfAny($value, array $classes, $message = '')
    {
        foreach ($classes as $class) {
            if ($value instanceof $class) {
                return;
            }
        }

        static::reportInvalidArgument(sprintf(
            $message ?: 'Expected an instance of any of %2$s. Got: %s',
            static::typeToString($value),
            implode(', ', array_map(array('static', 'valueToString'), $classes))
        ));
    }

    public static function isEmpty($value, $message = '')
    {
        if (!empty($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an empty value. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function notEmpty($value, $message = '')
    {
        if (empty($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a non-empty value. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function null($value, $message = '')
    {
        if (null !== $value) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected null. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function notNull($value, $message = '')
    {
        if (null === $value) {
            static::reportInvalidArgument(
                $message ?: 'Expected a value other than null.'
            );
        }
    }

    public static function true($value, $message = '')
    {
        if (true !== $value) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to be true. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function false($value, $message = '')
    {
        if (false !== $value) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to be false. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function ip($value, $message = '')
    {
        if (false === filter_var($value, FILTER_VALIDATE_IP)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to be an IP. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function ipv4($value, $message = '')
    {
        if (false === filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to be an IPv4. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function ipv6($value, $message = '')
    {
        if (false === filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to be an IPv6. Got %s',
                static::valueToString($value)
            ));
        }
    }

    public static function eq($value, $value2, $message = '')
    {
        if ($value2 != $value) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value equal to %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($value2)
            ));
        }
    }

    public static function notEq($value, $value2, $message = '')
    {
        if ($value2 == $value) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a different value than %s.',
                static::valueToString($value2)
            ));
        }
    }

    public static function same($value, $value2, $message = '')
    {
        if ($value2 !== $value) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value identical to %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($value2)
            ));
        }
    }

    public static function notSame($value, $value2, $message = '')
    {
        if ($value2 === $value) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value not identical to %s.',
                static::valueToString($value2)
            ));
        }
    }

    public static function greaterThan($value, $limit, $message = '')
    {
        if ($value <= $limit) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value greater than %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($limit)
            ));
        }
    }

    public static function greaterThanEq($value, $limit, $message = '')
    {
        if ($value < $limit) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value greater than or equal to %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($limit)
            ));
        }
    }

    public static function lessThan($value, $limit, $message = '')
    {
        if ($value >= $limit) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value less than %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($limit)
            ));
        }
    }

    public static function lessThanEq($value, $limit, $message = '')
    {
        if ($value > $limit) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value less than or equal to %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($limit)
            ));
        }
    }

    public static function range($value, $min, $max, $message = '')
    {
        if ($value < $min || $value > $max) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value between %2$s and %3$s. Got: %s',
                static::valueToString($value),
                static::valueToString($min),
                static::valueToString($max)
            ));
        }
    }

    public static function oneOf($value, array $values, $message = '')
    {
        if (!in_array($value, $values, true)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected one of: %2$s. Got: %s',
                static::valueToString($value),
                implode(', ', array_map(array('static', 'valueToString'), $values))
            ));
        }
    }

    public static function contains($value, $subString, $message = '')
    {
        if (false === strpos($value, $subString)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($subString)
            ));
        }
    }

    public static function notContains($value, $subString, $message = '')
    {
        if (false !== strpos($value, $subString)) {
            static::reportInvalidArgument(sprintf(
                $message ?: '%2$s was not expected to be contained in a value. Got: %s',
                static::valueToString($value),
                static::valueToString($subString)
            ));
        }
    }

    public static function notWhitespaceOnly($value, $message = '')
    {
        if (preg_match('/^\s*$/', $value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a non-whitespace string. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function startsWith($value, $prefix, $message = '')
    {
        if (0 !== strpos($value, $prefix)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to start with %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($prefix)
            ));
        }
    }

    public static function startsWithLetter($value, $message = '')
    {
        $valid = isset($value[0]);

        if ($valid) {
            $locale = setlocale(LC_CTYPE, 0);
            setlocale(LC_CTYPE, 'C');
            $valid = ctype_alpha($value[0]);
            setlocale(LC_CTYPE, $locale);
        }

        if (!$valid) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to start with a letter. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function endsWith($value, $suffix, $message = '')
    {
        if ($suffix !== substr($value, -static::strlen($suffix))) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to end with %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($suffix)
            ));
        }
    }

    public static function regex($value, $pattern, $message = '')
    {
        if (!preg_match($pattern, $value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'The value %s does not match the expected pattern.',
                static::valueToString($value)
            ));
        }
    }

    public static function notRegex($value, $pattern, $message = '')
    {
        if (preg_match($pattern, $value, $matches, PREG_OFFSET_CAPTURE)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'The value %s matches the pattern %s (at offset %d).',
                static::valueToString($value),
                static::valueToString($pattern),
                $matches[0][1]
            ));
        }
    }

    public static function alpha($value, $message = '')
    {
        $locale = setlocale(LC_CTYPE, 0);
        setlocale(LC_CTYPE, 'C');
        $valid = !ctype_alpha($value);
        setlocale(LC_CTYPE, $locale);

        if ($valid) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain only letters. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function digits($value, $message = '')
    {
        $locale = setlocale(LC_CTYPE, 0);
        setlocale(LC_CTYPE, 'C');
        $valid = !ctype_digit($value);
        setlocale(LC_CTYPE, $locale);

        if ($valid) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain digits only. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function alnum($value, $message = '')
    {
        $locale = setlocale(LC_CTYPE, 0);
        setlocale(LC_CTYPE, 'C');
        $valid = !ctype_alnum($value);
        setlocale(LC_CTYPE, $locale);

        if ($valid) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain letters and digits only. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function lower($value, $message = '')
    {
        $locale = setlocale(LC_CTYPE, 0);
        setlocale(LC_CTYPE, 'C');
        $valid = !ctype_lower($value);
        setlocale(LC_CTYPE, $locale);

        if ($valid) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain lowercase characters only. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function upper($value, $message = '')
    {
        $locale = setlocale(LC_CTYPE, 0);
        setlocale(LC_CTYPE, 'C');
        $valid = !ctype_upper($value);
        setlocale(LC_CTYPE, $locale);

        if ($valid) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain uppercase characters only. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function length($value, $length, $message = '')
    {
        if ($length !== static::strlen($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain %2$s characters. Got: %s',
                static::valueToString($value),
                $length
            ));
        }
    }

    public static function minLength($value, $min, $message = '')
    {
        if (static::strlen($value) < $min) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain at least %2$s characters. Got: %s',
                static::valueToString($value),
                $min
            ));
        }
    }

    public static function maxLength($value, $max, $message = '')
    {
        if (static::strlen($value) > $max) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain at most %2$s characters. Got: %s',
                static::valueToString($value),
                $max
            ));
        }
    }

    public static function lengthBetween($value, $min, $max, $message = '')
    {
        $length = static::strlen($value);

        if ($length < $min || $length > $max) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a value to contain between %2$s and %3$s characters. Got: %s',
                static::valueToString($value),
                $min,
                $max
            ));
        }
    }

    public static function fileExists($value, $message = '')
    {
        static::string($value);

        if (!file_exists($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'The file %s does not exist.',
                static::valueToString($value)
            ));
        }
    }

    public static function file($value, $message = '')
    {
        static::fileExists($value, $message);

        if (!is_file($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'The path %s is not a file.',
                static::valueToString($value)
            ));
        }
    }

    public static function directory($value, $message = '')
    {
        static::fileExists($value, $message);

        if (!is_dir($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'The path %s is no directory.',
                static::valueToString($value)
            ));
        }
    }

    public static function readable($value, $message = '')
    {
        if (!is_readable($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'The path %s is not readable.',
                static::valueToString($value)
            ));
        }
    }

    public static function writable($value, $message = '')
    {
        if (!is_writable($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'The path %s is not writable.',
                static::valueToString($value)
            ));
        }
    }

    public static function classExists($value, $message = '')
    {
        if (!class_exists($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an existing class name. Got: %s',
                static::valueToString($value)
            ));
        }
    }

    public static function subclassOf($value, $class, $message = '')
    {
        if (!is_subclass_of($value, $class)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected a sub-class of %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($class)
            ));
        }
    }

    public static function interfaceExists($value, $message = '')
    {
        if (!interface_exists($value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an existing interface name. got %s',
                static::valueToString($value)
            ));
        }
    }

    public static function implementsInterface($value, $interface, $message = '')
    {
        if (!in_array($interface, class_implements($value))) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an implementation of %2$s. Got: %s',
                static::valueToString($value),
                static::valueToString($interface)
            ));
        }
    }

    public static function propertyExists($classOrObject, $property, $message = '')
    {
        if (!property_exists($classOrObject, $property)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected the property %s to exist.',
                static::valueToString($property)
            ));
        }
    }

    public static function propertyNotExists($classOrObject, $property, $message = '')
    {
        if (property_exists($classOrObject, $property)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected the property %s to not exist.',
                static::valueToString($property)
            ));
        }
    }

    public static function methodExists($classOrObject, $method, $message = '')
    {
        if (!method_exists($classOrObject, $method)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected the method %s to exist.',
                static::valueToString($method)
            ));
        }
    }

    public static function methodNotExists($classOrObject, $method, $message = '')
    {
        if (method_exists($classOrObject, $method)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected the method %s to not exist.',
                static::valueToString($method)
            ));
        }
    }

    public static function keyExists($array, $key, $message = '')
    {
        if (!(isset($array[$key]) || array_key_exists($key, $array))) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected the key %s to exist.',
                static::valueToString($key)
            ));
        }
    }

    public static function keyNotExists($array, $key, $message = '')
    {
        if (isset($array[$key]) || array_key_exists($key, $array)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected the key %s to not exist.',
                static::valueToString($key)
            ));
        }
    }

    public static function count($array, $number, $message = '')
    {
        static::eq(
            count($array),
            $number,
            $message ?: sprintf('Expected an array to contain %d elements. Got: %d.', $number, count($array))
        );
    }

    public static function minCount($array, $min, $message = '')
    {
        if (count($array) < $min) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an array to contain at least %2$d elements. Got: %d',
                count($array),
                $min
            ));
        }
    }

    public static function maxCount($array, $max, $message = '')
    {
        if (count($array) > $max) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an array to contain at most %2$d elements. Got: %d',
                count($array),
                $max
            ));
        }
    }

    public static function countBetween($array, $min, $max, $message = '')
    {
        $count = count($array);

        if ($count < $min || $count > $max) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Expected an array to contain between %2$d and %3$d elements. Got: %d',
                $count,
                $min,
                $max
            ));
        }
    }

    public static function isList($array, $message = '')
    {
        if (!is_array($array) || !$array || array_keys($array) !== range(0, count($array) - 1)) {
            static::reportInvalidArgument(
                $message ?: 'Expected list - non-associative array.'
            );
        }
    }

    public static function isMap($array, $message = '')
    {
        if (
            !is_array($array) ||
            !$array ||
            array_keys($array) !== array_filter(array_keys($array), function ($key) {
                return is_string($key);
            })
        ) {
            static::reportInvalidArgument(
                $message ?: 'Expected map - associative array with string keys.'
            );
        }
    }

    public static function uuid($value, $message = '')
    {
        $value = str_replace(array('urn:', 'uuid:', '{', '}'), '', $value);

        // The nil UUID is special form of UUID that is specified to have all
        // 128 bits set to zero.
        if ('00000000-0000-0000-0000-000000000000' === $value) {
            return;
        }

        if (!preg_match('/^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/', $value)) {
            static::reportInvalidArgument(sprintf(
                $message ?: 'Value %s is not a valid UUID.',
                static::valueToString($value)
            ));
        }
    }

    public static function throws(Closure $expression, $class = 'Exception', $message = '')
    {
        static::string($class);

        $actual = 'none';

        try {
            $expression();
        } catch (Exception $e) {
            $actual = get_class($e);
            if ($e instanceof $class) {
                return;
            }
        } catch (Throwable $e) {
            $actual = get_class($e);
            if ($e instanceof $class) {
                return;
            }
        }

        static::reportInvalidArgument($message ?: sprintf(
            'Expected to throw "%s", got "%s"',
            $class,
            $actual
        ));
    }

    public static function __callStatic($name, $arguments)
    {
        if ('nullOr' === substr($name, 0, 6)) {
            if (null !== $arguments[0]) {
                $method = lcfirst(substr($name, 6));
                call_user_func_array(array('static', $method), $arguments);
            }

            return;
        }

        if ('all' === substr($name, 0, 3)) {
            static::isIterable($arguments[0]);

            $method = lcfirst(substr($name, 3));
            $args = $arguments;

            foreach ($arguments[0] as $entry) {
                $args[0] = $entry;

                call_user_func_array(array('static', $method), $args);
            }

            return;
        }

        throw new BadMethodCallException('No such method: '.$name);
    }

    protected static function valueToString($value)
    {
        if (null === $value) {
            return 'null';
        }

        if (true === $value) {
            return 'true';
        }

        if (false === $value) {
            return 'false';
        }

        if (is_array($value)) {
            return 'array';
        }

        if (is_object($value)) {
            if (method_exists($value, '__toString')) {
                return get_class($value).': '.self::valueToString($value->__toString());
            }

            return get_class($value);
        }

        if (is_resource($value)) {
            return 'resource';
        }

        if (is_string($value)) {
            return '"'.$value.'"';
        }

        return (string) $value;
    }

    protected static function typeToString($value)
    {
        return is_object($value) ? get_class($value) : gettype($value);
    }

    protected static function strlen($value)
    {
        if (!function_exists('mb_detect_encoding')) {
            return strlen($value);
        }

        if (false === $encoding = mb_detect_encoding($value)) {
            return strlen($value);
        }

        return mb_strwidth($value, $encoding);
    }

    protected static function reportInvalidArgument($message)
    {
        throw new InvalidArgumentException($message);
    }

    private function __construct()
    {
    }
}
{
    "name":         "phpspec/prophecy",
    "description":  "Highly opinionated mocking framework for PHP 5.3+",
    "keywords":     ["Mock", "Stub", "Dummy", "Double", "Fake", "Spy"],
    "homepage":     "https://github.com/phpspec/prophecy",
    "type":         "library",
    "license":      "MIT",
    "authors":      [
        {
            "name":      "Konstantin Kudryashov",
            "email":     "ever.zet@gmail.com",
            "homepage":  "http://everzet.com"
        },
        {
            "name":      "Marcello Duarte",
            "email":     "marcello.duarte@gmail.com"
        }
    ],

    "require": {
        "php":                               "^5.3|^7.0",
        "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0",
        "sebastian/comparator":              "^1.1|^2.0|^3.0",
        "doctrine/instantiator":             "^1.0.2",
        "sebastian/recursion-context":       "^1.0|^2.0|^3.0"
    },

    "require-dev": {
        "phpspec/phpspec": "^2.5|^3.2",
        "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1"
    },

    "autoload": {
        "psr-0": {
            "Prophecy\\": "src/"
        }
    },

    "autoload-dev": {
        "psr-4": {
            "Fixtures\\Prophecy\\": "fixtures"
        }
    },

    "extra": {
        "branch-alias": {
            "dev-master": "1.8.x-dev"
        }
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Comparator;

use SebastianBergmann\Comparator\Factory as BaseFactory;

/**
 * Prophecy comparator factory.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
final class Factory extends BaseFactory
{
    /**
     * @var Factory
     */
    private static $instance;

    public function __construct()
    {
        parent::__construct();

        $this->register(new ClosureComparator());
        $this->register(new ProphecyComparator());
    }

    /**
     * @return Factory
     */
    public static function getInstance()
    {
        if (self::$instance === null) {
            self::$instance = new Factory;
        }

        return self::$instance;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Comparator;

use SebastianBergmann\Comparator\Comparator;
use SebastianBergmann\Comparator\ComparisonFailure;

/**
 * Closure comparator.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
final class ClosureComparator extends Comparator
{
    public function accepts($expected, $actual)
    {
        return is_object($expected) && $expected instanceof \Closure
            && is_object($actual) && $actual instanceof \Closure;
    }

    public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false)
    {
        throw new ComparisonFailure(
            $expected,
            $actual,
            // we don't need a diff
            '',
            '',
            false,
            'all closures are born different'
        );
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Comparator;

use Prophecy\Prophecy\ProphecyInterface;
use SebastianBergmann\Comparator\ObjectComparator;

class ProphecyComparator extends ObjectComparator
{
    public function accepts($expected, $actual)
    {
        return is_object($expected) && is_object($actual) && $actual instanceof ProphecyInterface;
    }

    public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false, array &$processed = array())
    {
        parent::assertEquals($expected, $actual->reveal(), $delta, $canonicalize, $ignoreCase, $processed);
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy;

use Prophecy\Argument\Token;

/**
 * Argument tokens shortcuts.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class Argument
{
    /**
     * Checks that argument is exact value or object.
     *
     * @param mixed $value
     *
     * @return Token\ExactValueToken
     */
    public static function exact($value)
    {
        return new Token\ExactValueToken($value);
    }

    /**
     * Checks that argument is of specific type or instance of specific class.
     *
     * @param string $type Type name (`integer`, `string`) or full class name
     *
     * @return Token\TypeToken
     */
    public static function type($type)
    {
        return new Token\TypeToken($type);
    }

    /**
     * Checks that argument object has specific state.
     *
     * @param string $methodName
     * @param mixed  $value
     *
     * @return Token\ObjectStateToken
     */
    public static function which($methodName, $value)
    {
        return new Token\ObjectStateToken($methodName, $value);
    }

    /**
     * Checks that argument matches provided callback.
     *
     * @param callable $callback
     *
     * @return Token\CallbackToken
     */
    public static function that($callback)
    {
        return new Token\CallbackToken($callback);
    }

    /**
     * Matches any single value.
     *
     * @return Token\AnyValueToken
     */
    public static function any()
    {
        return new Token\AnyValueToken;
    }

    /**
     * Matches all values to the rest of the signature.
     *
     * @return Token\AnyValuesToken
     */
    public static function cetera()
    {
        return new Token\AnyValuesToken;
    }

    /**
     * Checks that argument matches all tokens
     *
     * @param mixed ... a list of tokens
     *
     * @return Token\LogicalAndToken
     */
    public static function allOf()
    {
        return new Token\LogicalAndToken(func_get_args());
    }

    /**
     * Checks that argument array or countable object has exact number of elements.
     *
     * @param integer $value array elements count
     *
     * @return Token\ArrayCountToken
     */
    public static function size($value)
    {
        return new Token\ArrayCountToken($value);
    }

    /**
     * Checks that argument array contains (key, value) pair
     *
     * @param mixed $key   exact value or token
     * @param mixed $value exact value or token
     *
     * @return Token\ArrayEntryToken
     */
    public static function withEntry($key, $value)
    {
        return new Token\ArrayEntryToken($key, $value);
    }

    /**
     * Checks that arguments array entries all match value
     *
     * @param mixed $value
     *
     * @return Token\ArrayEveryEntryToken
     */
    public static function withEveryEntry($value)
    {
        return new Token\ArrayEveryEntryToken($value);
    }

    /**
     * Checks that argument array contains value
     *
     * @param mixed $value
     *
     * @return Token\ArrayEntryToken
     */
    public static function containing($value)
    {
        return new Token\ArrayEntryToken(self::any(), $value);
    }

    /**
     * Checks that argument array has key
     *
     * @param mixed $key exact value or token
     *
     * @return Token\ArrayEntryToken
     */
    public static function withKey($key)
    {
        return new Token\ArrayEntryToken($key, self::any());
    }

    /**
     * Checks that argument does not match the value|token.
     *
     * @param mixed $value either exact value or argument token
     *
     * @return Token\LogicalNotToken
     */
    public static function not($value)
    {
        return new Token\LogicalNotToken($value);
    }

    /**
     * @param string $value
     *
     * @return Token\StringContainsToken
     */
    public static function containingString($value)
    {
        return new Token\StringContainsToken($value);
    }

    /**
     * Checks that argument is identical value.
     *
     * @param mixed $value
     *
     * @return Token\IdenticalValueToken
     */
    public static function is($value)
    {
        return new Token\IdenticalValueToken($value);
    }

    /**
     * Check that argument is same value when rounding to the
     * given precision.
     *
     * @param float $value
     * @param float $precision
     *
     * @return Token\ApproximateValueToken
     */
    public static function approximate($value, $precision = 0)
    {
        return new Token\ApproximateValueToken($value, $precision);
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Doubler;

/**
 * Core double interface.
 * All doubled classes will implement this one.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface DoubleInterface
{
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Doubler;

use Prophecy\Exception\Doubler\DoubleException;
use Prophecy\Exception\Doubler\ClassNotFoundException;
use Prophecy\Exception\Doubler\InterfaceNotFoundException;
use ReflectionClass;

/**
 * Lazy double.
 * Gives simple interface to describe double before creating it.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class LazyDouble
{
    private $doubler;
    private $class;
    private $interfaces = array();
    private $arguments  = null;
    private $double;

    /**
     * Initializes lazy double.
     *
     * @param Doubler $doubler
     */
    public function __construct(Doubler $doubler)
    {
        $this->doubler = $doubler;
    }

    /**
     * Tells doubler to use specific class as parent one for double.
     *
     * @param string|ReflectionClass $class
     *
     * @throws \Prophecy\Exception\Doubler\ClassNotFoundException
     * @throws \Prophecy\Exception\Doubler\DoubleException
     */
    public function setParentClass($class)
    {
        if (null !== $this->double) {
            throw new DoubleException('Can not extend class with already instantiated double.');
        }

        if (!$class instanceof ReflectionClass) {
            if (!class_exists($class)) {
                throw new ClassNotFoundException(sprintf('Class %s not found.', $class), $class);
            }

            $class = new ReflectionClass($class);
        }

        $this->class = $class;
    }

    /**
     * Tells doubler to implement specific interface with double.
     *
     * @param string|ReflectionClass $interface
     *
     * @throws \Prophecy\Exception\Doubler\InterfaceNotFoundException
     * @throws \Prophecy\Exception\Doubler\DoubleException
     */
    public function addInterface($interface)
    {
        if (null !== $this->double) {
            throw new DoubleException(
                'Can not implement interface with already instantiated double.'
            );
        }

        if (!$interface instanceof ReflectionClass) {
            if (!interface_exists($interface)) {
                throw new InterfaceNotFoundException(
                    sprintf('Interface %s not found.', $interface),
                    $interface
                );
            }

            $interface = new ReflectionClass($interface);
        }

        $this->interfaces[] = $interface;
    }

    /**
     * Sets constructor arguments.
     *
     * @param array $arguments
     */
    public function setArguments(array $arguments = null)
    {
        $this->arguments = $arguments;
    }

    /**
     * Creates double instance or returns already created one.
     *
     * @return DoubleInterface
     */
    public function getInstance()
    {
        if (null === $this->double) {
            if (null !== $this->arguments) {
                return $this->double = $this->doubler->double(
                    $this->class, $this->interfaces, $this->arguments
                );
            }

            $this->double = $this->doubler->double($this->class, $this->interfaces);
        }

        return $this->double;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Doubler;

use ReflectionClass;

/**
 * Name generator.
 * Generates classname for double.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class NameGenerator
{
    private static $counter = 1;

    /**
     * Generates name.
     *
     * @param ReflectionClass   $class
     * @param ReflectionClass[] $interfaces
     *
     * @return string
     */
    public function name(ReflectionClass $class = null, array $interfaces)
    {
        $parts = array();

        if (null !== $class) {
            $parts[] = $class->getName();
        } else {
            foreach ($interfaces as $interface) {
                $parts[] = $interface->getShortName();
            }
        }

        if (!count($parts)) {
            $parts[] = 'stdClass';
        }

        return sprintf('Double\%s\P%d', implode('\\', $parts), self::$counter++);
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Doubler;

use Doctrine\Instantiator\Instantiator;
use Prophecy\Doubler\ClassPatch\ClassPatchInterface;
use Prophecy\Doubler\Generator\ClassMirror;
use Prophecy\Doubler\Generator\ClassCreator;
use Prophecy\Exception\InvalidArgumentException;
use ReflectionClass;

/**
 * Cached class doubler.
 * Prevents mirroring/creation of the same structure twice.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class Doubler
{
    private $mirror;
    private $creator;
    private $namer;

    /**
     * @var ClassPatchInterface[]
     */
    private $patches = array();

    /**
     * @var \Doctrine\Instantiator\Instantiator
     */
    private $instantiator;

    /**
     * Initializes doubler.
     *
     * @param ClassMirror   $mirror
     * @param ClassCreator  $creator
     * @param NameGenerator $namer
     */
    public function __construct(ClassMirror $mirror = null, ClassCreator $creator = null,
                                NameGenerator $namer = null)
    {
        $this->mirror  = $mirror  ?: new ClassMirror;
        $this->creator = $creator ?: new ClassCreator;
        $this->namer   = $namer   ?: new NameGenerator;
    }

    /**
     * Returns list of registered class patches.
     *
     * @return ClassPatchInterface[]
     */
    public function getClassPatches()
    {
        return $this->patches;
    }

    /**
     * Registers new class patch.
     *
     * @param ClassPatchInterface $patch
     */
    public function registerClassPatch(ClassPatchInterface $patch)
    {
        $this->patches[] = $patch;

        @usort($this->patches, function (ClassPatchInterface $patch1, ClassPatchInterface $patch2) {
            return $patch2->getPriority() - $patch1->getPriority();
        });
    }

    /**
     * Creates double from specific class or/and list of interfaces.
     *
     * @param ReflectionClass   $class
     * @param ReflectionClass[] $interfaces Array of ReflectionClass instances
     * @param array             $args       Constructor arguments
     *
     * @return DoubleInterface
     *
     * @throws \Prophecy\Exception\InvalidArgumentException
     */
    public function double(ReflectionClass $class = null, array $interfaces, array $args = null)
    {
        foreach ($interfaces as $interface) {
            if (!$interface instanceof ReflectionClass) {
                throw new InvalidArgumentException(sprintf(
                    "[ReflectionClass \$interface1 [, ReflectionClass \$interface2]] array expected as\n".
                    "a second argument to `Doubler::double(...)`, but got %s.",
                    is_object($interface) ? get_class($interface).' class' : gettype($interface)
                ));
            }
        }

        $classname  = $this->createDoubleClass($class, $interfaces);
        $reflection = new ReflectionClass($classname);

        if (null !== $args) {
            return $reflection->newInstanceArgs($args);
        }
        if ((null === $constructor = $reflection->getConstructor())
            || ($constructor->isPublic() && !$constructor->isFinal())) {
            return $reflection->newInstance();
        }

        if (!$this->instantiator) {
            $this->instantiator = new Instantiator();
        }

        return $this->instantiator->instantiate($classname);
    }

    /**
     * Creates double class and returns its FQN.
     *
     * @param ReflectionClass   $class
     * @param ReflectionClass[] $interfaces
     *
     * @return string
     */
    protected function createDoubleClass(ReflectionClass $class = null, array $interfaces)
    {
        $name = $this->namer->name($class, $interfaces);
        $node = $this->mirror->reflect($class, $interfaces);

        foreach ($this->patches as $patch) {
            if ($patch->supports($node)) {
                $patch->apply($node);
            }
        }

        $this->creator->create($name, $node);

        return $name;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Doubler\Generator;

use Prophecy\Exception\Doubler\ClassCreatorException;

/**
 * Class creator.
 * Creates specific class in current environment.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class ClassCreator
{
    private $generator;

    /**
     * Initializes creator.
     *
     * @param ClassCodeGenerator $generator
     */
    public function __construct(ClassCodeGenerator $generator = null)
    {
        $this->generator = $generator ?: new ClassCodeGenerator;
    }

    /**
     * Creates class.
     *
     * @param string         $classname
     * @param Node\ClassNode $class
     *
     * @return mixed
     *
     * @throws \Prophecy\Exception\Doubler\ClassCreatorException
     */
    public function create($classname, Node\ClassNode $class)
    {
        $code = $this->generator->generate($classname, $class);
        $return = eval($code);

        if (!class_exists($classname, false)) {
            if (count($class->getInterfaces())) {
                throw new ClassCreatorException(sprintf(
                    'Could not double `%s` and implement interfaces: [%s].',
                    $class->getParentClass(), implode(', ', $class->getInterfaces())
                ), $class);
            }

            throw new ClassCreatorException(
                sprintf('Could not double `%s`.', $class->getParentClass()),
                $class
            );
        }

        return $return;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Doubler\Generator;

/**
 * Reflection interface.
 * All reflected classes implement this interface.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface ReflectionInterface
{
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Doubler\Generator;

/**
 * Class code creator.
 * Generates PHP code for specific class node tree.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class ClassCodeGenerator
{
    /**
     * @var TypeHintReference
     */
    private $typeHintReference;

    public function __construct(TypeHintReference $typeHintReference = null)
    {
        $this->typeHintReference = $typeHintReference ?: new TypeHintReference();
    }

    /**
     * Generates PHP code for class node.
     *
     * @param string         $classname
     * @param Node\ClassNode $class
     *
     * @return string
     */
    public function generate($classname, Node\ClassNode $class)
    {
        $parts     = explode('\\', $classname);
        $classname = array_pop($parts);
        $namespace = implode('\\', $parts);

        $code = sprintf("class %s extends \%s implements %s {\n",
            $classname, $class->getParentClass(), implode(', ',
                array_map(function ($interface) {return '\\'.$interface;}, $class->getInterfaces())
            )
        );

        foreach ($class->getProperties() as $name => $visibility) {
            $code .= sprintf("%s \$%s;\n", $visibility, $name);
        }
        $code .= "\n";

        foreach ($class->getMethods() as $method) {
            $code .= $this->generateMethod($method)."\n";
        }
        $code .= "\n}";

        return sprintf("namespace %s {\n%s\n}", $namespace, $code);
    }

    private function generateMethod(Node\MethodNode $method)
    {
        $php = sprintf("%s %s function %s%s(%s)%s {\n",
            $method->getVisibility(),
            $method->isStatic() ? 'static' : '',
            $method->returnsReference() ? '&':'',
            $method->getName(),
            implode(', ', $this->generateArguments($method->getArguments())),
            $this->getReturnType($method)
        );
        $php .= $method->getCode()."\n";

        return $php.'}';
    }

    /**
     * @return string
     */
    private function getReturnType(Node\MethodNode $method)
    {
        if (version_compare(PHP_VERSION, '7.1', '>=')) {
            if ($method->hasReturnType()) {
                return $method->hasNullableReturnType()
                    ? sprintf(': ?%s', $method->getReturnType())
                    : sprintf(': %s', $method->getReturnType());
            }
        }

        if (version_compare(PHP_VERSION, '7.0', '>=')) {
            return $method->hasReturnType() && $method->getReturnType() !== 'void'
                ? sprintf(': %s', $method->getReturnType())
                : '';
        }

        return '';
    }

    private function generateArguments(array $arguments)
    {
        $typeHintReference = $this->typeHintReference;
        return array_map(function (Node\ArgumentNode $argument) use ($typeHintReference) {
            $php = '';

            if (version_compare(PHP_VERSION, '7.1', '>=')) {
                $php .= $argument->isNullable() ? '?' : '';
            }

            if ($hint = $argument->getTypeHint()) {
                $php .= $typeHintReference->isBuiltInParamTypeHint($hint) ? $hint : '\\'.$hint;
            }

            $php .= ' '.($argument->isPassedByReference() ? '&' : '');

            $php .= $argument->isVariadic() ? '...' : '';

            $php .= '$'.$argument->getName();

            if ($argument->isOptional() && !$argument->isVariadic()) {
                $php .= ' = '.var_export($argument->getDefault(), true);
            }

            return $php;
        }, $arguments);
    }
}
<?php

namespace Prophecy\Doubler\Generator;

/**
 * Tells whether a keyword refers to a class or to a built-in type for the
 * current version of php
 */
final class TypeHintReference
{
    public function isBuiltInParamTypeHint($type)
    {
        switch ($type) {
            case 'self':
            case 'array':
                return true;

            case 'callable':
                return PHP_VERSION_ID >= 50400;

            case 'bool':
            case 'float':
            case 'int':
            case 'string':
                return PHP_VERSION_ID >= 70000;

            case 'iterable':
                return PHP_VERSION_ID >= 70100;

            case 'object':
                return PHP_VERSION_ID >= 70200;

            default:
                return false;
        }
    }

    public function isBuiltInReturnTypeHint($type)
    {
        if ($type === 'void') {
            return PHP_VERSION_ID >= 70100;
        }

        return $this->isBuiltInParamTypeHint($type);
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Doubler\Generator;

use Prophecy\Exception\InvalidArgumentException;
use Prophecy\Exception\Doubler\ClassMirrorException;
use ReflectionClass;
use ReflectionMethod;
use ReflectionParameter;

/**
 * Class mirror.
 * Core doubler class. Mirrors specific class and/or interfaces into class node tree.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class ClassMirror
{
    private static $reflectableMethods = array(
        '__construct',
        '__destruct',
        '__sleep',
        '__wakeup',
        '__toString',
        '__call',
        '__invoke'
    );

    /**
     * Reflects provided arguments into class node.
     *
     * @param ReflectionClass   $class
     * @param ReflectionClass[] $interfaces
     *
     * @return Node\ClassNode
     *
     * @throws \Prophecy\Exception\InvalidArgumentException
     */
    public function reflect(ReflectionClass $class = null, array $interfaces)
    {
        $node = new Node\ClassNode;

        if (null !== $class) {
            if (true === $class->isInterface()) {
                throw new InvalidArgumentException(sprintf(
                    "Could not reflect %s as a class, because it\n".
                    "is interface - use the second argument instead.",
                    $class->getName()
                ));
            }

            $this->reflectClassToNode($class, $node);
        }

        foreach ($interfaces as $interface) {
            if (!$interface instanceof ReflectionClass) {
                throw new InvalidArgumentException(sprintf(
                    "[ReflectionClass \$interface1 [, ReflectionClass \$interface2]] array expected as\n".
                    "a second argument to `ClassMirror::reflect(...)`, but got %s.",
                    is_object($interface) ? get_class($interface).' class' : gettype($interface)
                ));
            }
            if (false === $interface->isInterface()) {
                throw new InvalidArgumentException(sprintf(
                    "Could not reflect %s as an interface, because it\n".
                    "is class - use the first argument instead.",
                    $interface->getName()
                ));
            }

            $this->reflectInterfaceToNode($interface, $node);
        }

        $node->addInterface('Prophecy\Doubler\Generator\ReflectionInterface');

        return $node;
    }

    private function reflectClassToNode(ReflectionClass $class, Node\ClassNode $node)
    {
        if (true === $class->isFinal()) {
            throw new ClassMirrorException(sprintf(
                'Could not reflect class %s as it is marked final.', $class->getName()
            ), $class);
        }

        $node->setParentClass($class->getName());

        foreach ($class->getMethods(ReflectionMethod::IS_ABSTRACT) as $method) {
            if (false === $method->isProtected()) {
                continue;
            }

            $this->reflectMethodToNode($method, $node);
        }

        foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
            if (0 === strpos($method->getName(), '_')
                && !in_array($method->getName(), self::$reflectableMethods)) {
                continue;
            }

            if (true === $method->isFinal()) {
                $node->addUnextendableMethod($method->getName());
                continue;
            }

            $this->reflectMethodToNode($method, $node);
        }
    }

    private function reflectInterfaceToNode(ReflectionClass $interface, Node\ClassNode $node)
    {
        $node->addInterface($interface->getName());

        foreach ($interface->getMethods() as $method) {
            $this->reflectMethodToNode($method, $node);
        }
    }

    private function reflectMethodToNode(ReflectionMethod $method, Node\ClassNode $classNode)
    {
        $node = new Node\MethodNode($method->getName());

        if (true === $method->isProtected()) {
            $node->setVisibility('protected');
        }

        if (true === $method->isStatic()) {
            $node->setStatic();
        }

        if (true === $method->returnsReference()) {
            $node->setReturnsReference();
        }

        if (version_compare(PHP_VERSION, '7.0', '>=') && $method->hasReturnType()) {
            $returnType = (string) $method->getReturnType();
            $returnTypeLower = strtolower($returnType);

            if ('self' === $returnTypeLower) {
                $returnType = $method->getDeclaringClass()->getName();
            }
            if ('parent' === $returnTypeLower) {
                $returnType = $method->getDeclaringClass()->getParentClass()->getName();
            }

            $node->setReturnType($returnType);

            if (version_compare(PHP_VERSION, '7.1', '>=') && $method->getReturnType()->allowsNull()) {
                $node->setNullableReturnType(true);
            }
        }

        if (is_array($params = $method->getParameters()) && count($params)) {
            foreach ($params as $param) {
                $this->reflectArgumentToNode($param, $node);
            }
        }

        $classNode->addMethod($node);
    }

    private function reflectArgumentToNode(ReflectionParameter $parameter, Node\MethodNode $methodNode)
    {
        $name = $parameter->getName() == '...' ? '__dot_dot_dot__' : $parameter->getName();
        $node = new Node\ArgumentNode($name);

        $node->setTypeHint($this->getTypeHint($parameter));

        if ($this->isVariadic($parameter)) {
            $node->setAsVariadic();
        }

        if ($this->hasDefaultValue($parameter)) {
            $node->setDefault($this->getDefaultValue($parameter));
        }

        if ($parameter->isPassedByReference()) {
            $node->setAsPassedByReference();
        }

        $node->setAsNullable($this->isNullable($parameter));

        $methodNode->addArgument($node);
    }

    private function hasDefaultValue(ReflectionParameter $parameter)
    {
        if ($this->isVariadic($parameter)) {
            return false;
        }

        if ($parameter->isDefaultValueAvailable()) {
            return true;
        }

        return $parameter->isOptional() || $this->isNullable($parameter);
    }

    private function getDefaultValue(ReflectionParameter $parameter)
    {
        if (!$parameter->isDefaultValueAvailable()) {
            return null;
        }

        return $parameter->getDefaultValue();
    }

    private function getTypeHint(ReflectionParameter $parameter)
    {
        if (null !== $className = $this->getParameterClassName($parameter)) {
            return $className;
        }

        if (true === $parameter->isArray()) {
            return 'array';
        }

        if (version_compare(PHP_VERSION, '5.4', '>=') && true === $parameter->isCallable()) {
            return 'callable';
        }

        if (version_compare(PHP_VERSION, '7.0', '>=') && true === $parameter->hasType()) {
            return (string) $parameter->getType();
        }

        return null;
    }

    private function isVariadic(ReflectionParameter $parameter)
    {
        return PHP_VERSION_ID >= 50600 && $parameter->isVariadic();
    }

    private function isNullable(ReflectionParameter $parameter)
    {
        return $parameter->allowsNull() && null !== $this->getTypeHint($parameter);
    }

    private function getParameterClassName(ReflectionParameter $parameter)
    {
        try {
            return $parameter->getClass() ? $parameter->getClass()->getName() : null;
        } catch (\ReflectionException $e) {
            preg_match('/\[\s\<\w+?>\s([\w,\\\]+)/s', $parameter, $matches);

            return isset($matches[1]) ? $matches[1] : null;
        }
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Doubler\Generator\Node;

/**
 * Argument node.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class ArgumentNode
{
    private $name;
    private $typeHint;
    private $default;
    private $optional    = false;
    private $byReference = false;
    private $isVariadic  = false;
    private $isNullable  = false;

    /**
     * @param string $name
     */
    public function __construct($name)
    {
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }

    public function getTypeHint()
    {
        return $this->typeHint;
    }

    public function setTypeHint($typeHint = null)
    {
        $this->typeHint = $typeHint;
    }

    public function hasDefault()
    {
        return $this->isOptional() && !$this->isVariadic();
    }

    public function getDefault()
    {
        return $this->default;
    }

    public function setDefault($default = null)
    {
        $this->optional = true;
        $this->default  = $default;
    }

    public function isOptional()
    {
        return $this->optional;
    }

    public function setAsPassedByReference($byReference = true)
    {
        $this->byReference = $byReference;
    }

    public function isPassedByReference()
    {
        return $this->byReference;
    }

    public function setAsVariadic($isVariadic = true)
    {
        $this->isVariadic = $isVariadic;
    }

    public function isVariadic()
    {
        return $this->isVariadic;
    }

    public function isNullable()
    {
        return $this->isNullable;
    }

    public function setAsNullable($isNullable = true)
    {
        $this->isNullable = $isNullable;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Doubler\Generator\Node;

use Prophecy\Exception\Doubler\MethodNotExtendableException;
use Prophecy\Exception\InvalidArgumentException;

/**
 * Class node.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class ClassNode
{
    private $parentClass = 'stdClass';
    private $interfaces  = array();
    private $properties  = array();
    private $unextendableMethods = array();

    /**
     * @var MethodNode[]
     */
    private $methods     = array();

    public function getParentClass()
    {
        return $this->parentClass;
    }

    /**
     * @param string $class
     */
    public function setParentClass($class)
    {
        $this->parentClass = $class ?: 'stdClass';
    }

    /**
     * @return string[]
     */
    public function getInterfaces()
    {
        return $this->interfaces;
    }

    /**
     * @param string $interface
     */
    public function addInterface($interface)
    {
        if ($this->hasInterface($interface)) {
            return;
        }

        array_unshift($this->interfaces, $interface);
    }

    /**
     * @param string $interface
     *
     * @return bool
     */
    public function hasInterface($interface)
    {
        return in_array($interface, $this->interfaces);
    }

    public function getProperties()
    {
        return $this->properties;
    }

    public function addProperty($name, $visibility = 'public')
    {
        $visibility = strtolower($visibility);

        if (!in_array($visibility, array('public', 'private', 'protected'))) {
            throw new InvalidArgumentException(sprintf(
                '`%s` property visibility is not supported.', $visibility
            ));
        }

        $this->properties[$name] = $visibility;
    }

    /**
     * @return MethodNode[]
     */
    public function getMethods()
    {
        return $this->methods;
    }

    public function addMethod(MethodNode $method, $force = false)
    {
        if (!$this->isExtendable($method->getName())){
            $message = sprintf(
                'Method `%s` is not extendable, so can not be added.', $method->getName()
            );
            throw new MethodNotExtendableException($message, $this->getParentClass(), $method->getName());
        }

        if ($force || !isset($this->methods[$method->getName()])) {
            $this->methods[$method->getName()] = $method;
        }
    }

    public function removeMethod($name)
    {
        unset($this->methods[$name]);
    }

    /**
     * @param string $name
     *
     * @return MethodNode|null
     */
    public function getMethod($name)
    {
        return $this->hasMethod($name) ? $this->methods[$name] : null;
    }

    /**
     * @param string $name
     *
     * @return bool
     */
    public function hasMethod($name)
    {
        return isset($this->methods[$name]);
    }

    /**
     * @return string[]
     */
    public function getUnextendableMethods()
    {
        return $this->unextendableMethods;
    }

    /**
     * @param string $unextendableMethod
     */
    public function addUnextendableMethod($unextendableMethod)
    {
        if (!$this->isExtendable($unextendableMethod)){
            return;
        }
        $this->unextendableMethods[] = $unextendableMethod;
    }

    /**
     * @param string $method
     * @return bool
     */
    public function isExtendable($method)
    {
        return !in_array($method, $this->unextendableMethods);
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Doubler\Generator\Node;

use Prophecy\Doubler\Generator\TypeHintReference;
use Prophecy\Exception\InvalidArgumentException;

/**
 * Method node.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class MethodNode
{
    private $name;
    private $code;
    private $visibility = 'public';
    private $static = false;
    private $returnsReference = false;
    private $returnType;
    private $nullableReturnType = false;

    /**
     * @var ArgumentNode[]
     */
    private $arguments = array();

    /**
     * @var TypeHintReference
     */
    private $typeHintReference;

    /**
     * @param string $name
     * @param string $code
     */
    public function __construct($name, $code = null, TypeHintReference $typeHintReference = null)
    {
        $this->name = $name;
        $this->code = $code;
        $this->typeHintReference = $typeHintReference ?: new TypeHintReference();
    }

    public function getVisibility()
    {
        return $this->visibility;
    }

    /**
     * @param string $visibility
     */
    public function setVisibility($visibility)
    {
        $visibility = strtolower($visibility);

        if (!in_array($visibility, array('public', 'private', 'protected'))) {
            throw new InvalidArgumentException(sprintf(
                '`%s` method visibility is not supported.', $visibility
            ));
        }

        $this->visibility = $visibility;
    }

    public function isStatic()
    {
        return $this->static;
    }

    public function setStatic($static = true)
    {
        $this->static = (bool) $static;
    }

    public function returnsReference()
    {
        return $this->returnsReference;
    }

    public function setReturnsReference()
    {
        $this->returnsReference = true;
    }

    public function getName()
    {
        return $this->name;
    }

    public function addArgument(ArgumentNode $argument)
    {
        $this->arguments[] = $argument;
    }

    /**
     * @return ArgumentNode[]
     */
    public function getArguments()
    {
        return $this->arguments;
    }

    public function hasReturnType()
    {
        return null !== $this->returnType;
    }

    /**
     * @param string $type
     */
    public function setReturnType($type = null)
    {
        if ($type === '' || $type === null) {
            $this->returnType = null;
            return;
        }
        $typeMap = array(
            'double' => 'float',
            'real' => 'float',
            'boolean' => 'bool',
            'integer' => 'int',
        );
        if (isset($typeMap[$type])) {
            $type = $typeMap[$type];
        }
        $this->returnType = $this->typeHintReference->isBuiltInReturnTypeHint($type) ?
            $type :
            '\\' . ltrim($type, '\\');
    }

    public function getReturnType()
    {
        return $this->returnType;
    }

    /**
     * @param bool $bool
     */
    public function setNullableReturnType($bool = true)
    {
        $this->nullableReturnType = (bool) $bool;
    }

    /**
     * @return bool
     */
    public function hasNullableReturnType()
    {
        return $this->nullableReturnType;
    }

    /**
     * @param string $code
     */
    public function setCode($code)
    {
        $this->code = $code;
    }

    public function getCode()
    {
        if ($this->returnsReference)
        {
            return "throw new \Prophecy\Exception\Doubler\ReturnByReferenceException('Returning by reference not supported', get_class(\$this), '{$this->name}');";
        }

        return (string) $this->code;
    }

    public function useParentCode()
    {
        $this->code = sprintf(
            'return parent::%s(%s);', $this->getName(), implode(', ',
                array_map(array($this, 'generateArgument'), $this->arguments)
            )
        );
    }

    private function generateArgument(ArgumentNode $arg)
    {
        $argument = '$'.$arg->getName();

        if ($arg->isVariadic()) {
            $argument = '...'.$argument;
        }

        return $argument;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Doubler;

use ReflectionClass;

/**
 * Cached class doubler.
 * Prevents mirroring/creation of the same structure twice.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class CachedDoubler extends Doubler
{
    private $classes = array();

    /**
     * {@inheritdoc}
     */
    public function registerClassPatch(ClassPatch\ClassPatchInterface $patch)
    {
        $this->classes[] = array();

        parent::registerClassPatch($patch);
    }

    /**
     * {@inheritdoc}
     */
    protected function createDoubleClass(ReflectionClass $class = null, array $interfaces)
    {
        $classId = $this->generateClassId($class, $interfaces);
        if (isset($this->classes[$classId])) {
            return $this->classes[$classId];
        }

        return $this->classes[$classId] = parent::createDoubleClass($class, $interfaces);
    }

    /**
     * @param ReflectionClass   $class
     * @param ReflectionClass[] $interfaces
     *
     * @return string
     */
    private function generateClassId(ReflectionClass $class = null, array $interfaces)
    {
        $parts = array();
        if (null !== $class) {
            $parts[] = $class->getName();
        }
        foreach ($interfaces as $interface) {
            $parts[] = $interface->getName();
        }
        sort($parts);

        return md5(implode('', $parts));
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Doubler\ClassPatch;

use Prophecy\Doubler\Generator\Node\ClassNode;
use Prophecy\Doubler\Generator\Node\MethodNode;

/**
 * Traversable interface patch.
 * Forces classes that implement interfaces, that extend Traversable to also implement Iterator.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class TraversablePatch implements ClassPatchInterface
{
    /**
     * Supports nodetree, that implement Traversable, but not Iterator or IteratorAggregate.
     *
     * @param ClassNode $node
     *
     * @return bool
     */
    public function supports(ClassNode $node)
    {
        if (in_array('Iterator', $node->getInterfaces())) {
            return false;
        }
        if (in_array('IteratorAggregate', $node->getInterfaces())) {
            return false;
        }

        foreach ($node->getInterfaces() as $interface) {
            if ('Traversable' !== $interface && !is_subclass_of($interface, 'Traversable')) {
                continue;
            }
            if ('Iterator' === $interface || is_subclass_of($interface, 'Iterator')) {
                continue;
            }
            if ('IteratorAggregate' === $interface || is_subclass_of($interface, 'IteratorAggregate')) {
                continue;
            }

            return true;
        }

        return false;
    }

    /**
     * Forces class to implement Iterator interface.
     *
     * @param ClassNode $node
     */
    public function apply(ClassNode $node)
    {
        $node->addInterface('Iterator');

        $node->addMethod(new MethodNode('current'));
        $node->addMethod(new MethodNode('key'));
        $node->addMethod(new MethodNode('next'));
        $node->addMethod(new MethodNode('rewind'));
        $node->addMethod(new MethodNode('valid'));
    }

    /**
     * Returns patch priority, which determines when patch will be applied.
     *
     * @return int Priority number (higher - earlier)
     */
    public function getPriority()
    {
        return 100;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Doubler\ClassPatch;

use Prophecy\Doubler\Generator\Node\ClassNode;

/**
 * Remove method functionality from the double which will clash with php keywords.
 *
 * @author Milan Magudia <milan@magudia.com>
 */
class KeywordPatch implements ClassPatchInterface
{
    /**
     * Support any class
     *
     * @param ClassNode $node
     *
     * @return boolean
     */
    public function supports(ClassNode $node)
    {
        return true;
    }

    /**
     * Remove methods that clash with php keywords
     *
     * @param ClassNode $node
     */
    public function apply(ClassNode $node)
    {
        $methodNames = array_keys($node->getMethods());
        $methodsToRemove = array_intersect($methodNames, $this->getKeywords());
        foreach ($methodsToRemove as $methodName) {
            $node->removeMethod($methodName);
        }
    }

    /**
     * Returns patch priority, which determines when patch will be applied.
     *
     * @return int Priority number (higher - earlier)
     */
    public function getPriority()
    {
        return 49;
    }

    /**
     * Returns array of php keywords.
     *
     * @return array
     */
    private function getKeywords()
    {
        if (\PHP_VERSION_ID >= 70000) {
            return array('__halt_compiler');
        }

        return array(
            '__halt_compiler',
            'abstract',
            'and',
            'array',
            'as',
            'break',
            'callable',
            'case',
            'catch',
            'class',
            'clone',
            'const',
            'continue',
            'declare',
            'default',
            'die',
            'do',
            'echo',
            'else',
            'elseif',
            'empty',
            'enddeclare',
            'endfor',
            'endforeach',
            'endif',
            'endswitch',
            'endwhile',
            'eval',
            'exit',
            'extends',
            'final',
            'finally',
            'for',
            'foreach',
            'function',
            'global',
            'goto',
            'if',
            'implements',
            'include',
            'include_once',
            'instanceof',
            'insteadof',
            'interface',
            'isset',
            'list',
            'namespace',
            'new',
            'or',
            'print',
            'private',
            'protected',
            'public',
            'require',
            'require_once',
            'return',
            'static',
            'switch',
            'throw',
            'trait',
            'try',
            'unset',
            'use',
            'var',
            'while',
            'xor',
            'yield',
        );
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Doubler\ClassPatch;

use Prophecy\Doubler\Generator\Node\ClassNode;

/**
 * Class patch interface.
 * Class patches extend doubles functionality or help
 * Prophecy to avoid some internal PHP bugs.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface ClassPatchInterface
{
    /**
     * Checks if patch supports specific class node.
     *
     * @param ClassNode $node
     *
     * @return bool
     */
    public function supports(ClassNode $node);

    /**
     * Applies patch to the specific class node.
     *
     * @param ClassNode $node
     * @return void
     */
    public function apply(ClassNode $node);

    /**
     * Returns patch priority, which determines when patch will be applied.
     *
     * @return int Priority number (higher - earlier)
     */
    public function getPriority();
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Doubler\ClassPatch;

use Prophecy\Doubler\Generator\Node\ClassNode;

/**
 * ReflectionClass::newInstance patch.
 * Makes first argument of newInstance optional, since it works but signature is misleading
 *
 * @author Florian Klein <florian.klein@free.fr>
 */
class ReflectionClassNewInstancePatch implements ClassPatchInterface
{
    /**
     * Supports ReflectionClass
     *
     * @param ClassNode $node
     *
     * @return bool
     */
    public function supports(ClassNode $node)
    {
        return 'ReflectionClass' === $node->getParentClass();
    }

    /**
     * Updates newInstance's first argument to make it optional
     *
     * @param ClassNode $node
     */
    public function apply(ClassNode $node)
    {
        foreach ($node->getMethod('newInstance')->getArguments() as $argument) {
            $argument->setDefault(null);
        }
    }

    /**
     * Returns patch priority, which determines when patch will be applied.
     *
     * @return int Priority number (higher = earlier)
     */
    public function getPriority()
    {
        return 50;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Doubler\ClassPatch;

use Prophecy\Doubler\Generator\Node\ClassNode;

/**
 * Exception patch for HHVM to remove the stubs from special methods
 *
 * @author Christophe Coevoet <stof@notk.org>
 */
class HhvmExceptionPatch implements ClassPatchInterface
{
    /**
     * Supports exceptions on HHVM.
     *
     * @param ClassNode $node
     *
     * @return bool
     */
    public function supports(ClassNode $node)
    {
        if (!defined('HHVM_VERSION')) {
            return false;
        }

        return 'Exception' === $node->getParentClass() || is_subclass_of($node->getParentClass(), 'Exception');
    }

    /**
     * Removes special exception static methods from the doubled methods.
     *
     * @param ClassNode $node
     *
     * @return void
     */
    public function apply(ClassNode $node)
    {
        if ($node->hasMethod('setTraceOptions')) {
            $node->getMethod('setTraceOptions')->useParentCode();
        }
        if ($node->hasMethod('getTraceOptions')) {
            $node->getMethod('getTraceOptions')->useParentCode();
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getPriority()
    {
        return -50;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Doubler\ClassPatch;

use Prophecy\Doubler\Generator\Node\ClassNode;
use Prophecy\Doubler\Generator\Node\MethodNode;

/**
 * SplFileInfo patch.
 * Makes SplFileInfo and derivative classes usable with Prophecy.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class SplFileInfoPatch implements ClassPatchInterface
{
    /**
     * Supports everything that extends SplFileInfo.
     *
     * @param ClassNode $node
     *
     * @return bool
     */
    public function supports(ClassNode $node)
    {
        if (null === $node->getParentClass()) {
            return false;
        }
        return 'SplFileInfo' === $node->getParentClass()
            || is_subclass_of($node->getParentClass(), 'SplFileInfo')
        ;
    }

    /**
     * Updated constructor code to call parent one with dummy file argument.
     *
     * @param ClassNode $node
     */
    public function apply(ClassNode $node)
    {
        if ($node->hasMethod('__construct')) {
            $constructor = $node->getMethod('__construct');
        } else {
            $constructor = new MethodNode('__construct');
            $node->addMethod($constructor);
        }

        if ($this->nodeIsDirectoryIterator($node)) {
            $constructor->setCode('return parent::__construct("' . __DIR__ . '");');

            return;
        }

        if ($this->nodeIsSplFileObject($node)) {
            $filePath = str_replace('\\','\\\\',__FILE__);
            $constructor->setCode('return parent::__construct("' . $filePath .'");');

            return;
        }

        if ($this->nodeIsSymfonySplFileInfo($node)) {
            $filePath = str_replace('\\','\\\\',__FILE__);
            $constructor->setCode('return parent::__construct("' . $filePath .'", "", "");');

            return;
        }

        $constructor->useParentCode();
    }

    /**
     * Returns patch priority, which determines when patch will be applied.
     *
     * @return int Priority number (higher - earlier)
     */
    public function getPriority()
    {
        return 50;
    }

    /**
     * @param ClassNode $node
     * @return boolean
     */
    private function nodeIsDirectoryIterator(ClassNode $node)
    {
        $parent = $node->getParentClass();

        return 'DirectoryIterator' === $parent
            || is_subclass_of($parent, 'DirectoryIterator');
    }

    /**
     * @param ClassNode $node
     * @return boolean
     */
    private function nodeIsSplFileObject(ClassNode $node)
    {
        $parent = $node->getParentClass();

        return 'SplFileObject' === $parent
            || is_subclass_of($parent, 'SplFileObject');
    }

    /**
     * @param ClassNode $node
     * @return boolean
     */
    private function nodeIsSymfonySplFileInfo(ClassNode $node)
    {
        $parent = $node->getParentClass();

        return 'Symfony\\Component\\Finder\\SplFileInfo' === $parent;
    }
}
<?php

namespace Prophecy\Doubler\ClassPatch;

use Prophecy\Doubler\Generator\Node\ClassNode;
use Prophecy\Exception\Doubler\ClassCreatorException;

class ThrowablePatch implements ClassPatchInterface
{
    /**
     * Checks if patch supports specific class node.
     *
     * @param ClassNode $node
     * @return bool
     */
    public function supports(ClassNode $node)
    {
        return $this->implementsAThrowableInterface($node) && $this->doesNotExtendAThrowableClass($node);
    }

    /**
     * @param ClassNode $node
     * @return bool
     */
    private function implementsAThrowableInterface(ClassNode $node)
    {
        foreach ($node->getInterfaces() as $type) {
            if (is_a($type, 'Throwable', true)) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param ClassNode $node
     * @return bool
     */
    private function doesNotExtendAThrowableClass(ClassNode $node)
    {
        return !is_a($node->getParentClass(), 'Throwable', true);
    }

    /**
     * Applies patch to the specific class node.
     *
     * @param ClassNode $node
     *
     * @return void
     */
    public function apply(ClassNode $node)
    {
        $this->checkItCanBeDoubled($node);
        $this->setParentClassToException($node);
    }

    private function checkItCanBeDoubled(ClassNode $node)
    {
        $className = $node->getParentClass();
        if ($className !== 'stdClass') {
            throw new ClassCreatorException(
                sprintf(
                    'Cannot double concrete class %s as well as implement Traversable',
                    $className
                ),
                $node
            );
        }
    }

    private function setParentClassToException(ClassNode $node)
    {
        $node->setParentClass('Exception');

        $node->removeMethod('getMessage');
        $node->removeMethod('getCode');
        $node->removeMethod('getFile');
        $node->removeMethod('getLine');
        $node->removeMethod('getTrace');
        $node->removeMethod('getPrevious');
        $node->removeMethod('getNext');
        $node->removeMethod('getTraceAsString');
    }

    /**
     * Returns patch priority, which determines when patch will be applied.
     *
     * @return int Priority number (higher - earlier)
     */
    public function getPriority()
    {
        return 100;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Doubler\ClassPatch;

use Prophecy\Doubler\Generator\Node\ClassNode;
use Prophecy\Doubler\Generator\Node\MethodNode;
use Prophecy\Doubler\Generator\Node\ArgumentNode;

/**
 * Add Prophecy functionality to the double.
 * This is a core class patch for Prophecy.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class ProphecySubjectPatch implements ClassPatchInterface
{
    /**
     * Always returns true.
     *
     * @param ClassNode $node
     *
     * @return bool
     */
    public function supports(ClassNode $node)
    {
        return true;
    }

    /**
     * Apply Prophecy functionality to class node.
     *
     * @param ClassNode $node
     */
    public function apply(ClassNode $node)
    {
        $node->addInterface('Prophecy\Prophecy\ProphecySubjectInterface');
        $node->addProperty('objectProphecy', 'private');

        foreach ($node->getMethods() as $name => $method) {
            if ('__construct' === strtolower($name)) {
                continue;
            }

            if ($method->getReturnType() === 'void') {
                $method->setCode(
                    '$this->getProphecy()->makeProphecyMethodCall(__FUNCTION__, func_get_args());'
                );
            } else {
                $method->setCode(
                    'return $this->getProphecy()->makeProphecyMethodCall(__FUNCTION__, func_get_args());'
                );
            }
        }

        $prophecySetter = new MethodNode('setProphecy');
        $prophecyArgument = new ArgumentNode('prophecy');
        $prophecyArgument->setTypeHint('Prophecy\Prophecy\ProphecyInterface');
        $prophecySetter->addArgument($prophecyArgument);
        $prophecySetter->setCode('$this->objectProphecy = $prophecy;');

        $prophecyGetter = new MethodNode('getProphecy');
        $prophecyGetter->setCode('return $this->objectProphecy;');

        if ($node->hasMethod('__call')) {
            $__call = $node->getMethod('__call');
        } else {
            $__call = new MethodNode('__call');
            $__call->addArgument(new ArgumentNode('name'));
            $__call->addArgument(new ArgumentNode('arguments'));

            $node->addMethod($__call, true);
        }

        $__call->setCode(<<<PHP
throw new \Prophecy\Exception\Doubler\MethodNotFoundException(
    sprintf('Method `%s::%s()` not found.', get_class(\$this), func_get_arg(0)),
    \$this->getProphecy(), func_get_arg(0)
);
PHP
        );

        $node->addMethod($prophecySetter, true);
        $node->addMethod($prophecyGetter, true);
    }

    /**
     * Returns patch priority, which determines when patch will be applied.
     *
     * @return int Priority number (higher - earlier)
     */
    public function getPriority()
    {
        return 0;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Doubler\ClassPatch;

use Prophecy\Doubler\Generator\Node\ClassNode;
use Prophecy\Doubler\Generator\Node\MethodNode;

/**
 * Disable constructor.
 * Makes all constructor arguments optional.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class DisableConstructorPatch implements ClassPatchInterface
{
    /**
     * Checks if class has `__construct` method.
     *
     * @param ClassNode $node
     *
     * @return bool
     */
    public function supports(ClassNode $node)
    {
        return true;
    }

    /**
     * Makes all class constructor arguments optional.
     *
     * @param ClassNode $node
     */
    public function apply(ClassNode $node)
    {
        if (!$node->hasMethod('__construct')) {
            $node->addMethod(new MethodNode('__construct', ''));

            return;
        }

        $constructor = $node->getMethod('__construct');
        foreach ($constructor->getArguments() as $argument) {
            $argument->setDefault(null);
        }

        $constructor->setCode(<<<PHP
if (0 < func_num_args()) {
    call_user_func_array(array('parent', '__construct'), func_get_args());
}
PHP
        );
    }

    /**
     * Returns patch priority, which determines when patch will be applied.
     *
     * @return int Priority number (higher - earlier)
     */
    public function getPriority()
    {
        return 100;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Doubler\ClassPatch;

use Prophecy\Doubler\Generator\Node\ClassNode;
use Prophecy\Doubler\Generator\Node\MethodNode;
use Prophecy\PhpDocumentor\ClassAndInterfaceTagRetriever;
use Prophecy\PhpDocumentor\MethodTagRetrieverInterface;

/**
 * Discover Magical API using "@method" PHPDoc format.
 *
 * @author Thomas Tourlourat <thomas@tourlourat.com>
 * @author KÃ©vin Dunglas <dunglas@gmail.com>
 * @author ThÃ©o FIDRY <theo.fidry@gmail.com>
 */
class MagicCallPatch implements ClassPatchInterface
{
    private $tagRetriever;

    public function __construct(MethodTagRetrieverInterface $tagRetriever = null)
    {
        $this->tagRetriever = null === $tagRetriever ? new ClassAndInterfaceTagRetriever() : $tagRetriever;
    }

    /**
     * Support any class
     *
     * @param ClassNode $node
     *
     * @return boolean
     */
    public function supports(ClassNode $node)
    {
        return true;
    }

    /**
     * Discover Magical API
     *
     * @param ClassNode $node
     */
    public function apply(ClassNode $node)
    {
        $types = array_filter($node->getInterfaces(), function ($interface) {
            return 0 !== strpos($interface, 'Prophecy\\');
        });
        $types[] = $node->getParentClass();

        foreach ($types as $type) {
            $reflectionClass = new \ReflectionClass($type);

            while ($reflectionClass) {
                $tagList = $this->tagRetriever->getTagList($reflectionClass);

                foreach ($tagList as $tag) {
                    $methodName = $tag->getMethodName();

                    if (empty($methodName)) {
                        continue;
                    }

                    if (!$reflectionClass->hasMethod($methodName)) {
                        $methodNode = new MethodNode($methodName);
                        $methodNode->setStatic($tag->isStatic());
                        $node->addMethod($methodNode);
                    }
                }

                $reflectionClass = $reflectionClass->getParentClass();
            }
        }
    }

    /**
     * Returns patch priority, which determines when patch will be applied.
     *
     * @return integer Priority number (higher - earlier)
     */
    public function getPriority()
    {
        return 50;
    }
}

<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Util;

use Prophecy\Call\Call;

/**
 * String utility.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class StringUtil
{
    private $verbose;

    /**
     * @param bool $verbose
     */
    public function __construct($verbose = true)
    {
        $this->verbose = $verbose;
    }

    /**
     * Stringifies any provided value.
     *
     * @param mixed   $value
     * @param boolean $exportObject
     *
     * @return string
     */
    public function stringify($value, $exportObject = true)
    {
        if (is_array($value)) {
            if (range(0, count($value) - 1) === array_keys($value)) {
                return '['.implode(', ', array_map(array($this, __FUNCTION__), $value)).']';
            }

            $stringify = array($this, __FUNCTION__);

            return '['.implode(', ', array_map(function ($item, $key) use ($stringify) {
                return (is_integer($key) ? $key : '"'.$key.'"').
                    ' => '.call_user_func($stringify, $item);
            }, $value, array_keys($value))).']';
        }
        if (is_resource($value)) {
            return get_resource_type($value).':'.$value;
        }
        if (is_object($value)) {
            return $exportObject ? ExportUtil::export($value) : sprintf('%s:%s', get_class($value), spl_object_hash($value));
        }
        if (true === $value || false === $value) {
            return $value ? 'true' : 'false';
        }
        if (is_string($value)) {
            $str = sprintf('"%s"', str_replace("\n", '\\n', $value));

            if (!$this->verbose && 50 <= strlen($str)) {
                return substr($str, 0, 50).'"...';
            }

            return $str;
        }
        if (null === $value) {
            return 'null';
        }

        return (string) $value;
    }

    /**
     * Stringifies provided array of calls.
     *
     * @param Call[] $calls Array of Call instances
     *
     * @return string
     */
    public function stringifyCalls(array $calls)
    {
        $self = $this;

        return implode(PHP_EOL, array_map(function (Call $call) use ($self) {
            return sprintf('  - %s(%s) @ %s',
                $call->getMethodName(),
                implode(', ', array_map(array($self, 'stringify'), $call->getArguments())),
                str_replace(GETCWD().DIRECTORY_SEPARATOR, '', $call->getCallPlace())
            );
        }, $calls));
    }
}
<?php

namespace Prophecy\Util;

use Prophecy\Prophecy\ProphecyInterface;
use SebastianBergmann\RecursionContext\Context;

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * This class is a modification from sebastianbergmann/exporter
 * @see https://github.com/sebastianbergmann/exporter
 */
class ExportUtil
{
    /**
     * Exports a value as a string
     *
     * The output of this method is similar to the output of print_r(), but
     * improved in various aspects:
     *
     *  - NULL is rendered as "null" (instead of "")
     *  - TRUE is rendered as "true" (instead of "1")
     *  - FALSE is rendered as "false" (instead of "")
     *  - Strings are always quoted with single quotes
     *  - Carriage returns and newlines are normalized to \n
     *  - Recursion and repeated rendering is treated properly
     *
     * @param  mixed  $value
     * @param  int    $indentation The indentation level of the 2nd+ line
     * @return string
     */
    public static function export($value, $indentation = 0)
    {
        return self::recursiveExport($value, $indentation);
    }

    /**
     * Converts an object to an array containing all of its private, protected
     * and public properties.
     *
     * @param  mixed $value
     * @return array
     */
    public static function toArray($value)
    {
        if (!is_object($value)) {
            return (array) $value;
        }

        $array = array();

        foreach ((array) $value as $key => $val) {
            // properties are transformed to keys in the following way:
            // private   $property => "\0Classname\0property"
            // protected $property => "\0*\0property"
            // public    $property => "property"
            if (preg_match('/^\0.+\0(.+)$/', $key, $matches)) {
                $key = $matches[1];
            }

            // See https://github.com/php/php-src/commit/5721132
            if ($key === "\0gcdata") {
                continue;
            }

            $array[$key] = $val;
        }

        // Some internal classes like SplObjectStorage don't work with the
        // above (fast) mechanism nor with reflection in Zend.
        // Format the output similarly to print_r() in this case
        if ($value instanceof \SplObjectStorage) {
            // However, the fast method does work in HHVM, and exposes the
            // internal implementation. Hide it again.
            if (property_exists('\SplObjectStorage', '__storage')) {
                unset($array['__storage']);
            } elseif (property_exists('\SplObjectStorage', 'storage')) {
                unset($array['storage']);
            }

            if (property_exists('\SplObjectStorage', '__key')) {
                unset($array['__key']);
            }

            foreach ($value as $key => $val) {
                $array[spl_object_hash($val)] = array(
                    'obj' => $val,
                    'inf' => $value->getInfo(),
                );
            }
        }

        return $array;
    }

    /**
     * Recursive implementation of export
     *
     * @param  mixed                                       $value       The value to export
     * @param  int                                         $indentation The indentation level of the 2nd+ line
     * @param  \SebastianBergmann\RecursionContext\Context $processed   Previously processed objects
     * @return string
     * @see    SebastianBergmann\Exporter\Exporter::export
     */
    protected static function recursiveExport(&$value, $indentation, $processed = null)
    {
        if ($value === null) {
            return 'null';
        }

        if ($value === true) {
            return 'true';
        }

        if ($value === false) {
            return 'false';
        }

        if (is_float($value) && floatval(intval($value)) === $value) {
            return "$value.0";
        }

        if (is_resource($value)) {
            return sprintf(
                'resource(%d) of type (%s)',
                $value,
                get_resource_type($value)
            );
        }

        if (is_string($value)) {
            // Match for most non printable chars somewhat taking multibyte chars into account
            if (preg_match('/[^\x09-\x0d\x20-\xff]/', $value)) {
                return 'Binary String: 0x' . bin2hex($value);
            }

            return "'" .
            str_replace(array("\r\n", "\n\r", "\r"), array("\n", "\n", "\n"), $value) .
            "'";
        }

        $whitespace = str_repeat(' ', 4 * $indentation);

        if (!$processed) {
            $processed = new Context;
        }

        if (is_array($value)) {
            if (($key = $processed->contains($value)) !== false) {
                return 'Array &' . $key;
            }

            $array  = $value;
            $key    = $processed->add($value);
            $values = '';

            if (count($array) > 0) {
                foreach ($array as $k => $v) {
                    $values .= sprintf(
                        '%s    %s => %s' . "\n",
                        $whitespace,
                        self::recursiveExport($k, $indentation),
                        self::recursiveExport($value[$k], $indentation + 1, $processed)
                    );
                }

                $values = "\n" . $values . $whitespace;
            }

            return sprintf('Array &%s (%s)', $key, $values);
        }

        if (is_object($value)) {
            $class = get_class($value);

            if ($value instanceof ProphecyInterface) {
                return sprintf('%s Object (*Prophecy*)', $class);
            } elseif ($hash = $processed->contains($value)) {
                return sprintf('%s:%s Object', $class, $hash);
            }

            $hash   = $processed->add($value);
            $values = '';
            $array  = self::toArray($value);

            if (count($array) > 0) {
                foreach ($array as $k => $v) {
                    $values .= sprintf(
                        '%s    %s => %s' . "\n",
                        $whitespace,
                        self::recursiveExport($k, $indentation),
                        self::recursiveExport($v, $indentation + 1, $processed)
                    );
                }

                $values = "\n" . $values . $whitespace;
            }

            return sprintf('%s:%s Object (%s)', $class, $hash, $values);
        }

        return var_export($value, true);
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Argument\Token;

use Prophecy\Exception\InvalidArgumentException;

/**
 * Array entry token.
 *
 * @author Boris Mikhaylov <kaguxmail@gmail.com>
 */
class ArrayEntryToken implements TokenInterface
{
    /** @var \Prophecy\Argument\Token\TokenInterface */
    private $key;
    /** @var \Prophecy\Argument\Token\TokenInterface */
    private $value;

    /**
     * @param mixed $key   exact value or token
     * @param mixed $value exact value or token
     */
    public function __construct($key, $value)
    {
        $this->key = $this->wrapIntoExactValueToken($key);
        $this->value = $this->wrapIntoExactValueToken($value);
    }

    /**
     * Scores half of combined scores from key and value tokens for same entry. Capped at 8.
     * If argument implements \ArrayAccess without \Traversable, then key token is restricted to ExactValueToken.
     *
     * @param array|\ArrayAccess|\Traversable $argument
     *
     * @throws \Prophecy\Exception\InvalidArgumentException
     * @return bool|int
     */
    public function scoreArgument($argument)
    {
        if ($argument instanceof \Traversable) {
            $argument = iterator_to_array($argument);
        }

        if ($argument instanceof \ArrayAccess) {
            $argument = $this->convertArrayAccessToEntry($argument);
        }

        if (!is_array($argument) || empty($argument)) {
            return false;
        }

        $keyScores = array_map(array($this->key,'scoreArgument'), array_keys($argument));
        $valueScores = array_map(array($this->value,'scoreArgument'), $argument);
        $scoreEntry = function ($value, $key) {
            return $value && $key ? min(8, ($key + $value) / 2) : false;
        };

        return max(array_map($scoreEntry, $valueScores, $keyScores));
    }

    /**
     * Returns false.
     *
     * @return boolean
     */
    public function isLast()
    {
        return false;
    }

    /**
     * Returns string representation for token.
     *
     * @return string
     */
    public function __toString()
    {
        return sprintf('[..., %s => %s, ...]', $this->key, $this->value);
    }

    /**
     * Returns key
     *
     * @return TokenInterface
     */
    public function getKey()
    {
        return $this->key;
    }

    /**
     * Returns value
     *
     * @return TokenInterface
     */
    public function getValue()
    {
        return $this->value;
    }

    /**
     * Wraps non token $value into ExactValueToken
     *
     * @param $value
     * @return TokenInterface
     */
    private function wrapIntoExactValueToken($value)
    {
        return $value instanceof TokenInterface ? $value : new ExactValueToken($value);
    }

    /**
     * Converts instance of \ArrayAccess to key => value array entry
     *
     * @param \ArrayAccess $object
     *
     * @return array|null
     * @throws \Prophecy\Exception\InvalidArgumentException
     */
    private function convertArrayAccessToEntry(\ArrayAccess $object)
    {
        if (!$this->key instanceof ExactValueToken) {
            throw new InvalidArgumentException(sprintf(
                'You can only use exact value tokens to match key of ArrayAccess object'.PHP_EOL.
                'But you used `%s`.',
                $this->key
            ));
        }

        $key = $this->key->getValue();

        return $object->offsetExists($key) ? array($key => $object[$key]) : array();
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Argument\Token;

use SebastianBergmann\Comparator\ComparisonFailure;
use Prophecy\Comparator\Factory as ComparatorFactory;
use Prophecy\Util\StringUtil;

/**
 * Exact value token.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class ExactValueToken implements TokenInterface
{
    private $value;
    private $string;
    private $util;
    private $comparatorFactory;

    /**
     * Initializes token.
     *
     * @param mixed             $value
     * @param StringUtil        $util
     * @param ComparatorFactory $comparatorFactory
     */
    public function __construct($value, StringUtil $util = null, ComparatorFactory $comparatorFactory = null)
    {
        $this->value = $value;
        $this->util  = $util ?: new StringUtil();

        $this->comparatorFactory = $comparatorFactory ?: ComparatorFactory::getInstance();
    }

    /**
     * Scores 10 if argument matches preset value.
     *
     * @param $argument
     *
     * @return bool|int
     */
    public function scoreArgument($argument)
    {
        if (is_object($argument) && is_object($this->value)) {
            $comparator = $this->comparatorFactory->getComparatorFor(
                $argument, $this->value
            );

            try {
                $comparator->assertEquals($argument, $this->value);
                return 10;
            } catch (ComparisonFailure $failure) {}
        }

        // If either one is an object it should be castable to a string
        if (is_object($argument) xor is_object($this->value)) {
            if (is_object($argument) && !method_exists($argument, '__toString')) {
                return false;
            }

            if (is_object($this->value) && !method_exists($this->value, '__toString')) {
                return false;
            }
        } elseif (is_numeric($argument) && is_numeric($this->value)) {
            // noop
        } elseif (gettype($argument) !== gettype($this->value)) {
            return false;
        }

        return $argument == $this->value ? 10 : false;
    }

    /**
     * Returns preset value against which token checks arguments.
     *
     * @return mixed
     */
    public function getValue()
    {
        return $this->value;
    }

    /**
     * Returns false.
     *
     * @return bool
     */
    public function isLast()
    {
        return false;
    }

    /**
     * Returns string representation for token.
     *
     * @return string
     */
    public function __toString()
    {
        if (null === $this->string) {
            $this->string = sprintf('exact(%s)', $this->util->stringify($this->value));
        }

        return $this->string;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Argument\Token;

/**
 * Logical AND token.
 *
 * @author Boris Mikhaylov <kaguxmail@gmail.com>
 */
class LogicalAndToken implements TokenInterface
{
    private $tokens = array();

    /**
     * @param array $arguments exact values or tokens
     */
    public function __construct(array $arguments)
    {
        foreach ($arguments as $argument) {
            if (!$argument instanceof TokenInterface) {
                $argument = new ExactValueToken($argument);
            }
            $this->tokens[] = $argument;
        }
    }

    /**
     * Scores maximum score from scores returned by tokens for this argument if all of them score.
     *
     * @param $argument
     *
     * @return bool|int
     */
    public function scoreArgument($argument)
    {
        if (0 === count($this->tokens)) {
            return false;
        }

        $maxScore = 0;
        foreach ($this->tokens as $token) {
            $score = $token->scoreArgument($argument);
            if (false === $score) {
                return false;
            }
            $maxScore = max($score, $maxScore);
        }

        return $maxScore;
    }

    /**
     * Returns false.
     *
     * @return boolean
     */
    public function isLast()
    {
        return false;
    }

    /**
     * Returns string representation for token.
     *
     * @return string
     */
    public function __toString()
    {
        return sprintf('bool(%s)', implode(' AND ', $this->tokens));
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Argument\Token;

/**
 * Any values token.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class AnyValuesToken implements TokenInterface
{
    /**
     * Always scores 2 for any argument.
     *
     * @param $argument
     *
     * @return int
     */
    public function scoreArgument($argument)
    {
        return 2;
    }

    /**
     * Returns true to stop wildcard from processing other tokens.
     *
     * @return bool
     */
    public function isLast()
    {
        return true;
    }

    /**
     * Returns string representation for token.
     *
     * @return string
     */
    public function __toString()
    {
        return '* [, ...]';
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Argument\Token;

use Prophecy\Exception\InvalidArgumentException;

/**
 * Callback-verified token.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class CallbackToken implements TokenInterface
{
    private $callback;

    /**
     * Initializes token.
     *
     * @param callable $callback
     *
     * @throws \Prophecy\Exception\InvalidArgumentException
     */
    public function __construct($callback)
    {
        if (!is_callable($callback)) {
            throw new InvalidArgumentException(sprintf(
                'Callable expected as an argument to CallbackToken, but got %s.',
                gettype($callback)
            ));
        }

        $this->callback = $callback;
    }

    /**
     * Scores 7 if callback returns true, false otherwise.
     *
     * @param $argument
     *
     * @return bool|int
     */
    public function scoreArgument($argument)
    {
        return call_user_func($this->callback, $argument) ? 7 : false;
    }

    /**
     * Returns false.
     *
     * @return bool
     */
    public function isLast()
    {
        return false;
    }

    /**
     * Returns string representation for token.
     *
     * @return string
     */
    public function __toString()
    {
        return 'callback()';
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Argument\Token;

use Prophecy\Util\StringUtil;

/**
 * Identical value token.
 *
 * @author Florian Voutzinos <florian@voutzinos.com>
 */
class IdenticalValueToken implements TokenInterface
{
    private $value;
    private $string;
    private $util;

    /**
     * Initializes token.
     *
     * @param mixed      $value
     * @param StringUtil $util
     */
    public function __construct($value, StringUtil $util = null)
    {
        $this->value = $value;
        $this->util  = $util ?: new StringUtil();
    }

    /**
     * Scores 11 if argument matches preset value.
     *
     * @param $argument
     *
     * @return bool|int
     */
    public function scoreArgument($argument)
    {
        return $argument === $this->value ? 11 : false;
    }

    /**
     * Returns false.
     *
     * @return bool
     */
    public function isLast()
    {
        return false;
    }

    /**
     * Returns string representation for token.
     *
     * @return string
     */
    public function __toString()
    {
        if (null === $this->string) {
            $this->string = sprintf('identical(%s)', $this->util->stringify($this->value));
        }

        return $this->string;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Argument\Token;

use Prophecy\Exception\InvalidArgumentException;

/**
 * Value type token.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class TypeToken implements TokenInterface
{
    private $type;

    /**
     * @param string $type
     */
    public function __construct($type)
    {
        $checker = "is_{$type}";
        if (!function_exists($checker) && !interface_exists($type) && !class_exists($type)) {
            throw new InvalidArgumentException(sprintf(
                'Type or class name expected as an argument to TypeToken, but got %s.', $type
            ));
        }

        $this->type = $type;
    }

    /**
     * Scores 5 if argument has the same type this token was constructed with.
     *
     * @param $argument
     *
     * @return bool|int
     */
    public function scoreArgument($argument)
    {
        $checker = "is_{$this->type}";
        if (function_exists($checker)) {
            return call_user_func($checker, $argument) ? 5 : false;
        }

        return $argument instanceof $this->type ? 5 : false;
    }

    /**
     * Returns false.
     *
     * @return bool
     */
    public function isLast()
    {
        return false;
    }

    /**
     * Returns string representation for token.
     *
     * @return string
     */
    public function __toString()
    {
        return sprintf('type(%s)', $this->type);
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Argument\Token;

use SebastianBergmann\Comparator\ComparisonFailure;
use Prophecy\Comparator\Factory as ComparatorFactory;
use Prophecy\Util\StringUtil;

/**
 * Object state-checker token.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class ObjectStateToken implements TokenInterface
{
    private $name;
    private $value;
    private $util;
    private $comparatorFactory;

    /**
     * Initializes token.
     *
     * @param string            $methodName
     * @param mixed             $value             Expected return value
     * @param null|StringUtil   $util
     * @param ComparatorFactory $comparatorFactory
     */
    public function __construct(
        $methodName,
        $value,
        StringUtil $util = null,
        ComparatorFactory $comparatorFactory = null
    ) {
        $this->name  = $methodName;
        $this->value = $value;
        $this->util  = $util ?: new StringUtil;

        $this->comparatorFactory = $comparatorFactory ?: ComparatorFactory::getInstance();
    }

    /**
     * Scores 8 if argument is an object, which method returns expected value.
     *
     * @param mixed $argument
     *
     * @return bool|int
     */
    public function scoreArgument($argument)
    {
        if (is_object($argument) && method_exists($argument, $this->name)) {
            $actual = call_user_func(array($argument, $this->name));

            $comparator = $this->comparatorFactory->getComparatorFor(
                $this->value, $actual
            );

            try {
                $comparator->assertEquals($this->value, $actual);
                return 8;
            } catch (ComparisonFailure $failure) {
                return false;
            }
        }

        if (is_object($argument) && property_exists($argument, $this->name)) {
            return $argument->{$this->name} === $this->value ? 8 : false;
        }

        return false;
    }

    /**
     * Returns false.
     *
     * @return bool
     */
    public function isLast()
    {
        return false;
    }

    /**
     * Returns string representation for token.
     *
     * @return string
     */
    public function __toString()
    {
        return sprintf('state(%s(), %s)',
            $this->name,
            $this->util->stringify($this->value)
        );
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Argument\Token;

/**
 * Any single value token.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class AnyValueToken implements TokenInterface
{
    /**
     * Always scores 3 for any argument.
     *
     * @param $argument
     *
     * @return int
     */
    public function scoreArgument($argument)
    {
        return 3;
    }

    /**
     * Returns false.
     *
     * @return bool
     */
    public function isLast()
    {
        return false;
    }

    /**
     * Returns string representation for token.
     *
     * @return string
     */
    public function __toString()
    {
        return '*';
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Argument\Token;

/**
 * Argument token interface.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface TokenInterface
{
    /**
     * Calculates token match score for provided argument.
     *
     * @param $argument
     *
     * @return bool|int
     */
    public function scoreArgument($argument);

    /**
     * Returns true if this token prevents check of other tokens (is last one).
     *
     * @return bool|int
     */
    public function isLast();

    /**
     * Returns string representation for token.
     *
     * @return string
     */
    public function __toString();
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Argument\Token;

/**
 * Logical NOT token.
 *
 * @author Boris Mikhaylov <kaguxmail@gmail.com>
 */
class LogicalNotToken implements TokenInterface
{
    /** @var \Prophecy\Argument\Token\TokenInterface  */
    private $token;

    /**
     * @param mixed $value exact value or token
     */
    public function __construct($value)
    {
        $this->token = $value instanceof TokenInterface? $value : new ExactValueToken($value);
    }

    /**
     * Scores 4 when preset token does not match the argument.
     *
     * @param $argument
     *
     * @return bool|int
     */
    public function scoreArgument($argument)
    {
        return false === $this->token->scoreArgument($argument) ? 4 : false;
    }

    /**
     * Returns true if preset token is last.
     *
     * @return bool|int
     */
    public function isLast()
    {
        return $this->token->isLast();
    }

    /**
     * Returns originating token.
     *
     * @return TokenInterface
     */
    public function getOriginatingToken()
    {
        return $this->token;
    }

    /**
     * Returns string representation for token.
     *
     * @return string
     */
    public function __toString()
    {
        return sprintf('not(%s)', $this->token);
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Argument\Token;

/**
 * Array every entry token.
 *
 * @author Adrien Brault <adrien.brault@gmail.com>
 */
class ArrayEveryEntryToken implements TokenInterface
{
    /**
     * @var TokenInterface
     */
    private $value;

    /**
     * @param mixed $value exact value or token
     */
    public function __construct($value)
    {
        if (!$value instanceof TokenInterface) {
            $value = new ExactValueToken($value);
        }

        $this->value = $value;
    }

    /**
     * {@inheritdoc}
     */
    public function scoreArgument($argument)
    {
        if (!$argument instanceof \Traversable && !is_array($argument)) {
            return false;
        }

        $scores = array();
        foreach ($argument as $key => $argumentEntry) {
            $scores[] = $this->value->scoreArgument($argumentEntry);
        }

        if (empty($scores) || in_array(false, $scores, true)) {
            return false;
        }

        return array_sum($scores) / count($scores);
    }

    /**
     * {@inheritdoc}
     */
    public function isLast()
    {
        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function __toString()
    {
        return sprintf('[%s, ..., %s]', $this->value, $this->value);
    }

    /**
     * @return TokenInterface
     */
    public function getValue()
    {
        return $this->value;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Argument\Token;

/**
 * String contains token.
 *
 * @author Peter Mitchell <pete@peterjmit.com>
 */
class StringContainsToken implements TokenInterface
{
    private $value;

    /**
     * Initializes token.
     *
     * @param string $value
     */
    public function __construct($value)
    {
        $this->value = $value;
    }

    public function scoreArgument($argument)
    {
        return is_string($argument) && strpos($argument, $this->value) !== false ? 6 : false;
    }

    /**
     * Returns preset value against which token checks arguments.
     *
     * @return mixed
     */
    public function getValue()
    {
        return $this->value;
    }

    /**
     * Returns false.
     *
     * @return bool
     */
    public function isLast()
    {
        return false;
    }

    /**
     * Returns string representation for token.
     *
     * @return string
     */
    public function __toString()
    {
        return sprintf('contains("%s")', $this->value);
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Argument\Token;

/**
 * Array elements count token.
 *
 * @author Boris Mikhaylov <kaguxmail@gmail.com>
 */

class ArrayCountToken implements TokenInterface
{
    private $count;

    /**
     * @param integer $value
     */
    public function __construct($value)
    {
        $this->count = $value;
    }

    /**
     * Scores 6 when argument has preset number of elements.
     *
     * @param $argument
     *
     * @return bool|int
     */
    public function scoreArgument($argument)
    {
        return $this->isCountable($argument) && $this->hasProperCount($argument) ? 6 : false;
    }

    /**
     * Returns false.
     *
     * @return boolean
     */
    public function isLast()
    {
        return false;
    }

    /**
     * Returns string representation for token.
     *
     * @return string
     */
    public function __toString()
    {
        return sprintf('count(%s)', $this->count);
    }

    /**
     * Returns true if object is either array or instance of \Countable
     *
     * @param $argument
     * @return bool
     */
    private function isCountable($argument)
    {
        return (is_array($argument) || $argument instanceof \Countable);
    }

    /**
     * Returns true if $argument has expected number of elements
     *
     * @param array|\Countable $argument
     *
     * @return bool
     */
    private function hasProperCount($argument)
    {
        return $this->count === count($argument);
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Argument\Token;

/**
 * Approximate value token
 *
 * @author Daniel Leech <daniel@dantleech.com>
 */
class ApproximateValueToken implements TokenInterface
{
    private $value;
    private $precision;

    public function __construct($value, $precision = 0)
    {
        $this->value = $value;
        $this->precision = $precision;
    }

    /**
     * {@inheritdoc}
     */
    public function scoreArgument($argument)
    {
        return round($argument, $this->precision) === round($this->value, $this->precision) ? 10 : false;
    }

    /**
     * {@inheritdoc}
     */
    public function isLast()
    {
        return false;
    }

    /**
     * Returns string representation for token.
     *
     * @return string
     */
    public function __toString()
    {
        return sprintf('â‰…%s', round($this->value, $this->precision));
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Argument;

/**
 * Arguments wildcarding.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class ArgumentsWildcard
{
    /**
     * @var Token\TokenInterface[]
     */
    private $tokens = array();
    private $string;

    /**
     * Initializes wildcard.
     *
     * @param array $arguments Array of argument tokens or values
     */
    public function __construct(array $arguments)
    {
        foreach ($arguments as $argument) {
            if (!$argument instanceof Token\TokenInterface) {
                $argument = new Token\ExactValueToken($argument);
            }

            $this->tokens[] = $argument;
        }
    }

    /**
     * Calculates wildcard match score for provided arguments.
     *
     * @param array $arguments
     *
     * @return false|int False OR integer score (higher - better)
     */
    public function scoreArguments(array $arguments)
    {
        if (0 == count($arguments) && 0 == count($this->tokens)) {
            return 1;
        }

        $arguments  = array_values($arguments);
        $totalScore = 0;
        foreach ($this->tokens as $i => $token) {
            $argument = isset($arguments[$i]) ? $arguments[$i] : null;
            if (1 >= $score = $token->scoreArgument($argument)) {
                return false;
            }

            $totalScore += $score;

            if (true === $token->isLast()) {
                return $totalScore;
            }
        }

        if (count($arguments) > count($this->tokens)) {
            return false;
        }

        return $totalScore;
    }

    /**
     * Returns string representation for wildcard.
     *
     * @return string
     */
    public function __toString()
    {
        if (null === $this->string) {
            $this->string = implode(', ', array_map(function ($token) {
                return (string) $token;
            }, $this->tokens));
        }

        return $this->string;
    }

    /**
     * @return array
     */
    public function getTokens()
    {
        return $this->tokens;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy;

use Prophecy\Doubler\Doubler;
use Prophecy\Doubler\LazyDouble;
use Prophecy\Doubler\ClassPatch;
use Prophecy\Prophecy\ObjectProphecy;
use Prophecy\Prophecy\RevealerInterface;
use Prophecy\Prophecy\Revealer;
use Prophecy\Call\CallCenter;
use Prophecy\Util\StringUtil;
use Prophecy\Exception\Prediction\PredictionException;
use Prophecy\Exception\Prediction\AggregateException;

/**
 * Prophet creates prophecies.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class Prophet
{
    private $doubler;
    private $revealer;
    private $util;

    /**
     * @var ObjectProphecy[]
     */
    private $prophecies = array();

    /**
     * Initializes Prophet.
     *
     * @param null|Doubler           $doubler
     * @param null|RevealerInterface $revealer
     * @param null|StringUtil        $util
     */
    public function __construct(Doubler $doubler = null, RevealerInterface $revealer = null,
                                StringUtil $util = null)
    {
        if (null === $doubler) {
            $doubler = new Doubler;
            $doubler->registerClassPatch(new ClassPatch\SplFileInfoPatch);
            $doubler->registerClassPatch(new ClassPatch\TraversablePatch);
            $doubler->registerClassPatch(new ClassPatch\ThrowablePatch);
            $doubler->registerClassPatch(new ClassPatch\DisableConstructorPatch);
            $doubler->registerClassPatch(new ClassPatch\ProphecySubjectPatch);
            $doubler->registerClassPatch(new ClassPatch\ReflectionClassNewInstancePatch);
            $doubler->registerClassPatch(new ClassPatch\HhvmExceptionPatch());
            $doubler->registerClassPatch(new ClassPatch\MagicCallPatch);
            $doubler->registerClassPatch(new ClassPatch\KeywordPatch);
        }

        $this->doubler  = $doubler;
        $this->revealer = $revealer ?: new Revealer;
        $this->util     = $util ?: new StringUtil;
    }

    /**
     * Creates new object prophecy.
     *
     * @param null|string $classOrInterface Class or interface name
     *
     * @return ObjectProphecy
     */
    public function prophesize($classOrInterface = null)
    {
        $this->prophecies[] = $prophecy = new ObjectProphecy(
            new LazyDouble($this->doubler),
            new CallCenter($this->util),
            $this->revealer
        );

        if ($classOrInterface && class_exists($classOrInterface)) {
            return $prophecy->willExtend($classOrInterface);
        }

        if ($classOrInterface && interface_exists($classOrInterface)) {
            return $prophecy->willImplement($classOrInterface);
        }

        return $prophecy;
    }

    /**
     * Returns all created object prophecies.
     *
     * @return ObjectProphecy[]
     */
    public function getProphecies()
    {
        return $this->prophecies;
    }

    /**
     * Returns Doubler instance assigned to this Prophet.
     *
     * @return Doubler
     */
    public function getDoubler()
    {
        return $this->doubler;
    }

    /**
     * Checks all predictions defined by prophecies of this Prophet.
     *
     * @throws Exception\Prediction\AggregateException If any prediction fails
     */
    public function checkPredictions()
    {
        $exception = new AggregateException("Some predictions failed:\n");
        foreach ($this->prophecies as $prophecy) {
            try {
                $prophecy->checkProphecyMethodsPredictions();
            } catch (PredictionException $e) {
                $exception->append($e);
            }
        }

        if (count($exception->getExceptions())) {
            throw $exception;
        }
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\PhpDocumentor;

use phpDocumentor\Reflection\DocBlock\Tag\MethodTag as LegacyMethodTag;
use phpDocumentor\Reflection\DocBlock\Tags\Method;

/**
 * @author ThÃ©o FIDRY <theo.fidry@gmail.com>
 *
 * @internal
 */
interface MethodTagRetrieverInterface
{
    /**
     * @param \ReflectionClass $reflectionClass
     *
     * @return LegacyMethodTag[]|Method[]
     */
    public function getTagList(\ReflectionClass $reflectionClass);
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\PhpDocumentor;

use phpDocumentor\Reflection\DocBlock\Tags\Method;
use phpDocumentor\Reflection\DocBlockFactory;
use phpDocumentor\Reflection\Types\ContextFactory;

/**
 * @author ThÃ©o FIDRY <theo.fidry@gmail.com>
 *
 * @internal
 */
final class ClassTagRetriever implements MethodTagRetrieverInterface
{
    private $docBlockFactory;
    private $contextFactory;

    public function __construct()
    {
        $this->docBlockFactory = DocBlockFactory::createInstance();
        $this->contextFactory = new ContextFactory();
    }

    /**
     * @param \ReflectionClass $reflectionClass
     *
     * @return Method[]
     */
    public function getTagList(\ReflectionClass $reflectionClass)
    {
        try {
            $phpdoc = $this->docBlockFactory->create(
                $reflectionClass,
                $this->contextFactory->createFromReflector($reflectionClass)
            );

            return $phpdoc->getTagsByName('method');
        } catch (\InvalidArgumentException $e) {
            return array();
        }
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\PhpDocumentor;

use phpDocumentor\Reflection\DocBlock\Tag\MethodTag as LegacyMethodTag;
use phpDocumentor\Reflection\DocBlock\Tags\Method;

/**
 * @author ThÃ©o FIDRY <theo.fidry@gmail.com>
 *
 * @internal
 */
final class ClassAndInterfaceTagRetriever implements MethodTagRetrieverInterface
{
    private $classRetriever;

    public function __construct(MethodTagRetrieverInterface $classRetriever = null)
    {
        if (null !== $classRetriever) {
            $this->classRetriever = $classRetriever;

            return;
        }

        $this->classRetriever = class_exists('phpDocumentor\Reflection\DocBlockFactory') && class_exists('phpDocumentor\Reflection\Types\ContextFactory')
            ? new ClassTagRetriever()
            : new LegacyClassTagRetriever()
        ;
    }

    /**
     * @param \ReflectionClass $reflectionClass
     *
     * @return LegacyMethodTag[]|Method[]
     */
    public function getTagList(\ReflectionClass $reflectionClass)
    {
        return array_merge(
            $this->classRetriever->getTagList($reflectionClass),
            $this->getInterfacesTagList($reflectionClass)
        );
    }

    /**
     * @param \ReflectionClass $reflectionClass
     *
     * @return LegacyMethodTag[]|Method[]
     */
    private function getInterfacesTagList(\ReflectionClass $reflectionClass)
    {
        $interfaces = $reflectionClass->getInterfaces();
        $tagList = array();

        foreach($interfaces as $interface) {
            $tagList = array_merge($tagList, $this->classRetriever->getTagList($interface));
        }

        return $tagList;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\PhpDocumentor;

use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock\Tag\MethodTag as LegacyMethodTag;

/**
 * @author ThÃ©o FIDRY <theo.fidry@gmail.com>
 *
 * @internal
 */
final class LegacyClassTagRetriever implements MethodTagRetrieverInterface
{
    /**
     * @param \ReflectionClass $reflectionClass
     *
     * @return LegacyMethodTag[]
     */
    public function getTagList(\ReflectionClass $reflectionClass)
    {
        $phpdoc = new DocBlock($reflectionClass->getDocComment());

        return $phpdoc->getTagsByName('method');
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Prediction;

use Prophecy\Call\Call;
use Prophecy\Prophecy\ObjectProphecy;
use Prophecy\Prophecy\MethodProphecy;
use Prophecy\Util\StringUtil;
use Prophecy\Exception\Prediction\UnexpectedCallsException;

/**
 * No calls prediction.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class NoCallsPrediction implements PredictionInterface
{
    private $util;

    /**
     * Initializes prediction.
     *
     * @param null|StringUtil $util
     */
    public function __construct(StringUtil $util = null)
    {
        $this->util = $util ?: new StringUtil;
    }

    /**
     * Tests that there were no calls made.
     *
     * @param Call[]         $calls
     * @param ObjectProphecy $object
     * @param MethodProphecy $method
     *
     * @throws \Prophecy\Exception\Prediction\UnexpectedCallsException
     */
    public function check(array $calls, ObjectProphecy $object, MethodProphecy $method)
    {
        if (!count($calls)) {
            return;
        }

        $verb = count($calls) === 1 ? 'was' : 'were';

        throw new UnexpectedCallsException(sprintf(
            "No calls expected that match:\n".
            "  %s->%s(%s)\n".
            "but %d %s made:\n%s",
            get_class($object->reveal()),
            $method->getMethodName(),
            $method->getArgumentsWildcard(),
            count($calls),
            $verb,
            $this->util->stringifyCalls($calls)
        ), $method, $calls);
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Prediction;

use Prophecy\Call\Call;
use Prophecy\Prophecy\ObjectProphecy;
use Prophecy\Prophecy\MethodProphecy;
use Prophecy\Exception\InvalidArgumentException;
use Closure;

/**
 * Callback prediction.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class CallbackPrediction implements PredictionInterface
{
    private $callback;

    /**
     * Initializes callback prediction.
     *
     * @param callable $callback Custom callback
     *
     * @throws \Prophecy\Exception\InvalidArgumentException
     */
    public function __construct($callback)
    {
        if (!is_callable($callback)) {
            throw new InvalidArgumentException(sprintf(
                'Callable expected as an argument to CallbackPrediction, but got %s.',
                gettype($callback)
            ));
        }

        $this->callback = $callback;
    }

    /**
     * Executes preset callback.
     *
     * @param Call[]         $calls
     * @param ObjectProphecy $object
     * @param MethodProphecy $method
     */
    public function check(array $calls, ObjectProphecy $object, MethodProphecy $method)
    {
        $callback = $this->callback;

        if ($callback instanceof Closure && method_exists('Closure', 'bind')) {
            $callback = Closure::bind($callback, $object);
        }

        call_user_func($callback, $calls, $object, $method);
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Prediction;

use Prophecy\Call\Call;
use Prophecy\Prophecy\ObjectProphecy;
use Prophecy\Prophecy\MethodProphecy;
use Prophecy\Argument\ArgumentsWildcard;
use Prophecy\Argument\Token\AnyValuesToken;
use Prophecy\Util\StringUtil;
use Prophecy\Exception\Prediction\NoCallsException;

/**
 * Call prediction.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class CallPrediction implements PredictionInterface
{
    private $util;

    /**
     * Initializes prediction.
     *
     * @param StringUtil $util
     */
    public function __construct(StringUtil $util = null)
    {
        $this->util = $util ?: new StringUtil;
    }

    /**
     * Tests that there was at least one call.
     *
     * @param Call[]         $calls
     * @param ObjectProphecy $object
     * @param MethodProphecy $method
     *
     * @throws \Prophecy\Exception\Prediction\NoCallsException
     */
    public function check(array $calls, ObjectProphecy $object, MethodProphecy $method)
    {
        if (count($calls)) {
            return;
        }

        $methodCalls = $object->findProphecyMethodCalls(
            $method->getMethodName(),
            new ArgumentsWildcard(array(new AnyValuesToken))
        );

        if (count($methodCalls)) {
            throw new NoCallsException(sprintf(
                "No calls have been made that match:\n".
                "  %s->%s(%s)\n".
                "but expected at least one.\n".
                "Recorded `%s(...)` calls:\n%s",

                get_class($object->reveal()),
                $method->getMethodName(),
                $method->getArgumentsWildcard(),
                $method->getMethodName(),
                $this->util->stringifyCalls($methodCalls)
            ), $method);
        }

        throw new NoCallsException(sprintf(
            "No calls have been made that match:\n".
            "  %s->%s(%s)\n".
            "but expected at least one.",

            get_class($object->reveal()),
            $method->getMethodName(),
            $method->getArgumentsWildcard()
        ), $method);
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Prediction;

use Prophecy\Call\Call;
use Prophecy\Prophecy\ObjectProphecy;
use Prophecy\Prophecy\MethodProphecy;
use Prophecy\Argument\ArgumentsWildcard;
use Prophecy\Argument\Token\AnyValuesToken;
use Prophecy\Util\StringUtil;
use Prophecy\Exception\Prediction\UnexpectedCallsCountException;

/**
 * Prediction interface.
 * Predictions are logical test blocks, tied to `should...` keyword.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class CallTimesPrediction implements PredictionInterface
{
    private $times;
    private $util;

    /**
     * Initializes prediction.
     *
     * @param int        $times
     * @param StringUtil $util
     */
    public function __construct($times, StringUtil $util = null)
    {
        $this->times = intval($times);
        $this->util  = $util ?: new StringUtil;
    }

    /**
     * Tests that there was exact amount of calls made.
     *
     * @param Call[]         $calls
     * @param ObjectProphecy $object
     * @param MethodProphecy $method
     *
     * @throws \Prophecy\Exception\Prediction\UnexpectedCallsCountException
     */
    public function check(array $calls, ObjectProphecy $object, MethodProphecy $method)
    {
        if ($this->times == count($calls)) {
            return;
        }

        $methodCalls = $object->findProphecyMethodCalls(
            $method->getMethodName(),
            new ArgumentsWildcard(array(new AnyValuesToken))
        );

        if (count($calls)) {
            $message = sprintf(
                "Expected exactly %d calls that match:\n".
                "  %s->%s(%s)\n".
                "but %d were made:\n%s",

                $this->times,
                get_class($object->reveal()),
                $method->getMethodName(),
                $method->getArgumentsWildcard(),
                count($calls),
                $this->util->stringifyCalls($calls)
            );
        } elseif (count($methodCalls)) {
            $message = sprintf(
                "Expected exactly %d calls that match:\n".
                "  %s->%s(%s)\n".
                "but none were made.\n".
                "Recorded `%s(...)` calls:\n%s",

                $this->times,
                get_class($object->reveal()),
                $method->getMethodName(),
                $method->getArgumentsWildcard(),
                $method->getMethodName(),
                $this->util->stringifyCalls($methodCalls)
            );
        } else {
            $message = sprintf(
                "Expected exactly %d calls that match:\n".
                "  %s->%s(%s)\n".
                "but none were made.",

                $this->times,
                get_class($object->reveal()),
                $method->getMethodName(),
                $method->getArgumentsWildcard()
            );
        }

        throw new UnexpectedCallsCountException($message, $method, $this->times, $calls);
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Prediction;

use Prophecy\Call\Call;
use Prophecy\Prophecy\ObjectProphecy;
use Prophecy\Prophecy\MethodProphecy;

/**
 * Prediction interface.
 * Predictions are logical test blocks, tied to `should...` keyword.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface PredictionInterface
{
    /**
     * Tests that double fulfilled prediction.
     *
     * @param Call[]        $calls
     * @param ObjectProphecy $object
     * @param MethodProphecy $method
     *
     * @throws object
     * @return void
     */
    public function check(array $calls, ObjectProphecy $object, MethodProphecy $method);
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Call;

use Prophecy\Exception\Prophecy\MethodProphecyException;
use Prophecy\Prophecy\MethodProphecy;
use Prophecy\Prophecy\ObjectProphecy;
use Prophecy\Argument\ArgumentsWildcard;
use Prophecy\Util\StringUtil;
use Prophecy\Exception\Call\UnexpectedCallException;

/**
 * Calls receiver & manager.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class CallCenter
{
    private $util;

    /**
     * @var Call[]
     */
    private $recordedCalls = array();

    /**
     * Initializes call center.
     *
     * @param StringUtil $util
     */
    public function __construct(StringUtil $util = null)
    {
        $this->util = $util ?: new StringUtil;
    }

    /**
     * Makes and records specific method call for object prophecy.
     *
     * @param ObjectProphecy $prophecy
     * @param string         $methodName
     * @param array          $arguments
     *
     * @return mixed Returns null if no promise for prophecy found or promise return value.
     *
     * @throws \Prophecy\Exception\Call\UnexpectedCallException If no appropriate method prophecy found
     */
    public function makeCall(ObjectProphecy $prophecy, $methodName, array $arguments)
    {
        // For efficiency exclude 'args' from the generated backtrace
        if (PHP_VERSION_ID >= 50400) {
            // Limit backtrace to last 3 calls as we don't use the rest
            // Limit argument was introduced in PHP 5.4.0
            $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
        } elseif (defined('DEBUG_BACKTRACE_IGNORE_ARGS')) {
            // DEBUG_BACKTRACE_IGNORE_ARGS was introduced in PHP 5.3.6
            $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
        } else {
            $backtrace = debug_backtrace();
        }

        $file = $line = null;
        if (isset($backtrace[2]) && isset($backtrace[2]['file'])) {
            $file = $backtrace[2]['file'];
            $line = $backtrace[2]['line'];
        }

        // If no method prophecies defined, then it's a dummy, so we'll just return null
        if ('__destruct' === $methodName || 0 == count($prophecy->getMethodProphecies())) {
            $this->recordedCalls[] = new Call($methodName, $arguments, null, null, $file, $line);

            return null;
        }

        // There are method prophecies, so it's a fake/stub. Searching prophecy for this call
        $matches = array();
        foreach ($prophecy->getMethodProphecies($methodName) as $methodProphecy) {
            if (0 < $score = $methodProphecy->getArgumentsWildcard()->scoreArguments($arguments)) {
                $matches[] = array($score, $methodProphecy);
            }
        }

        // If fake/stub doesn't have method prophecy for this call - throw exception
        if (!count($matches)) {
            throw $this->createUnexpectedCallException($prophecy, $methodName, $arguments);
        }

        // Sort matches by their score value
        @usort($matches, function ($match1, $match2) { return $match2[0] - $match1[0]; });

        $score = $matches[0][0];
        // If Highest rated method prophecy has a promise - execute it or return null instead
        $methodProphecy = $matches[0][1];
        $returnValue = null;
        $exception   = null;
        if ($promise = $methodProphecy->getPromise()) {
            try {
                $returnValue = $promise->execute($arguments, $prophecy, $methodProphecy);
            } catch (\Exception $e) {
                $exception = $e;
            }
        }

        if ($methodProphecy->hasReturnVoid() && $returnValue !== null) {
            throw new MethodProphecyException(
                "The method \"$methodName\" has a void return type, but the promise returned a value",
                $methodProphecy
            );
        }

        $this->recordedCalls[] = $call = new Call(
            $methodName, $arguments, $returnValue, $exception, $file, $line
        );
        $call->addScore($methodProphecy->getArgumentsWildcard(), $score);

        if (null !== $exception) {
            throw $exception;
        }

        return $returnValue;
    }

    /**
     * Searches for calls by method name & arguments wildcard.
     *
     * @param string            $methodName
     * @param ArgumentsWildcard $wildcard
     *
     * @return Call[]
     */
    public function findCalls($methodName, ArgumentsWildcard $wildcard)
    {
        return array_values(
            array_filter($this->recordedCalls, function (Call $call) use ($methodName, $wildcard) {
                return $methodName === $call->getMethodName()
                    && 0 < $call->getScore($wildcard)
                ;
            })
        );
    }

    private function createUnexpectedCallException(ObjectProphecy $prophecy, $methodName,
                                                   array $arguments)
    {
        $classname = get_class($prophecy->reveal());
        $indentationLength = 8; // looks good
        $argstring = implode(
            ",\n",
            $this->indentArguments(
                array_map(array($this->util, 'stringify'), $arguments),
                $indentationLength
            )
        );

        $expected = array();

        foreach (call_user_func_array('array_merge', $prophecy->getMethodProphecies()) as $methodProphecy) {
            $expected[] = sprintf(
                "  - %s(\n" .
                "%s\n" .
                "    )",
                $methodProphecy->getMethodName(),
                implode(
                    ",\n",
                    $this->indentArguments(
                        array_map('strval', $methodProphecy->getArgumentsWildcard()->getTokens()),
                        $indentationLength
                    )
                )
            );
        }

        return new UnexpectedCallException(
            sprintf(
                "Unexpected method call on %s:\n".
                "  - %s(\n".
                "%s\n".
                "    )\n".
                "expected calls were:\n".
                "%s",

                $classname, $methodName, $argstring, implode("\n", $expected)
            ),
            $prophecy, $methodName, $arguments

        );
    }

    private function formatExceptionMessage(MethodProphecy $methodProphecy)
    {
        return sprintf(
            "  - %s(\n".
            "%s\n".
            "    )",
            $methodProphecy->getMethodName(),
            implode(
                ",\n",
                $this->indentArguments(
                    array_map(
                        function ($token) {
                            return (string) $token;
                        },
                        $methodProphecy->getArgumentsWildcard()->getTokens()
                    ),
                    $indentationLength
                )
            )
        );
    }

    private function indentArguments(array $arguments, $indentationLength)
    {
        return preg_replace_callback(
            '/^/m',
            function () use ($indentationLength) {
                return str_repeat(' ', $indentationLength);
            },
            $arguments
        );
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Call;

use Exception;
use Prophecy\Argument\ArgumentsWildcard;

/**
 * Call object.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class Call
{
    private $methodName;
    private $arguments;
    private $returnValue;
    private $exception;
    private $file;
    private $line;
    private $scores;

    /**
     * Initializes call.
     *
     * @param string      $methodName
     * @param array       $arguments
     * @param mixed       $returnValue
     * @param Exception   $exception
     * @param null|string $file
     * @param null|int    $line
     */
    public function __construct($methodName, array $arguments, $returnValue,
                                Exception $exception = null, $file, $line)
    {
        $this->methodName  = $methodName;
        $this->arguments   = $arguments;
        $this->returnValue = $returnValue;
        $this->exception   = $exception;
        $this->scores      = new \SplObjectStorage();

        if ($file) {
            $this->file = $file;
            $this->line = intval($line);
        }
    }

    /**
     * Returns called method name.
     *
     * @return string
     */
    public function getMethodName()
    {
        return $this->methodName;
    }

    /**
     * Returns called method arguments.
     *
     * @return array
     */
    public function getArguments()
    {
        return $this->arguments;
    }

    /**
     * Returns called method return value.
     *
     * @return null|mixed
     */
    public function getReturnValue()
    {
        return $this->returnValue;
    }

    /**
     * Returns exception that call thrown.
     *
     * @return null|Exception
     */
    public function getException()
    {
        return $this->exception;
    }

    /**
     * Returns callee filename.
     *
     * @return string
     */
    public function getFile()
    {
        return $this->file;
    }

    /**
     * Returns callee line number.
     *
     * @return int
     */
    public function getLine()
    {
        return $this->line;
    }

    /**
     * Returns short notation for callee place.
     *
     * @return string
     */
    public function getCallPlace()
    {
        if (null === $this->file) {
            return 'unknown';
        }

        return sprintf('%s:%d', $this->file, $this->line);
    }

    /**
     * Adds the wildcard match score for the provided wildcard.
     *
     * @param ArgumentsWildcard $wildcard
     * @param false|int $score
     *
     * @return $this
     */
    public function addScore(ArgumentsWildcard $wildcard, $score)
    {
        $this->scores[$wildcard] = $score;

        return $this;
    }

    /**
     * Returns wildcard match score for the provided wildcard. The score is
     * calculated if not already done.
     *
     * @param ArgumentsWildcard $wildcard
     *
     * @return false|int False OR integer score (higher - better)
     */
    public function getScore(ArgumentsWildcard $wildcard)
    {
        if (isset($this->scores[$wildcard])) {
            return $this->scores[$wildcard];
        }

        return $this->scores[$wildcard] = $wildcard->scoreArguments($this->getArguments());
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Prophecy;

/**
 * Prophecies revealer interface.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface RevealerInterface
{
    /**
     * Unwraps value(s).
     *
     * @param mixed $value
     *
     * @return mixed
     */
    public function reveal($value);
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Prophecy;

use SebastianBergmann\Comparator\ComparisonFailure;
use Prophecy\Comparator\Factory as ComparatorFactory;
use Prophecy\Call\Call;
use Prophecy\Doubler\LazyDouble;
use Prophecy\Argument\ArgumentsWildcard;
use Prophecy\Call\CallCenter;
use Prophecy\Exception\Prophecy\ObjectProphecyException;
use Prophecy\Exception\Prophecy\MethodProphecyException;
use Prophecy\Exception\Prediction\AggregateException;
use Prophecy\Exception\Prediction\PredictionException;

/**
 * Object prophecy.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class ObjectProphecy implements ProphecyInterface
{
    private $lazyDouble;
    private $callCenter;
    private $revealer;
    private $comparatorFactory;

    /**
     * @var MethodProphecy[][]
     */
    private $methodProphecies = array();

    /**
     * Initializes object prophecy.
     *
     * @param LazyDouble        $lazyDouble
     * @param CallCenter        $callCenter
     * @param RevealerInterface $revealer
     * @param ComparatorFactory $comparatorFactory
     */
    public function __construct(
        LazyDouble $lazyDouble,
        CallCenter $callCenter = null,
        RevealerInterface $revealer = null,
        ComparatorFactory $comparatorFactory = null
    ) {
        $this->lazyDouble = $lazyDouble;
        $this->callCenter = $callCenter ?: new CallCenter;
        $this->revealer   = $revealer ?: new Revealer;

        $this->comparatorFactory = $comparatorFactory ?: ComparatorFactory::getInstance();
    }

    /**
     * Forces double to extend specific class.
     *
     * @param string $class
     *
     * @return $this
     */
    public function willExtend($class)
    {
        $this->lazyDouble->setParentClass($class);

        return $this;
    }

    /**
     * Forces double to implement specific interface.
     *
     * @param string $interface
     *
     * @return $this
     */
    public function willImplement($interface)
    {
        $this->lazyDouble->addInterface($interface);

        return $this;
    }

    /**
     * Sets constructor arguments.
     *
     * @param array $arguments
     *
     * @return $this
     */
    public function willBeConstructedWith(array $arguments = null)
    {
        $this->lazyDouble->setArguments($arguments);

        return $this;
    }

    /**
     * Reveals double.
     *
     * @return object
     *
     * @throws \Prophecy\Exception\Prophecy\ObjectProphecyException If double doesn't implement needed interface
     */
    public function reveal()
    {
        $double = $this->lazyDouble->getInstance();

        if (null === $double || !$double instanceof ProphecySubjectInterface) {
            throw new ObjectProphecyException(
                "Generated double must implement ProphecySubjectInterface, but it does not.\n".
                'It seems you have wrongly configured doubler without required ClassPatch.',
                $this
            );
        }

        $double->setProphecy($this);

        return $double;
    }

    /**
     * Adds method prophecy to object prophecy.
     *
     * @param MethodProphecy $methodProphecy
     *
     * @throws \Prophecy\Exception\Prophecy\MethodProphecyException If method prophecy doesn't
     *                                                              have arguments wildcard
     */
    public function addMethodProphecy(MethodProphecy $methodProphecy)
    {
        $argumentsWildcard = $methodProphecy->getArgumentsWildcard();
        if (null === $argumentsWildcard) {
            throw new MethodProphecyException(sprintf(
                "Can not add prophecy for a method `%s::%s()`\n".
                "as you did not specify arguments wildcard for it.",
                get_class($this->reveal()),
                $methodProphecy->getMethodName()
            ), $methodProphecy);
        }

        $methodName = $methodProphecy->getMethodName();

        if (!isset($this->methodProphecies[$methodName])) {
            $this->methodProphecies[$methodName] = array();
        }

        $this->methodProphecies[$methodName][] = $methodProphecy;
    }

    /**
     * Returns either all or related to single method prophecies.
     *
     * @param null|string $methodName
     *
     * @return MethodProphecy[]
     */
    public function getMethodProphecies($methodName = null)
    {
        if (null === $methodName) {
            return $this->methodProphecies;
        }

        if (!isset($this->methodProphecies[$methodName])) {
            return array();
        }

        return $this->methodProphecies[$methodName];
    }

    /**
     * Makes specific method call.
     *
     * @param string $methodName
     * @param array  $arguments
     *
     * @return mixed
     */
    public function makeProphecyMethodCall($methodName, array $arguments)
    {
        $arguments = $this->revealer->reveal($arguments);
        $return    = $this->callCenter->makeCall($this, $methodName, $arguments);

        return $this->revealer->reveal($return);
    }

    /**
     * Finds calls by method name & arguments wildcard.
     *
     * @param string            $methodName
     * @param ArgumentsWildcard $wildcard
     *
     * @return Call[]
     */
    public function findProphecyMethodCalls($methodName, ArgumentsWildcard $wildcard)
    {
        return $this->callCenter->findCalls($methodName, $wildcard);
    }

    /**
     * Checks that registered method predictions do not fail.
     *
     * @throws \Prophecy\Exception\Prediction\AggregateException If any of registered predictions fail
     */
    public function checkProphecyMethodsPredictions()
    {
        $exception = new AggregateException(sprintf("%s:\n", get_class($this->reveal())));
        $exception->setObjectProphecy($this);

        foreach ($this->methodProphecies as $prophecies) {
            foreach ($prophecies as $prophecy) {
                try {
                    $prophecy->checkPrediction();
                } catch (PredictionException $e) {
                    $exception->append($e);
                }
            }
        }

        if (count($exception->getExceptions())) {
            throw $exception;
        }
    }

    /**
     * Creates new method prophecy using specified method name and arguments.
     *
     * @param string $methodName
     * @param array  $arguments
     *
     * @return MethodProphecy
     */
    public function __call($methodName, array $arguments)
    {
        $arguments = new ArgumentsWildcard($this->revealer->reveal($arguments));

        foreach ($this->getMethodProphecies($methodName) as $prophecy) {
            $argumentsWildcard = $prophecy->getArgumentsWildcard();
            $comparator = $this->comparatorFactory->getComparatorFor(
                $argumentsWildcard, $arguments
            );

            try {
                $comparator->assertEquals($argumentsWildcard, $arguments);
                return $prophecy;
            } catch (ComparisonFailure $failure) {}
        }

        return new MethodProphecy($this, $methodName, $arguments);
    }

    /**
     * Tries to get property value from double.
     *
     * @param string $name
     *
     * @return mixed
     */
    public function __get($name)
    {
        return $this->reveal()->$name;
    }

    /**
     * Tries to set property value to double.
     *
     * @param string $name
     * @param mixed  $value
     */
    public function __set($name, $value)
    {
        $this->reveal()->$name = $this->revealer->reveal($value);
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Prophecy;

/**
 * Controllable doubles interface.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface ProphecySubjectInterface
{
    /**
     * Sets subject prophecy.
     *
     * @param ProphecyInterface $prophecy
     */
    public function setProphecy(ProphecyInterface $prophecy);

    /**
     * Returns subject prophecy.
     *
     * @return ProphecyInterface
     */
    public function getProphecy();
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Prophecy;

/**
 * Basic prophecies revealer.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class Revealer implements RevealerInterface
{
    /**
     * Unwraps value(s).
     *
     * @param mixed $value
     *
     * @return mixed
     */
    public function reveal($value)
    {
        if (is_array($value)) {
            return array_map(array($this, __FUNCTION__), $value);
        }

        if (!is_object($value)) {
            return $value;
        }

        if ($value instanceof ProphecyInterface) {
            $value = $value->reveal();
        }

        return $value;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Prophecy;

use Prophecy\Argument;
use Prophecy\Prophet;
use Prophecy\Promise;
use Prophecy\Prediction;
use Prophecy\Exception\Doubler\MethodNotFoundException;
use Prophecy\Exception\InvalidArgumentException;
use Prophecy\Exception\Prophecy\MethodProphecyException;

/**
 * Method prophecy.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class MethodProphecy
{
    private $objectProphecy;
    private $methodName;
    private $argumentsWildcard;
    private $promise;
    private $prediction;
    private $checkedPredictions = array();
    private $bound = false;
    private $voidReturnType = false;

    /**
     * Initializes method prophecy.
     *
     * @param ObjectProphecy                        $objectProphecy
     * @param string                                $methodName
     * @param null|Argument\ArgumentsWildcard|array $arguments
     *
     * @throws \Prophecy\Exception\Doubler\MethodNotFoundException If method not found
     */
    public function __construct(ObjectProphecy $objectProphecy, $methodName, $arguments = null)
    {
        $double = $objectProphecy->reveal();
        if (!method_exists($double, $methodName)) {
            throw new MethodNotFoundException(sprintf(
                'Method `%s::%s()` is not defined.', get_class($double), $methodName
            ), get_class($double), $methodName, $arguments);
        }

        $this->objectProphecy = $objectProphecy;
        $this->methodName     = $methodName;

        $reflectedMethod = new \ReflectionMethod($double, $methodName);
        if ($reflectedMethod->isFinal()) {
            throw new MethodProphecyException(sprintf(
                "Can not add prophecy for a method `%s::%s()`\n".
                "as it is a final method.",
                get_class($double),
                $methodName
            ), $this);
        }

        if (null !== $arguments) {
            $this->withArguments($arguments);
        }

        if (version_compare(PHP_VERSION, '7.0', '>=') && true === $reflectedMethod->hasReturnType()) {
            $type = (string) $reflectedMethod->getReturnType();

            if ('void' === $type) {
                $this->voidReturnType = true;
            }

            $this->will(function () use ($type) {
                switch ($type) {
                    case 'void': return;
                    case 'string': return '';
                    case 'float':  return 0.0;
                    case 'int':    return 0;
                    case 'bool':   return false;
                    case 'array':  return array();

                    case 'callable':
                    case 'Closure':
                        return function () {};

                    case 'Traversable':
                    case 'Generator':
                        // Remove eval() when minimum version >=5.5
                        /** @var callable $generator */
                        $generator = eval('return function () { yield; };');
                        return $generator();

                    default:
                        $prophet = new Prophet;
                        return $prophet->prophesize($type)->reveal();
                }
            });
        }
    }

    /**
     * Sets argument wildcard.
     *
     * @param array|Argument\ArgumentsWildcard $arguments
     *
     * @return $this
     *
     * @throws \Prophecy\Exception\InvalidArgumentException
     */
    public function withArguments($arguments)
    {
        if (is_array($arguments)) {
            $arguments = new Argument\ArgumentsWildcard($arguments);
        }

        if (!$arguments instanceof Argument\ArgumentsWildcard) {
            throw new InvalidArgumentException(sprintf(
                "Either an array or an instance of ArgumentsWildcard expected as\n".
                'a `MethodProphecy::withArguments()` argument, but got %s.',
                gettype($arguments)
            ));
        }

        $this->argumentsWildcard = $arguments;

        return $this;
    }

    /**
     * Sets custom promise to the prophecy.
     *
     * @param callable|Promise\PromiseInterface $promise
     *
     * @return $this
     *
     * @throws \Prophecy\Exception\InvalidArgumentException
     */
    public function will($promise)
    {
        if (is_callable($promise)) {
            $promise = new Promise\CallbackPromise($promise);
        }

        if (!$promise instanceof Promise\PromiseInterface) {
            throw new InvalidArgumentException(sprintf(
                'Expected callable or instance of PromiseInterface, but got %s.',
                gettype($promise)
            ));
        }

        $this->bindToObjectProphecy();
        $this->promise = $promise;

        return $this;
    }

    /**
     * Sets return promise to the prophecy.
     *
     * @see \Prophecy\Promise\ReturnPromise
     *
     * @return $this
     */
    public function willReturn()
    {
        if ($this->voidReturnType) {
            throw new MethodProphecyException(
                "The method \"$this->methodName\" has a void return type, and so cannot return anything",
                $this
            );
        }

        return $this->will(new Promise\ReturnPromise(func_get_args()));
    }

    /**
     * Sets return argument promise to the prophecy.
     *
     * @param int $index The zero-indexed number of the argument to return
     *
     * @see \Prophecy\Promise\ReturnArgumentPromise
     *
     * @return $this
     */
    public function willReturnArgument($index = 0)
    {
        if ($this->voidReturnType) {
            throw new MethodProphecyException("The method \"$this->methodName\" has a void return type", $this);
        }

        return $this->will(new Promise\ReturnArgumentPromise($index));
    }

    /**
     * Sets throw promise to the prophecy.
     *
     * @see \Prophecy\Promise\ThrowPromise
     *
     * @param string|\Exception $exception Exception class or instance
     *
     * @return $this
     */
    public function willThrow($exception)
    {
        return $this->will(new Promise\ThrowPromise($exception));
    }

    /**
     * Sets custom prediction to the prophecy.
     *
     * @param callable|Prediction\PredictionInterface $prediction
     *
     * @return $this
     *
     * @throws \Prophecy\Exception\InvalidArgumentException
     */
    public function should($prediction)
    {
        if (is_callable($prediction)) {
            $prediction = new Prediction\CallbackPrediction($prediction);
        }

        if (!$prediction instanceof Prediction\PredictionInterface) {
            throw new InvalidArgumentException(sprintf(
                'Expected callable or instance of PredictionInterface, but got %s.',
                gettype($prediction)
            ));
        }

        $this->bindToObjectProphecy();
        $this->prediction = $prediction;

        return $this;
    }

    /**
     * Sets call prediction to the prophecy.
     *
     * @see \Prophecy\Prediction\CallPrediction
     *
     * @return $this
     */
    public function shouldBeCalled()
    {
        return $this->should(new Prediction\CallPrediction);
    }

    /**
     * Sets no calls prediction to the prophecy.
     *
     * @see \Prophecy\Prediction\NoCallsPrediction
     *
     * @return $this
     */
    public function shouldNotBeCalled()
    {
        return $this->should(new Prediction\NoCallsPrediction);
    }

    /**
     * Sets call times prediction to the prophecy.
     *
     * @see \Prophecy\Prediction\CallTimesPrediction
     *
     * @param $count
     *
     * @return $this
     */
    public function shouldBeCalledTimes($count)
    {
        return $this->should(new Prediction\CallTimesPrediction($count));
    }

    /**
     * Sets call times prediction to the prophecy.
     *
     * @see \Prophecy\Prediction\CallTimesPrediction
     *
     * @return $this
     */
    public function shouldBeCalledOnce()
    {
        return $this->shouldBeCalledTimes(1);
    }

    /**
     * Checks provided prediction immediately.
     *
     * @param callable|Prediction\PredictionInterface $prediction
     *
     * @return $this
     *
     * @throws \Prophecy\Exception\InvalidArgumentException
     */
    public function shouldHave($prediction)
    {
        if (is_callable($prediction)) {
            $prediction = new Prediction\CallbackPrediction($prediction);
        }

        if (!$prediction instanceof Prediction\PredictionInterface) {
            throw new InvalidArgumentException(sprintf(
                'Expected callable or instance of PredictionInterface, but got %s.',
                gettype($prediction)
            ));
        }

        if (null === $this->promise && !$this->voidReturnType) {
            $this->willReturn();
        }

        $calls = $this->getObjectProphecy()->findProphecyMethodCalls(
            $this->getMethodName(),
            $this->getArgumentsWildcard()
        );

        try {
            $prediction->check($calls, $this->getObjectProphecy(), $this);
            $this->checkedPredictions[] = $prediction;
        } catch (\Exception $e) {
            $this->checkedPredictions[] = $prediction;

            throw $e;
        }

        return $this;
    }

    /**
     * Checks call prediction.
     *
     * @see \Prophecy\Prediction\CallPrediction
     *
     * @return $this
     */
    public function shouldHaveBeenCalled()
    {
        return $this->shouldHave(new Prediction\CallPrediction);
    }

    /**
     * Checks no calls prediction.
     *
     * @see \Prophecy\Prediction\NoCallsPrediction
     *
     * @return $this
     */
    public function shouldNotHaveBeenCalled()
    {
        return $this->shouldHave(new Prediction\NoCallsPrediction);
    }

    /**
     * Checks no calls prediction.
     *
     * @see \Prophecy\Prediction\NoCallsPrediction
     * @deprecated
     *
     * @return $this
     */
    public function shouldNotBeenCalled()
    {
        return $this->shouldNotHaveBeenCalled();
    }

    /**
     * Checks call times prediction.
     *
     * @see \Prophecy\Prediction\CallTimesPrediction
     *
     * @param int $count
     *
     * @return $this
     */
    public function shouldHaveBeenCalledTimes($count)
    {
        return $this->shouldHave(new Prediction\CallTimesPrediction($count));
    }

    /**
     * Checks call times prediction.
     *
     * @see \Prophecy\Prediction\CallTimesPrediction
     *
     * @return $this
     */
    public function shouldHaveBeenCalledOnce()
    {
        return $this->shouldHaveBeenCalledTimes(1);
    }

    /**
     * Checks currently registered [with should(...)] prediction.
     */
    public function checkPrediction()
    {
        if (null === $this->prediction) {
            return;
        }

        $this->shouldHave($this->prediction);
    }

    /**
     * Returns currently registered promise.
     *
     * @return null|Promise\PromiseInterface
     */
    public function getPromise()
    {
        return $this->promise;
    }

    /**
     * Returns currently registered prediction.
     *
     * @return null|Prediction\PredictionInterface
     */
    public function getPrediction()
    {
        return $this->prediction;
    }

    /**
     * Returns predictions that were checked on this object.
     *
     * @return Prediction\PredictionInterface[]
     */
    public function getCheckedPredictions()
    {
        return $this->checkedPredictions;
    }

    /**
     * Returns object prophecy this method prophecy is tied to.
     *
     * @return ObjectProphecy
     */
    public function getObjectProphecy()
    {
        return $this->objectProphecy;
    }

    /**
     * Returns method name.
     *
     * @return string
     */
    public function getMethodName()
    {
        return $this->methodName;
    }

    /**
     * Returns arguments wildcard.
     *
     * @return Argument\ArgumentsWildcard
     */
    public function getArgumentsWildcard()
    {
        return $this->argumentsWildcard;
    }

    /**
     * @return bool
     */
    public function hasReturnVoid()
    {
        return $this->voidReturnType;
    }

    private function bindToObjectProphecy()
    {
        if ($this->bound) {
            return;
        }

        $this->getObjectProphecy()->addMethodProphecy($this);
        $this->bound = true;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Prophecy;

/**
 * Core Prophecy interface.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface ProphecyInterface
{
    /**
     * Reveals prophecy object (double) .
     *
     * @return object
     */
    public function reveal();
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Promise;

use Prophecy\Prophecy\ObjectProphecy;
use Prophecy\Prophecy\MethodProphecy;

/**
 * Promise interface.
 * Promises are logical blocks, tied to `will...` keyword.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface PromiseInterface
{
    /**
     * Evaluates promise.
     *
     * @param array          $args
     * @param ObjectProphecy $object
     * @param MethodProphecy $method
     *
     * @return mixed
     */
    public function execute(array $args, ObjectProphecy $object, MethodProphecy $method);
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Promise;

use Prophecy\Exception\InvalidArgumentException;
use Prophecy\Prophecy\ObjectProphecy;
use Prophecy\Prophecy\MethodProphecy;

/**
 * Return argument promise.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class ReturnArgumentPromise implements PromiseInterface
{
    /**
     * @var int
     */
    private $index;

    /**
     * Initializes callback promise.
     *
     * @param int $index The zero-indexed number of the argument to return
     *
     * @throws \Prophecy\Exception\InvalidArgumentException
     */
    public function __construct($index = 0)
    {
        if (!is_int($index) || $index < 0) {
            throw new InvalidArgumentException(sprintf(
                'Zero-based index expected as argument to ReturnArgumentPromise, but got %s.',
                $index
            ));
        }
        $this->index = $index;
    }

    /**
     * Returns nth argument if has one, null otherwise.
     *
     * @param array          $args
     * @param ObjectProphecy $object
     * @param MethodProphecy $method
     *
     * @return null|mixed
     */
    public function execute(array $args, ObjectProphecy $object, MethodProphecy $method)
    {
        return count($args) > $this->index ? $args[$this->index] : null;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Promise;

use Prophecy\Prophecy\ObjectProphecy;
use Prophecy\Prophecy\MethodProphecy;
use Prophecy\Exception\InvalidArgumentException;
use Closure;

/**
 * Callback promise.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class CallbackPromise implements PromiseInterface
{
    private $callback;

    /**
     * Initializes callback promise.
     *
     * @param callable $callback Custom callback
     *
     * @throws \Prophecy\Exception\InvalidArgumentException
     */
    public function __construct($callback)
    {
        if (!is_callable($callback)) {
            throw new InvalidArgumentException(sprintf(
                'Callable expected as an argument to CallbackPromise, but got %s.',
                gettype($callback)
            ));
        }

        $this->callback = $callback;
    }

    /**
     * Evaluates promise callback.
     *
     * @param array          $args
     * @param ObjectProphecy $object
     * @param MethodProphecy $method
     *
     * @return mixed
     */
    public function execute(array $args, ObjectProphecy $object, MethodProphecy $method)
    {
        $callback = $this->callback;

        if ($callback instanceof Closure && method_exists('Closure', 'bind')) {
            $callback = Closure::bind($callback, $object);
        }

        return call_user_func($callback, $args, $object, $method);
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Promise;

use Prophecy\Prophecy\ObjectProphecy;
use Prophecy\Prophecy\MethodProphecy;

/**
 * Return promise.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class ReturnPromise implements PromiseInterface
{
    private $returnValues = array();

    /**
     * Initializes promise.
     *
     * @param array $returnValues Array of values
     */
    public function __construct(array $returnValues)
    {
        $this->returnValues = $returnValues;
    }

    /**
     * Returns saved values one by one until last one, then continuously returns last value.
     *
     * @param array          $args
     * @param ObjectProphecy $object
     * @param MethodProphecy $method
     *
     * @return mixed
     */
    public function execute(array $args, ObjectProphecy $object, MethodProphecy $method)
    {
        $value = array_shift($this->returnValues);

        if (!count($this->returnValues)) {
            $this->returnValues[] = $value;
        }

        return $value;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Promise;

use Doctrine\Instantiator\Instantiator;
use Prophecy\Prophecy\ObjectProphecy;
use Prophecy\Prophecy\MethodProphecy;
use Prophecy\Exception\InvalidArgumentException;
use ReflectionClass;

/**
 * Throw promise.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class ThrowPromise implements PromiseInterface
{
    private $exception;

    /**
     * @var \Doctrine\Instantiator\Instantiator
     */
    private $instantiator;

    /**
     * Initializes promise.
     *
     * @param string|\Exception|\Throwable $exception Exception class name or instance
     *
     * @throws \Prophecy\Exception\InvalidArgumentException
     */
    public function __construct($exception)
    {
        if (is_string($exception)) {
            if (!class_exists($exception) || !$this->isAValidThrowable($exception)) {
                throw new InvalidArgumentException(sprintf(
                    'Exception / Throwable class or instance expected as argument to ThrowPromise, but got %s.',
                    $exception
                ));
            }
        } elseif (!$exception instanceof \Exception && !$exception instanceof \Throwable) {
            throw new InvalidArgumentException(sprintf(
                'Exception / Throwable class or instance expected as argument to ThrowPromise, but got %s.',
                is_object($exception) ? get_class($exception) : gettype($exception)
            ));
        }

        $this->exception = $exception;
    }

    /**
     * Throws predefined exception.
     *
     * @param array          $args
     * @param ObjectProphecy $object
     * @param MethodProphecy $method
     *
     * @throws object
     */
    public function execute(array $args, ObjectProphecy $object, MethodProphecy $method)
    {
        if (is_string($this->exception)) {
            $classname   = $this->exception;
            $reflection  = new ReflectionClass($classname);
            $constructor = $reflection->getConstructor();

            if ($constructor->isPublic() && 0 == $constructor->getNumberOfRequiredParameters()) {
                throw $reflection->newInstance();
            }

            if (!$this->instantiator) {
                $this->instantiator = new Instantiator();
            }

            throw $this->instantiator->instantiate($classname);
        }

        throw $this->exception;
    }

    /**
     * @param string $exception
     *
     * @return bool
     */
    private function isAValidThrowable($exception)
    {
        return is_a($exception, 'Exception', true) || is_subclass_of($exception, 'Throwable', true);
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Exception\Doubler;

class MethodNotFoundException extends DoubleException
{
    /**
     * @var string|object
     */
    private $classname;

    /**
     * @var string
     */
    private $methodName;

    /**
     * @var array
     */
    private $arguments;

    /**
     * @param string $message
     * @param string|object $classname
     * @param string $methodName
     * @param null|Argument\ArgumentsWildcard|array $arguments
     */
    public function __construct($message, $classname, $methodName, $arguments = null)
    {
        parent::__construct($message);

        $this->classname  = $classname;
        $this->methodName = $methodName;
        $this->arguments = $arguments;
    }

    public function getClassname()
    {
        return $this->classname;
    }

    public function getMethodName()
    {
        return $this->methodName;
    }

    public function getArguments()
    {
        return $this->arguments;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Exception\Doubler;

class InterfaceNotFoundException extends ClassNotFoundException
{
    public function getInterfaceName()
    {
        return $this->getClassname();
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Exception\Doubler;

class ReturnByReferenceException extends DoubleException
{
    private $classname;
    private $methodName;

    /**
     * @param string $message
     * @param string $classname
     * @param string $methodName
     */
    public function __construct($message, $classname, $methodName)
    {
        parent::__construct($message);

        $this->classname  = $classname;
        $this->methodName = $methodName;
    }

    public function getClassname()
    {
        return $this->classname;
    }

    public function getMethodName()
    {
        return $this->methodName;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Exception\Doubler;

use Prophecy\Exception\Exception;

interface DoublerException extends Exception
{
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Exception\Doubler;

use Prophecy\Doubler\Generator\Node\ClassNode;

class ClassCreatorException extends \RuntimeException implements DoublerException
{
    private $node;

    public function __construct($message, ClassNode $node)
    {
        parent::__construct($message);

        $this->node = $node;
    }

    public function getClassNode()
    {
        return $this->node;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Exception\Doubler;

use RuntimeException;

class DoubleException extends RuntimeException implements DoublerException
{
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Exception\Doubler;

class ClassNotFoundException extends DoubleException
{
    private $classname;

    /**
     * @param string $message
     * @param string $classname
     */
    public function __construct($message, $classname)
    {
        parent::__construct($message);

        $this->classname = $classname;
    }

    public function getClassname()
    {
        return $this->classname;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Exception\Doubler;

use ReflectionClass;

class ClassMirrorException extends \RuntimeException implements DoublerException
{
    private $class;

    public function __construct($message, ReflectionClass $class)
    {
        parent::__construct($message);

        $this->class = $class;
    }

    public function getReflectedClass()
    {
        return $this->class;
    }
}
<?php

    namespace Prophecy\Exception\Doubler;

    class MethodNotExtendableException extends DoubleException
    {
        private $methodName;

        private $className;

        /**
         * @param string $message
         * @param string $className
         * @param string $methodName
         */
        public function __construct($message, $className, $methodName)
        {
            parent::__construct($message);

            $this->methodName = $methodName;
            $this->className = $className;
        }


        /**
         * @return string
         */
        public function getMethodName()
        {
            return $this->methodName;
        }

        /**
         * @return string
         */
        public function getClassName()
        {
            return $this->className;
        }

    }
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Exception\Prediction;

use Prophecy\Prophecy\MethodProphecy;
use Prophecy\Exception\Prophecy\MethodProphecyException;

class UnexpectedCallsException extends MethodProphecyException implements PredictionException
{
    private $calls = array();

    public function __construct($message, MethodProphecy $methodProphecy, array $calls)
    {
        parent::__construct($message, $methodProphecy);

        $this->calls = $calls;
    }

    public function getCalls()
    {
        return $this->calls;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Exception\Prediction;

use Prophecy\Prophecy\MethodProphecy;

class UnexpectedCallsCountException extends UnexpectedCallsException
{
    private $expectedCount;

    public function __construct($message, MethodProphecy $methodProphecy, $count, array $calls)
    {
        parent::__construct($message, $methodProphecy, $calls);

        $this->expectedCount = intval($count);
    }

    public function getExpectedCount()
    {
        return $this->expectedCount;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Exception\Prediction;

use Prophecy\Exception\Exception;

interface PredictionException extends Exception
{
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Exception\Prediction;

use RuntimeException;

/**
 * Basic failed prediction exception.
 * Use it for custom prediction failures.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class FailedPredictionException extends RuntimeException implements PredictionException
{
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Exception\Prediction;

use Prophecy\Exception\Prophecy\MethodProphecyException;

class NoCallsException extends MethodProphecyException implements PredictionException
{
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Exception\Prediction;

use Prophecy\Prophecy\ObjectProphecy;

class AggregateException extends \RuntimeException implements PredictionException
{
    private $exceptions = array();
    private $objectProphecy;

    public function append(PredictionException $exception)
    {
        $message = $exception->getMessage();
        $message = strtr($message, array("\n" => "\n  "))."\n";
        $message = empty($this->exceptions) ? $message : "\n" . $message;

        $this->message      = rtrim($this->message.$message);
        $this->exceptions[] = $exception;
    }

    /**
     * @return PredictionException[]
     */
    public function getExceptions()
    {
        return $this->exceptions;
    }

    public function setObjectProphecy(ObjectProphecy $objectProphecy)
    {
        $this->objectProphecy = $objectProphecy;
    }

    /**
     * @return ObjectProphecy
     */
    public function getObjectProphecy()
    {
        return $this->objectProphecy;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Exception;

class InvalidArgumentException extends \InvalidArgumentException implements Exception
{
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Exception\Call;

use Prophecy\Exception\Prophecy\ObjectProphecyException;
use Prophecy\Prophecy\ObjectProphecy;

class UnexpectedCallException extends ObjectProphecyException
{
    private $methodName;
    private $arguments;

    public function __construct($message, ObjectProphecy $objectProphecy,
                                $methodName, array $arguments)
    {
        parent::__construct($message, $objectProphecy);

        $this->methodName = $methodName;
        $this->arguments = $arguments;
    }

    public function getMethodName()
    {
        return $this->methodName;
    }

    public function getArguments()
    {
        return $this->arguments;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Exception\Prophecy;

use Prophecy\Prophecy\MethodProphecy;

class MethodProphecyException extends ObjectProphecyException
{
    private $methodProphecy;

    public function __construct($message, MethodProphecy $methodProphecy)
    {
        parent::__construct($message, $methodProphecy->getObjectProphecy());

        $this->methodProphecy = $methodProphecy;
    }

    /**
     * @return MethodProphecy
     */
    public function getMethodProphecy()
    {
        return $this->methodProphecy;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Exception\Prophecy;

use Prophecy\Exception\Exception;

interface ProphecyException extends Exception
{
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Exception\Prophecy;

use Prophecy\Prophecy\ObjectProphecy;

class ObjectProphecyException extends \RuntimeException implements ProphecyException
{
    private $objectProphecy;

    public function __construct($message, ObjectProphecy $objectProphecy)
    {
        parent::__construct($message);

        $this->objectProphecy = $objectProphecy;
    }

    /**
     * @return ObjectProphecy
     */
    public function getObjectProphecy()
    {
        return $this->objectProphecy;
    }
}
<?php

/*
 * This file is part of the Prophecy.
 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
 *     Marcello Duarte <marcello.duarte@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Prophecy\Exception;

/**
 * Core Prophecy exception interface.
 * All Prophecy exceptions implement it.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface Exception
{
    /**
     * @return string
     */
    public function getMessage();
}
{
    "name": "react/promise",
    "description": "A lightweight implementation of CommonJS Promises/A for PHP",
    "license": "MIT",
    "authors": [
        {"name": "Jan Sorgalla", "email": "jsorgalla@gmail.com"}
    ],
    "require": {
        "php": ">=5.4.0"
    },
    "require-dev": {
        "phpunit/phpunit": "~4.8"
    },
    "autoload": {
        "psr-4": {
            "React\\Promise\\": "src/"
        },
        "files": ["src/functions_include.php"]
    },
    "autoload-dev": {
        "psr-4": {
            "React\\Promise\\": "tests/fixtures"
        }
    },
    "keywords": [
        "promise",
        "promises"
    ]
}
<?php

namespace React\Promise;

interface PromiseInterface
{
    /**
     *
     * The `$onProgress` argument is deprecated and should not be used anymore.
     *
     * @return PromiseInterface
     */
    public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null);
}
<?php

namespace React\Promise;

function resolve($promiseOrValue = null)
{
    if ($promiseOrValue instanceof ExtendedPromiseInterface) {
        return $promiseOrValue;
    }

    // Check is_object() first to avoid method_exists() triggering
    // class autoloaders if $promiseOrValue is a string.
    if (\is_object($promiseOrValue) && \method_exists($promiseOrValue, 'then')) {
        $canceller = null;

        if (\method_exists($promiseOrValue, 'cancel')) {
            $canceller = [$promiseOrValue, 'cancel'];
        }

        return new Promise(function ($resolve, $reject, $notify) use ($promiseOrValue) {
            $promiseOrValue->then($resolve, $reject, $notify);
        }, $canceller);
    }

    return new FulfilledPromise($promiseOrValue);
}

function reject($promiseOrValue = null)
{
    if ($promiseOrValue instanceof PromiseInterface) {
        return resolve($promiseOrValue)->then(function ($value) {
            return new RejectedPromise($value);
        });
    }

    return new RejectedPromise($promiseOrValue);
}

function all($promisesOrValues)
{
    return map($promisesOrValues, function ($val) {
        return $val;
    });
}

function race($promisesOrValues)
{
    $cancellationQueue = new CancellationQueue();
    $cancellationQueue->enqueue($promisesOrValues);

    return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $cancellationQueue) {
        resolve($promisesOrValues)
            ->done(function ($array) use ($cancellationQueue, $resolve, $reject, $notify) {
                if (!is_array($array) || !$array) {
                    $resolve();
                    return;
                }

                foreach ($array as $promiseOrValue) {
                    $cancellationQueue->enqueue($promiseOrValue);

                    resolve($promiseOrValue)
                        ->done($resolve, $reject, $notify);
                }
            }, $reject, $notify);
    }, $cancellationQueue);
}

function any($promisesOrValues)
{
    return some($promisesOrValues, 1)
        ->then(function ($val) {
            return \array_shift($val);
        });
}

function some($promisesOrValues, $howMany)
{
    $cancellationQueue = new CancellationQueue();
    $cancellationQueue->enqueue($promisesOrValues);

    return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $howMany, $cancellationQueue) {
        resolve($promisesOrValues)
            ->done(function ($array) use ($howMany, $cancellationQueue, $resolve, $reject, $notify) {
                if (!\is_array($array) || $howMany < 1) {
                    $resolve([]);
                    return;
                }

                $len = \count($array);

                if ($len < $howMany) {
                    throw new Exception\LengthException(
                        \sprintf(
                            'Input array must contain at least %d item%s but contains only %s item%s.',
                            $howMany,
                            1 === $howMany ? '' : 's',
                            $len,
                            1 === $len ? '' : 's'
                        )
                    );
                }

                $toResolve = $howMany;
                $toReject  = ($len - $toResolve) + 1;
                $values    = [];
                $reasons   = [];

                foreach ($array as $i => $promiseOrValue) {
                    $fulfiller = function ($val) use ($i, &$values, &$toResolve, $toReject, $resolve) {
                        if ($toResolve < 1 || $toReject < 1) {
                            return;
                        }

                        $values[$i] = $val;

                        if (0 === --$toResolve) {
                            $resolve($values);
                        }
                    };

                    $rejecter = function ($reason) use ($i, &$reasons, &$toReject, $toResolve, $reject) {
                        if ($toResolve < 1 || $toReject < 1) {
                            return;
                        }

                        $reasons[$i] = $reason;

                        if (0 === --$toReject) {
                            $reject($reasons);
                        }
                    };

                    $cancellationQueue->enqueue($promiseOrValue);

                    resolve($promiseOrValue)
                        ->done($fulfiller, $rejecter, $notify);
                }
            }, $reject, $notify);
    }, $cancellationQueue);
}

function map($promisesOrValues, callable $mapFunc)
{
    $cancellationQueue = new CancellationQueue();
    $cancellationQueue->enqueue($promisesOrValues);

    return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $mapFunc, $cancellationQueue) {
        resolve($promisesOrValues)
            ->done(function ($array) use ($mapFunc, $cancellationQueue, $resolve, $reject, $notify) {
                if (!\is_array($array) || !$array) {
                    $resolve([]);
                    return;
                }

                $toResolve = \count($array);
                $values    = [];

                foreach ($array as $i => $promiseOrValue) {
                    $cancellationQueue->enqueue($promiseOrValue);
                    $values[$i] = null;

                    resolve($promiseOrValue)
                        ->then($mapFunc)
                        ->done(
                            function ($mapped) use ($i, &$values, &$toResolve, $resolve) {
                                $values[$i] = $mapped;

                                if (0 === --$toResolve) {
                                    $resolve($values);
                                }
                            },
                            $reject,
                            $notify
                        );
                }
            }, $reject, $notify);
    }, $cancellationQueue);
}

function reduce($promisesOrValues, callable $reduceFunc, $initialValue = null)
{
    $cancellationQueue = new CancellationQueue();
    $cancellationQueue->enqueue($promisesOrValues);

    return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $reduceFunc, $initialValue, $cancellationQueue) {
        resolve($promisesOrValues)
            ->done(function ($array) use ($reduceFunc, $initialValue, $cancellationQueue, $resolve, $reject, $notify) {
                if (!\is_array($array)) {
                    $array = [];
                }

                $total = \count($array);
                $i = 0;

                // Wrap the supplied $reduceFunc with one that handles promises and then
                // delegates to the supplied.
                $wrappedReduceFunc = function ($current, $val) use ($reduceFunc, $cancellationQueue, $total, &$i) {
                    $cancellationQueue->enqueue($val);

                    return $current
                        ->then(function ($c) use ($reduceFunc, $total, &$i, $val) {
                            return resolve($val)
                                ->then(function ($value) use ($reduceFunc, $total, &$i, $c) {
                                    return $reduceFunc($c, $value, $i++, $total);
                                });
                        });
                };

                $cancellationQueue->enqueue($initialValue);

                \array_reduce($array, $wrappedReduceFunc, resolve($initialValue))
                    ->done($resolve, $reject, $notify);
            }, $reject, $notify);
    }, $cancellationQueue);
}

// Internal functions
function _checkTypehint(callable $callback, $object)
{
    if (!\is_object($object)) {
        return true;
    }

    if (\is_array($callback)) {
        $callbackReflection = new \ReflectionMethod($callback[0], $callback[1]);
    } elseif (\is_object($callback) && !$callback instanceof \Closure) {
        $callbackReflection = new \ReflectionMethod($callback, '__invoke');
    } else {
        $callbackReflection = new \ReflectionFunction($callback);
    }

    $parameters = $callbackReflection->getParameters();

    if (!isset($parameters[0])) {
        return true;
    }

    $expectedException = $parameters[0];

    if (!$expectedException->getClass()) {
        return true;
    }

    return $expectedException->getClass()->isInstance($object);
}
<?php

if (!\function_exists('React\Promise\resolve')) {
    require __DIR__.'/functions.php';
}
<?php

namespace React\Promise;

interface CancellablePromiseInterface extends PromiseInterface
{
    /**
     * @return void
     */
    public function cancel();
}
<?php

namespace React\Promise;

class Promise implements ExtendedPromiseInterface, CancellablePromiseInterface
{
    private $canceller;
    private $result;

    private $handlers = [];
    private $progressHandlers = [];

    private $requiredCancelRequests = 0;
    private $cancelRequests = 0;

    public function __construct(callable $resolver, callable $canceller = null)
    {
        $this->canceller = $canceller;

        // Explicitly overwrite arguments with null values before invoking
        // resolver function. This ensure that these arguments do not show up
        // in the stack trace in PHP 7+ only.
        $cb = $resolver;
        $resolver = $canceller = null;
        $this->call($cb);
    }

    public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
    {
        if (null !== $this->result) {
            return $this->result->then($onFulfilled, $onRejected, $onProgress);
        }

        if (null === $this->canceller) {
            return new static($this->resolver($onFulfilled, $onRejected, $onProgress));
        }

        // This promise has a canceller, so we create a new child promise which
        // has a canceller that invokes the parent canceller if all other
        // followers are also cancelled. We keep a reference to this promise
        // instance for the static canceller function and clear this to avoid
        // keeping a cyclic reference between parent and follower.
        $parent = $this;
        ++$parent->requiredCancelRequests;

        return new static(
            $this->resolver($onFulfilled, $onRejected, $onProgress),
            static function () use (&$parent) {
                if (++$parent->cancelRequests >= $parent->requiredCancelRequests) {
                    $parent->cancel();
                }

                $parent = null;
            }
        );
    }

    public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
    {
        if (null !== $this->result) {
            return $this->result->done($onFulfilled, $onRejected, $onProgress);
        }

        $this->handlers[] = static function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected) {
            $promise
                ->done($onFulfilled, $onRejected);
        };

        if ($onProgress) {
            $this->progressHandlers[] = $onProgress;
        }
    }

    public function otherwise(callable $onRejected)
    {
        return $this->then(null, static function ($reason) use ($onRejected) {
            if (!_checkTypehint($onRejected, $reason)) {
                return new RejectedPromise($reason);
            }

            return $onRejected($reason);
        });
    }

    public function always(callable $onFulfilledOrRejected)
    {
        return $this->then(static function ($value) use ($onFulfilledOrRejected) {
            return resolve($onFulfilledOrRejected())->then(function () use ($value) {
                return $value;
            });
        }, static function ($reason) use ($onFulfilledOrRejected) {
            return resolve($onFulfilledOrRejected())->then(function () use ($reason) {
                return new RejectedPromise($reason);
            });
        });
    }

    public function progress(callable $onProgress)
    {
        return $this->then(null, null, $onProgress);
    }

    public function cancel()
    {
        if (null === $this->canceller || null !== $this->result) {
            return;
        }

        $canceller = $this->canceller;
        $this->canceller = null;

        $this->call($canceller);
    }

    private function resolver(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
    {
        return function ($resolve, $reject, $notify) use ($onFulfilled, $onRejected, $onProgress) {
            if ($onProgress) {
                $progressHandler = static function ($update) use ($notify, $onProgress) {
                    try {
                        $notify($onProgress($update));
                    } catch (\Throwable $e) {
                        $notify($e);
                    } catch (\Exception $e) {
                        $notify($e);
                    }
                };
            } else {
                $progressHandler = $notify;
            }

            $this->handlers[] = static function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject, $progressHandler) {
                $promise
                    ->then($onFulfilled, $onRejected)
                    ->done($resolve, $reject, $progressHandler);
            };

            $this->progressHandlers[] = $progressHandler;
        };
    }

    private function reject($reason = null)
    {
        if (null !== $this->result) {
            return;
        }

        $this->settle(reject($reason));
    }

    private function settle(ExtendedPromiseInterface $promise)
    {
        $promise = $this->unwrap($promise);

        if ($promise === $this) {
            $promise = new RejectedPromise(
                new \LogicException('Cannot resolve a promise with itself.')
            );
        }

        $handlers = $this->handlers;

        $this->progressHandlers = $this->handlers = [];
        $this->result = $promise;
        $this->canceller = null;

        foreach ($handlers as $handler) {
            $handler($promise);
        }
    }

    private function unwrap($promise)
    {
        $promise = $this->extract($promise);

        while ($promise instanceof self && null !== $promise->result) {
            $promise = $this->extract($promise->result);
        }

        return $promise;
    }

    private function extract($promise)
    {
        if ($promise instanceof LazyPromise) {
            $promise = $promise->promise();
        }

        return $promise;
    }

    private function call(callable $cb)
    {
        // Explicitly overwrite argument with null value. This ensure that this
        // argument does not show up in the stack trace in PHP 7+ only.
        $callback = $cb;
        $cb = null;

        // Use reflection to inspect number of arguments expected by this callback.
        // We did some careful benchmarking here: Using reflection to avoid unneeded
        // function arguments is actually faster than blindly passing them.
        // Also, this helps avoiding unnecessary function arguments in the call stack
        // if the callback creates an Exception (creating garbage cycles).
        if (\is_array($callback)) {
            $ref = new \ReflectionMethod($callback[0], $callback[1]);
        } elseif (\is_object($callback) && !$callback instanceof \Closure) {
            $ref = new \ReflectionMethod($callback, '__invoke');
        } else {
            $ref = new \ReflectionFunction($callback);
        }
        $args = $ref->getNumberOfParameters();

        try {
            if ($args === 0) {
                $callback();
            } else {
                // Keep references to this promise instance for the static resolve/reject functions.
                // By using static callbacks that are not bound to this instance
                // and passing the target promise instance by reference, we can
                // still execute its resolving logic and still clear this
                // reference when settling the promise. This helps avoiding
                // garbage cycles if any callback creates an Exception.
                // These assumptions are covered by the test suite, so if you ever feel like
                // refactoring this, go ahead, any alternative suggestions are welcome!
                $target =& $this;
                $progressHandlers =& $this->progressHandlers;

                $callback(
                    static function ($value = null) use (&$target) {
                        if ($target !== null) {
                            $target->settle(resolve($value));
                            $target = null;
                        }
                    },
                    static function ($reason = null) use (&$target) {
                        if ($target !== null) {
                            $target->reject($reason);
                            $target = null;
                        }
                    },
                    static function ($update = null) use (&$progressHandlers) {
                        foreach ($progressHandlers as $handler) {
                            $handler($update);
                        }
                    }
                );
            }
        } catch (\Throwable $e) {
            $target = null;
            $this->reject($e);
        } catch (\Exception $e) {
            $target = null;
            $this->reject($e);
        }
    }
}
<?php

namespace React\Promise;

class Deferred implements PromisorInterface
{
    private $promise;
    private $resolveCallback;
    private $rejectCallback;
    private $notifyCallback;
    private $canceller;

    public function __construct(callable $canceller = null)
    {
        $this->canceller = $canceller;
    }

    public function promise()
    {
        if (null === $this->promise) {
            $this->promise = new Promise(function ($resolve, $reject, $notify) {
                $this->resolveCallback = $resolve;
                $this->rejectCallback  = $reject;
                $this->notifyCallback  = $notify;
            }, $this->canceller);
            $this->canceller = null;
        }

        return $this->promise;
    }

    public function resolve($value = null)
    {
        $this->promise();

        \call_user_func($this->resolveCallback, $value);
    }

    public function reject($reason = null)
    {
        $this->promise();

        \call_user_func($this->rejectCallback, $reason);
    }

    /**
     * @deprecated 2.6.0 Progress support is deprecated and should not be used anymore.
     * @param mixed $update
     */
    public function notify($update = null)
    {
        $this->promise();

        \call_user_func($this->notifyCallback, $update);
    }

    /**
     * @deprecated 2.2.0
     * @see Deferred::notify()
     */
    public function progress($update = null)
    {
        $this->notify($update);
    }
}
<?php

namespace React\Promise;

interface PromisorInterface
{
    /**
     * @return PromiseInterface
     */
    public function promise();
}
<?php

namespace React\Promise;

class RejectedPromise implements ExtendedPromiseInterface, CancellablePromiseInterface
{
    private $reason;

    public function __construct($reason = null)
    {
        if ($reason instanceof PromiseInterface) {
            throw new \InvalidArgumentException('You cannot create React\Promise\RejectedPromise with a promise. Use React\Promise\reject($promiseOrValue) instead.');
        }

        $this->reason = $reason;
    }

    public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
    {
        if (null === $onRejected) {
            return $this;
        }

        try {
            return resolve($onRejected($this->reason));
        } catch (\Throwable $exception) {
            return new RejectedPromise($exception);
        } catch (\Exception $exception) {
            return new RejectedPromise($exception);
        }
    }

    public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
    {
        if (null === $onRejected) {
            throw UnhandledRejectionException::resolve($this->reason);
        }

        $result = $onRejected($this->reason);

        if ($result instanceof self) {
            throw UnhandledRejectionException::resolve($result->reason);
        }

        if ($result instanceof ExtendedPromiseInterface) {
            $result->done();
        }
    }

    public function otherwise(callable $onRejected)
    {
        if (!_checkTypehint($onRejected, $this->reason)) {
            return $this;
        }

        return $this->then(null, $onRejected);
    }

    public function always(callable $onFulfilledOrRejected)
    {
        return $this->then(null, function ($reason) use ($onFulfilledOrRejected) {
            return resolve($onFulfilledOrRejected())->then(function () use ($reason) {
                return new RejectedPromise($reason);
            });
        });
    }

    public function progress(callable $onProgress)
    {
        return $this;
    }

    public function cancel()
    {
    }
}
<?php

namespace React\Promise;

class UnhandledRejectionException extends \RuntimeException
{
    private $reason;

    public static function resolve($reason)
    {
        if ($reason instanceof \Exception || $reason instanceof \Throwable) {
            return $reason;
        }

        return new static($reason);
    }

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

        $message = \sprintf('Unhandled Rejection: %s', \json_encode($reason));

        parent::__construct($message, 0);
    }

    public function getReason()
    {
        return $this->reason;
    }
}
<?php

namespace React\Promise;

class LazyPromise implements ExtendedPromiseInterface, CancellablePromiseInterface
{
    private $factory;
    private $promise;

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

    public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
    {
        return $this->promise()->then($onFulfilled, $onRejected, $onProgress);
    }

    public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
    {
        return $this->promise()->done($onFulfilled, $onRejected, $onProgress);
    }

    public function otherwise(callable $onRejected)
    {
        return $this->promise()->otherwise($onRejected);
    }

    public function always(callable $onFulfilledOrRejected)
    {
        return $this->promise()->always($onFulfilledOrRejected);
    }

    public function progress(callable $onProgress)
    {
        return $this->promise()->progress($onProgress);
    }

    public function cancel()
    {
        return $this->promise()->cancel();
    }

    /**
     * @internal
     * @see Promise::settle()
     */
    public function promise()
    {
        if (null === $this->promise) {
            try {
                $this->promise = resolve(\call_user_func($this->factory));
            } catch (\Throwable $exception) {
                $this->promise = new RejectedPromise($exception);
            } catch (\Exception $exception) {
                $this->promise = new RejectedPromise($exception);
            }
        }

        return $this->promise;
    }
}
<?php

namespace React\Promise;

class FulfilledPromise implements ExtendedPromiseInterface, CancellablePromiseInterface
{
    private $value;

    public function __construct($value = null)
    {
        if ($value instanceof PromiseInterface) {
            throw new \InvalidArgumentException('You cannot create React\Promise\FulfilledPromise with a promise. Use React\Promise\resolve($promiseOrValue) instead.');
        }

        $this->value = $value;
    }

    public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
    {
        if (null === $onFulfilled) {
            return $this;
        }

        try {
            return resolve($onFulfilled($this->value));
        } catch (\Throwable $exception) {
            return new RejectedPromise($exception);
        } catch (\Exception $exception) {
            return new RejectedPromise($exception);
        }
    }

    public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
    {
        if (null === $onFulfilled) {
            return;
        }

        $result = $onFulfilled($this->value);

        if ($result instanceof ExtendedPromiseInterface) {
            $result->done();
        }
    }

    public function otherwise(callable $onRejected)
    {
        return $this;
    }

    public function always(callable $onFulfilledOrRejected)
    {
        return $this->then(function ($value) use ($onFulfilledOrRejected) {
            return resolve($onFulfilledOrRejected())->then(function () use ($value) {
                return $value;
            });
        });
    }

    public function progress(callable $onProgress)
    {
        return $this;
    }

    public function cancel()
    {
    }
}
<?php

namespace React\Promise;

class CancellationQueue
{
    private $started = false;
    private $queue = [];

    public function __invoke()
    {
        if ($this->started) {
            return;
        }

        $this->started = true;
        $this->drain();
    }

    public function enqueue($cancellable)
    {
        if (!\method_exists($cancellable, 'then') || !\method_exists($cancellable, 'cancel')) {
            return;
        }

        $length = \array_push($this->queue, $cancellable);

        if ($this->started && 1 === $length) {
            $this->drain();
        }
    }

    private function drain()
    {
        for ($i = key($this->queue); isset($this->queue[$i]); $i++) {
            $cancellable = $this->queue[$i];

            $exception = null;

            try {
                $cancellable->cancel();
            } catch (\Throwable $exception) {
            } catch (\Exception $exception) {
            }

            unset($this->queue[$i]);

            if ($exception) {
                throw $exception;
            }
        }

        $this->queue = [];
    }
}
<?php

namespace React\Promise\Exception;

class LengthException extends \LengthException
{
}
<?php

namespace React\Promise;

interface ExtendedPromiseInterface extends PromiseInterface
{
    /**
     *
     * The `$onProgress` argument is deprecated and should not be used anymore.
     *
     * @return void
     */
    public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null);

    /**
     * @return ExtendedPromiseInterface
     */
    public function otherwise(callable $onRejected);

    /**
     * @return ExtendedPromiseInterface
     */
    public function always(callable $onFulfilledOrRejected);

    /**
     * @return ExtendedPromiseInterface
     * @deprecated 2.6.0 Progress support is deprecated and should not be used anymore.
     */
    public function progress(callable $onProgress);
}
{
    "name": "sebastian/comparator",
    "description": "Provides the functionality to compare PHP values for equality",
    "keywords": ["comparator","compare","equality"],
    "homepage": "http://www.github.com/sebastianbergmann/comparator",
    "license": "BSD-3-Clause",
    "authors": [
        {
            "name": "Sebastian Bergmann",
            "email": "sebastian@phpunit.de"
        },
        {
            "name": "Jeff Welch",
            "email": "whatthejeff@gmail.com"
        },
        {
            "name": "Volker Dusch",
            "email": "github@wallbash.com"
        },
        {
            "name": "Bernhard Schussek",
            "email": "bschussek@2bepublished.at"
        }
    ],
    "require": {
        "php": ">=5.3.3",
        "sebastian/diff": "~1.2",
        "sebastian/exporter": "~1.2 || ~2.0"
    },
    "require-dev": {
        "phpunit/phpunit": "~4.4"
    },
    "autoload": {
        "classmap": [
            "src/"
        ]
    },
    "extra": {
        "branch-alias": {
            "dev-master": "1.2.x-dev"
        }
    }
}

<?php
/*
 * This file is part of the Comparator package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\Comparator;

/**
 * Compares arrays for equality.
 */
class ArrayComparator extends Comparator
{
    /**
     * Returns whether the comparator can compare two values.
     *
     * @param  mixed $expected The first value to compare
     * @param  mixed $actual   The second value to compare
     * @return bool
     */
    public function accepts($expected, $actual)
    {
        return is_array($expected) && is_array($actual);
    }

    /**
     * Asserts that two values are equal.
     *
     * @param mixed $expected     First value to compare
     * @param mixed $actual       Second value to compare
     * @param float $delta        Allowed numerical distance between two values to consider them equal
     * @param bool  $canonicalize Arrays are sorted before comparison when set to true
     * @param bool  $ignoreCase   Case is ignored when set to true
     * @param array $processed    List of already processed elements (used to prevent infinite recursion)
     *
     * @throws ComparisonFailure
     */
    public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false, array &$processed = array())
    {
        if ($canonicalize) {
            sort($expected);
            sort($actual);
        }

        $remaining = $actual;
        $expString = $actString = "Array (\n";
        $equal     = true;

        foreach ($expected as $key => $value) {
            unset($remaining[$key]);

            if (!array_key_exists($key, $actual)) {
                $expString .= sprintf(
                    "    %s => %s\n",
                    $this->exporter->export($key),
                    $this->exporter->shortenedExport($value)
                );

                $equal = false;

                continue;
            }

            try {
                $comparator = $this->factory->getComparatorFor($value, $actual[$key]);
                $comparator->assertEquals($value, $actual[$key], $delta, $canonicalize, $ignoreCase, $processed);

                $expString .= sprintf(
                    "    %s => %s\n",
                    $this->exporter->export($key),
                    $this->exporter->shortenedExport($value)
                );
                $actString .= sprintf(
                    "    %s => %s\n",
                    $this->exporter->export($key),
                    $this->exporter->shortenedExport($actual[$key])
                );
            } catch (ComparisonFailure $e) {
                $expString .= sprintf(
                    "    %s => %s\n",
                    $this->exporter->export($key),
                    $e->getExpectedAsString()
                    ? $this->indent($e->getExpectedAsString())
                    : $this->exporter->shortenedExport($e->getExpected())
                );

                $actString .= sprintf(
                    "    %s => %s\n",
                    $this->exporter->export($key),
                    $e->getActualAsString()
                    ? $this->indent($e->getActualAsString())
                    : $this->exporter->shortenedExport($e->getActual())
                );

                $equal = false;
            }
        }

        foreach ($remaining as $key => $value) {
            $actString .= sprintf(
                "    %s => %s\n",
                $this->exporter->export($key),
                $this->exporter->shortenedExport($value)
            );

            $equal = false;
        }

        $expString .= ')';
        $actString .= ')';

        if (!$equal) {
            throw new ComparisonFailure(
                $expected,
                $actual,
                $expString,
                $actString,
                false,
                'Failed asserting that two arrays are equal.'
            );
        }
    }

    protected function indent($lines)
    {
        return trim(str_replace("\n", "\n    ", $lines));
    }
}
<?php
/*
 * This file is part of the Comparator package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\Comparator;

/**
 * Compares values for type equality.
 */
class TypeComparator extends Comparator
{
    /**
     * Returns whether the comparator can compare two values.
     *
     * @param  mixed $expected The first value to compare
     * @param  mixed $actual   The second value to compare
     * @return bool
     */
    public function accepts($expected, $actual)
    {
        return true;
    }

    /**
     * Asserts that two values are equal.
     *
     * @param mixed $expected     First value to compare
     * @param mixed $actual       Second value to compare
     * @param float $delta        Allowed numerical distance between two values to consider them equal
     * @param bool  $canonicalize Arrays are sorted before comparison when set to true
     * @param bool  $ignoreCase   Case is ignored when set to true
     *
     * @throws ComparisonFailure
     */
    public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false)
    {
        if (gettype($expected) != gettype($actual)) {
            throw new ComparisonFailure(
                $expected,
                $actual,
                // we don't need a diff
                '',
                '',
                false,
                sprintf(
                    '%s does not match expected type "%s".',
                    $this->exporter->shortenedExport($actual),
                    gettype($expected)
                )
            );
        }
    }
}
<?php
/*
 * This file is part of the Comparator package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\Comparator;

/**
 * Factory for comparators which compare values for equality.
 */
class Factory
{
    /**
     * @var Comparator[]
     */
    private $comparators = array();

    /**
     * @var Factory
     */
    private static $instance;

    /**
     * Constructs a new factory.
     */
    public function __construct()
    {
        $this->register(new TypeComparator);
        $this->register(new ScalarComparator);
        $this->register(new NumericComparator);
        $this->register(new DoubleComparator);
        $this->register(new ArrayComparator);
        $this->register(new ResourceComparator);
        $this->register(new ObjectComparator);
        $this->register(new ExceptionComparator);
        $this->register(new SplObjectStorageComparator);
        $this->register(new DOMNodeComparator);
        $this->register(new MockObjectComparator);
        $this->register(new DateTimeComparator);
    }

    /**
     * @return Factory
     */
    public static function getInstance()
    {
        if (self::$instance === null) {
            self::$instance = new self;
        }

        return self::$instance;
    }

    /**
     * Returns the correct comparator for comparing two values.
     *
     * @param  mixed      $expected The first value to compare
     * @param  mixed      $actual   The second value to compare
     * @return Comparator
     */
    public function getComparatorFor($expected, $actual)
    {
        foreach ($this->comparators as $comparator) {
            if ($comparator->accepts($expected, $actual)) {
                return $comparator;
            }
        }
    }

    /**
     * Registers a new comparator.
     *
     * This comparator will be returned by getInstance() if its accept() method
     * returns TRUE for the compared values. It has higher priority than the
     * existing comparators, meaning that its accept() method will be tested
     * before those of the other comparators.
     *
     * @param Comparator $comparator The registered comparator
     */
    public function register(Comparator $comparator)
    {
        array_unshift($this->comparators, $comparator);

        $comparator->setFactory($this);
    }

    /**
     * Unregisters a comparator.
     *
     * This comparator will no longer be returned by getInstance().
     *
     * @param Comparator $comparator The unregistered comparator
     */
    public function unregister(Comparator $comparator)
    {
        foreach ($this->comparators as $key => $_comparator) {
            if ($comparator === $_comparator) {
                unset($this->comparators[$key]);
            }
        }
    }
}
<?php
/*
 * This file is part of the Comparator package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\Comparator;

/**
 * Compares resources for equality.
 */
class ResourceComparator extends Comparator
{
    /**
     * Returns whether the comparator can compare two values.
     *
     * @param  mixed $expected The first value to compare
     * @param  mixed $actual   The second value to compare
     * @return bool
     */
    public function accepts($expected, $actual)
    {
        return is_resource($expected) && is_resource($actual);
    }

    /**
     * Asserts that two values are equal.
     *
     * @param mixed $expected     First value to compare
     * @param mixed $actual       Second value to compare
     * @param float $delta        Allowed numerical distance between two values to consider them equal
     * @param bool  $canonicalize Arrays are sorted before comparison when set to true
     * @param bool  $ignoreCase   Case is ignored when set to true
     *
     * @throws ComparisonFailure
     */
    public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false)
    {
        if ($actual != $expected) {
            throw new ComparisonFailure(
                $expected,
                $actual,
                $this->exporter->export($expected),
                $this->exporter->export($actual)
            );
        }
    }
}
<?php
/*
 * This file is part of the Comparator package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\Comparator;

/**
 * Compares PHPUnit_Framework_MockObject_MockObject instances for equality.
 */
class MockObjectComparator extends ObjectComparator
{
    /**
     * Returns whether the comparator can compare two values.
     *
     * @param  mixed $expected The first value to compare
     * @param  mixed $actual   The second value to compare
     * @return bool
     */
    public function accepts($expected, $actual)
    {
        return $expected instanceof \PHPUnit_Framework_MockObject_MockObject && $actual instanceof \PHPUnit_Framework_MockObject_MockObject;
    }

    /**
     * Converts an object to an array containing all of its private, protected
     * and public properties.
     *
     * @param  object $object
     * @return array
     */
    protected function toArray($object)
    {
        $array = parent::toArray($object);

        unset($array['__phpunit_invocationMocker']);

        return $array;
    }
}<?php
/*
 * This file is part of the Comparator package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\Comparator;

/**
 * Compares objects for equality.
 */
class ObjectComparator extends ArrayComparator
{
    /**
     * Returns whether the comparator can compare two values.
     *
     * @param  mixed $expected The first value to compare
     * @param  mixed $actual   The second value to compare
     * @return bool
     */
    public function accepts($expected, $actual)
    {
        return is_object($expected) && is_object($actual);
    }

    /**
     * Asserts that two values are equal.
     *
     * @param mixed $expected     First value to compare
     * @param mixed $actual       Second value to compare
     * @param float $delta        Allowed numerical distance between two values to consider them equal
     * @param bool  $canonicalize Arrays are sorted before comparison when set to true
     * @param bool  $ignoreCase   Case is ignored when set to true
     * @param array $processed    List of already processed elements (used to prevent infinite recursion)
     *
     * @throws ComparisonFailure
     */
    public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false, array &$processed = array())
    {
        if (get_class($actual) !== get_class($expected)) {
            throw new ComparisonFailure(
                $expected,
                $actual,
                $this->exporter->export($expected),
                $this->exporter->export($actual),
                false,
                sprintf(
                    '%s is not instance of expected class "%s".',
                    $this->exporter->export($actual),
                    get_class($expected)
                )
            );
        }

        // don't compare twice to allow for cyclic dependencies
        if (in_array(array($actual, $expected), $processed, true) ||
            in_array(array($expected, $actual), $processed, true)) {
            return;
        }

        $processed[] = array($actual, $expected);

        // don't compare objects if they are identical
        // this helps to avoid the error "maximum function nesting level reached"
        // CAUTION: this conditional clause is not tested
        if ($actual !== $expected) {
            try {
                parent::assertEquals(
                    $this->toArray($expected),
                    $this->toArray($actual),
                    $delta,
                    $canonicalize,
                    $ignoreCase,
                    $processed
                );
            } catch (ComparisonFailure $e) {
                throw new ComparisonFailure(
                    $expected,
                    $actual,
                    // replace "Array" with "MyClass object"
                    substr_replace($e->getExpectedAsString(), get_class($expected) . ' Object', 0, 5),
                    substr_replace($e->getActualAsString(), get_class($actual) . ' Object', 0, 5),
                    false,
                    'Failed asserting that two objects are equal.'
                );
            }
        }
    }

    /**
     * Converts an object to an array containing all of its private, protected
     * and public properties.
     *
     * @param  object $object
     * @return array
     */
    protected function toArray($object)
    {
        return $this->exporter->toArray($object);
    }
}
<?php
/*
 * This file is part of the Comparator package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\Comparator;

/**
 * Compares numerical values for equality.
 */
class NumericComparator extends ScalarComparator
{
    /**
     * Returns whether the comparator can compare two values.
     *
     * @param  mixed $expected The first value to compare
     * @param  mixed $actual   The second value to compare
     * @return bool
     */
    public function accepts($expected, $actual)
    {
        // all numerical values, but not if one of them is a double
        // or both of them are strings
        return is_numeric($expected) && is_numeric($actual) &&
               !(is_double($expected) || is_double($actual)) &&
               !(is_string($expected) && is_string($actual));
    }

    /**
     * Asserts that two values are equal.
     *
     * @param mixed $expected     First value to compare
     * @param mixed $actual       Second value to compare
     * @param float $delta        Allowed numerical distance between two values to consider them equal
     * @param bool  $canonicalize Arrays are sorted before comparison when set to true
     * @param bool  $ignoreCase   Case is ignored when set to true
     *
     * @throws ComparisonFailure
     */
    public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false)
    {
        if (is_infinite($actual) && is_infinite($expected)) {
            return;
        }

        if ((is_infinite($actual) xor is_infinite($expected)) ||
            (is_nan($actual) or is_nan($expected)) ||
            abs($actual - $expected) > $delta) {
            throw new ComparisonFailure(
                $expected,
                $actual,
                '',
                '',
                false,
                sprintf(
                    'Failed asserting that %s matches expected %s.',
                    $this->exporter->export($actual),
                    $this->exporter->export($expected)
                )
            );
        }
    }
}
<?php
/*
 * This file is part of the Comparator package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\Comparator;

/**
 * Compares DateTimeInterface instances for equality.
 */
class DateTimeComparator extends ObjectComparator
{
    /**
     * Returns whether the comparator can compare two values.
     *
     * @param  mixed $expected The first value to compare
     * @param  mixed $actual   The second value to compare
     * @return bool
     */
    public function accepts($expected, $actual)
    {
        return ($expected instanceof \DateTime || $expected instanceof \DateTimeInterface) &&
            ($actual instanceof \DateTime || $actual instanceof \DateTimeInterface);
    }

    /**
     * Asserts that two values are equal.
     *
     * @param mixed $expected     First value to compare
     * @param mixed $actual       Second value to compare
     * @param float $delta        Allowed numerical distance between two values to consider them equal
     * @param bool  $canonicalize Arrays are sorted before comparison when set to true
     * @param bool  $ignoreCase   Case is ignored when set to true
     * @param array $processed    List of already processed elements (used to prevent infinite recursion)
     *
     * @throws ComparisonFailure
     */
    public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false, array &$processed = array())
    {
        $delta = new \DateInterval(sprintf('PT%sS', abs($delta)));

        $expectedLower = clone $expected;
        $expectedUpper = clone $expected;

        if ($actual < $expectedLower->sub($delta) ||
            $actual > $expectedUpper->add($delta)) {
            throw new ComparisonFailure(
                $expected,
                $actual,
                $this->dateTimeToString($expected),
                $this->dateTimeToString($actual),
                false,
                'Failed asserting that two DateTime objects are equal.'
            );
        }
    }

    /**
     * Returns an ISO 8601 formatted string representation of a datetime or
     * 'Invalid DateTimeInterface object' if the provided DateTimeInterface was not properly
     * initialized.
     *
     * @param  \DateTimeInterface $datetime
     * @return string
     */
    private function dateTimeToString($datetime)
    {
        $string = $datetime->format('Y-m-d\TH:i:s.uO');

        return $string ? $string : 'Invalid DateTimeInterface object';
    }
}
<?php
/*
 * This file is part of the Comparator package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\Comparator;

/**
 * Compares scalar or NULL values for equality.
 */
class ScalarComparator extends Comparator
{
    /**
     * Returns whether the comparator can compare two values.
     *
     * @param  mixed $expected The first value to compare
     * @param  mixed $actual   The second value to compare
     * @return bool
     * @since  Method available since Release 3.6.0
     */
    public function accepts($expected, $actual)
    {
        return ((is_scalar($expected) xor null === $expected) &&
               (is_scalar($actual) xor null === $actual))
               // allow comparison between strings and objects featuring __toString()
               || (is_string($expected) && is_object($actual) && method_exists($actual, '__toString'))
               || (is_object($expected) && method_exists($expected, '__toString') && is_string($actual));
    }

    /**
     * Asserts that two values are equal.
     *
     * @param mixed $expected     First value to compare
     * @param mixed $actual       Second value to compare
     * @param float $delta        Allowed numerical distance between two values to consider them equal
     * @param bool  $canonicalize Arrays are sorted before comparison when set to true
     * @param bool  $ignoreCase   Case is ignored when set to true
     *
     * @throws ComparisonFailure
     */
    public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false)
    {
        $expectedToCompare = $expected;
        $actualToCompare   = $actual;

        // always compare as strings to avoid strange behaviour
        // otherwise 0 == 'Foobar'
        if (is_string($expected) || is_string($actual)) {
            $expectedToCompare = (string) $expectedToCompare;
            $actualToCompare   = (string) $actualToCompare;

            if ($ignoreCase) {
                $expectedToCompare = strtolower($expectedToCompare);
                $actualToCompare   = strtolower($actualToCompare);
            }
        }

        if ($expectedToCompare != $actualToCompare) {
            if (is_string($expected) && is_string($actual)) {
                throw new ComparisonFailure(
                    $expected,
                    $actual,
                    $this->exporter->export($expected),
                    $this->exporter->export($actual),
                    false,
                    'Failed asserting that two strings are equal.'
                );
            }

            throw new ComparisonFailure(
                $expected,
                $actual,
                // no diff is required
                '',
                '',
                false,
                sprintf(
                    'Failed asserting that %s matches expected %s.',
                    $this->exporter->export($actual),
                    $this->exporter->export($expected)
                )
            );
        }
    }
}
<?php
/*
 * This file is part of the Comparator package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\Comparator;

/**
 * Compares Exception instances for equality.
 */
class ExceptionComparator extends ObjectComparator
{
    /**
     * Returns whether the comparator can compare two values.
     *
     * @param  mixed $expected The first value to compare
     * @param  mixed $actual   The second value to compare
     * @return bool
     */
    public function accepts($expected, $actual)
    {
        return $expected instanceof \Exception && $actual instanceof \Exception;
    }

    /**
     * Converts an object to an array containing all of its private, protected
     * and public properties.
     *
     * @param  object $object
     * @return array
     */
    protected function toArray($object)
    {
        $array = parent::toArray($object);

        unset(
            $array['file'],
            $array['line'],
            $array['trace'],
            $array['string'],
            $array['xdebug_message']
        );

        return $array;
    }
}
<?php
/*
 * This file is part of the Comparator package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\Comparator;

use SebastianBergmann\Exporter\Exporter;

/**
 * Abstract base class for comparators which compare values for equality.
 */
abstract class Comparator
{
    /**
     * @var Factory
     */
    protected $factory;

    /**
     * @var Exporter
     */
    protected $exporter;

    public function __construct()
    {
        $this->exporter = new Exporter;
    }

    /**
     * @param Factory $factory
     */
    public function setFactory(Factory $factory)
    {
        $this->factory = $factory;
    }

    /**
     * Returns whether the comparator can compare two values.
     *
     * @param  mixed $expected The first value to compare
     * @param  mixed $actual   The second value to compare
     * @return bool
     */
    abstract public function accepts($expected, $actual);

    /**
     * Asserts that two values are equal.
     *
     * @param mixed $expected     First value to compare
     * @param mixed $actual       Second value to compare
     * @param float $delta        Allowed numerical distance between two values to consider them equal
     * @param bool  $canonicalize Arrays are sorted before comparison when set to true
     * @param bool  $ignoreCase   Case is ignored when set to true
     *
     * @throws ComparisonFailure
     */
    abstract public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false);
}
<?php
/*
 * This file is part of the Comparator package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\Comparator;

/**
 * Compares doubles for equality.
 */
class DoubleComparator extends NumericComparator
{
    /**
     * Smallest value available in PHP.
     *
     * @var float
     */
    const EPSILON = 0.0000000001;

    /**
     * Returns whether the comparator can compare two values.
     *
     * @param  mixed $expected The first value to compare
     * @param  mixed $actual   The second value to compare
     * @return bool
     */
    public function accepts($expected, $actual)
    {
        return (is_double($expected) || is_double($actual)) && is_numeric($expected) && is_numeric($actual);
    }

    /**
     * Asserts that two values are equal.
     *
     * @param mixed $expected     First value to compare
     * @param mixed $actual       Second value to compare
     * @param float $delta        Allowed numerical distance between two values to consider them equal
     * @param bool  $canonicalize Arrays are sorted before comparison when set to true
     * @param bool  $ignoreCase   Case is ignored when set to true
     *
     * @throws ComparisonFailure
     */
    public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false)
    {
        if ($delta == 0) {
            $delta = self::EPSILON;
        }

        parent::assertEquals($expected, $actual, $delta, $canonicalize, $ignoreCase);
    }
}
<?php
/*
 * This file is part of the Comparator package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\Comparator;

use DOMDocument;
use DOMNode;

/**
 * Compares DOMNode instances for equality.
 */
class DOMNodeComparator extends ObjectComparator
{
    /**
     * Returns whether the comparator can compare two values.
     *
     * @param  mixed $expected The first value to compare
     * @param  mixed $actual   The second value to compare
     * @return bool
     */
    public function accepts($expected, $actual)
    {
        return $expected instanceof DOMNode && $actual instanceof DOMNode;
    }

    /**
     * Asserts that two values are equal.
     *
     * @param mixed $expected     First value to compare
     * @param mixed $actual       Second value to compare
     * @param float $delta        Allowed numerical distance between two values to consider them equal
     * @param bool  $canonicalize Arrays are sorted before comparison when set to true
     * @param bool  $ignoreCase   Case is ignored when set to true
     * @param array $processed    List of already processed elements (used to prevent infinite recursion)
     *
     * @throws ComparisonFailure
     */
    public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false, array &$processed = array())
    {
        $expectedAsString = $this->nodeToText($expected, true, $ignoreCase);
        $actualAsString   = $this->nodeToText($actual, true, $ignoreCase);

        if ($expectedAsString !== $actualAsString) {
            if ($expected instanceof DOMDocument) {
                $type = 'documents';
            } else {
                $type = 'nodes';
            }

            throw new ComparisonFailure(
                $expected,
                $actual,
                $expectedAsString,
                $actualAsString,
                false,
                sprintf("Failed asserting that two DOM %s are equal.\n", $type)
            );
        }
    }

    /**
     * Returns the normalized, whitespace-cleaned, and indented textual
     * representation of a DOMNode.
     *
     * @param  DOMNode $node
     * @param  bool    $canonicalize
     * @param  bool    $ignoreCase
     * @return string
     */
    private function nodeToText(DOMNode $node, $canonicalize, $ignoreCase)
    {
        if ($canonicalize) {
            $document = new DOMDocument;
            $document->loadXML($node->C14N());

            $node = $document;
        }

        if ($node instanceof DOMDocument) {
            $document = $node;
        } else {
            $document = $node->ownerDocument;
        }

        $document->formatOutput = true;
        $document->normalizeDocument();

        if ($node instanceof DOMDocument) {
            $text = $node->saveXML();
        } else {
            $text = $document->saveXML($node);
        }

        if ($ignoreCase) {
            $text = strtolower($text);
        }

        return $text;
    }
}
<?php
/*
 * This file is part of the Comparator package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\Comparator;

use SebastianBergmann\Diff\Differ;

/**
 * Thrown when an assertion for string equality failed.
 */
class ComparisonFailure extends \RuntimeException
{
    /**
     * Expected value of the retrieval which does not match $actual.
     * @var mixed
     */
    protected $expected;

    /**
     * Actually retrieved value which does not match $expected.
     * @var mixed
     */
    protected $actual;

    /**
     * The string representation of the expected value
     * @var string
     */
    protected $expectedAsString;

    /**
     * The string representation of the actual value
     * @var string
     */
    protected $actualAsString;

    /**
     * @var bool
     */
    protected $identical;

    /**
     * Optional message which is placed in front of the first line
     * returned by toString().
     * @var string
     */
    protected $message;

    /**
     * Initialises with the expected value and the actual value.
     *
     * @param mixed  $expected         Expected value retrieved.
     * @param mixed  $actual           Actual value retrieved.
     * @param string $expectedAsString
     * @param string $actualAsString
     * @param bool   $identical
     * @param string $message          A string which is prefixed on all returned lines
     *                                 in the difference output.
     */
    public function __construct($expected, $actual, $expectedAsString, $actualAsString, $identical = false, $message = '')
    {
        $this->expected         = $expected;
        $this->actual           = $actual;
        $this->expectedAsString = $expectedAsString;
        $this->actualAsString   = $actualAsString;
        $this->message          = $message;
    }

    /**
     * @return mixed
     */
    public function getActual()
    {
        return $this->actual;
    }

    /**
     * @return mixed
     */
    public function getExpected()
    {
        return $this->expected;
    }

    /**
     * @return string
     */
    public function getActualAsString()
    {
        return $this->actualAsString;
    }

    /**
     * @return string
     */
    public function getExpectedAsString()
    {
        return $this->expectedAsString;
    }

    /**
     * @return string
     */
    public function getDiff()
    {
        if (!$this->actualAsString && !$this->expectedAsString) {
            return '';
        }

        $differ = new Differ("\n--- Expected\n+++ Actual\n");

        return $differ->diff($this->expectedAsString, $this->actualAsString);
    }

    /**
     * @return string
     */
    public function toString()
    {
        return $this->message . $this->getDiff();
    }
}
<?php
/*
 * This file is part of the Comparator package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\Comparator;

/**
 * Compares \SplObjectStorage instances for equality.
 */
class SplObjectStorageComparator extends Comparator
{
    /**
     * Returns whether the comparator can compare two values.
     *
     * @param  mixed $expected The first value to compare
     * @param  mixed $actual   The second value to compare
     * @return bool
     */
    public function accepts($expected, $actual)
    {
        return $expected instanceof \SplObjectStorage && $actual instanceof \SplObjectStorage;
    }

    /**
     * Asserts that two values are equal.
     *
     * @param mixed $expected     First value to compare
     * @param mixed $actual       Second value to compare
     * @param float $delta        Allowed numerical distance between two values to consider them equal
     * @param bool  $canonicalize Arrays are sorted before comparison when set to true
     * @param bool  $ignoreCase   Case is ignored when set to true
     *
     * @throws ComparisonFailure
     */
    public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false)
    {
        foreach ($actual as $object) {
            if (!$expected->contains($object)) {
                throw new ComparisonFailure(
                    $expected,
                    $actual,
                    $this->exporter->export($expected),
                    $this->exporter->export($actual),
                    false,
                    'Failed asserting that two objects are equal.'
                );
            }
        }

        foreach ($expected as $object) {
            if (!$actual->contains($object)) {
                throw new ComparisonFailure(
                    $expected,
                    $actual,
                    $this->exporter->export($expected),
                    $this->exporter->export($actual),
                    false,
                    'Failed asserting that two objects are equal.'
                );
            }
        }
    }
}
{
    "name": "sebastian/global-state",
    "description": "Snapshotting of global state",
    "keywords": ["global state"],
    "homepage": "http://www.github.com/sebastianbergmann/global-state",
    "license": "BSD-3-Clause",
    "authors": [
        {
            "name": "Sebastian Bergmann",
            "email": "sebastian@phpunit.de"
        }
    ],
    "require": {
        "php": ">=5.3.3"
    },
    "require-dev": {
        "phpunit/phpunit": "~4.2"
    },
    "suggest": {
        "ext-uopz": "*"
    },
    "autoload": {
        "classmap": [
            "src/"
        ]
    },
    "autoload-dev": {
        "classmap": [
            "tests/_fixture/"
        ]
    },
    "extra": {
        "branch-alias": {
            "dev-master": "1.0-dev"
        }
    }
}
<?php
/*
 * This file is part of the GlobalState package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\GlobalState;

use ReflectionProperty;

/**
 * Restorer of snapshots of global state.
 */
class Restorer
{
    /**
     * Deletes function definitions that are not defined in a snapshot.
     *
     * @param  Snapshot         $snapshot
     * @throws RuntimeException when the uopz_delete() function is not available
     * @see    https://github.com/krakjoe/uopz
     */
    public function restoreFunctions(Snapshot $snapshot)
    {
        if (!function_exists('uopz_delete')) {
            throw new RuntimeException('The uopz_delete() function is required for this operation');
        }

        $functions = get_defined_functions();

        foreach (array_diff($functions['user'], $snapshot->functions()) as $function) {
            uopz_delete($function);
        }
    }

    /**
     * Restores all global and super-global variables from a snapshot.
     *
     * @param Snapshot $snapshot
     */
    public function restoreGlobalVariables(Snapshot $snapshot)
    {
        $superGlobalArrays = $snapshot->superGlobalArrays();

        foreach ($superGlobalArrays as $superGlobalArray) {
            $this->restoreSuperGlobalArray($snapshot, $superGlobalArray);
        }

        $globalVariables = $snapshot->globalVariables();

        foreach (array_keys($GLOBALS) as $key) {
            if ($key != 'GLOBALS' &&
                !in_array($key, $superGlobalArrays) &&
                !$snapshot->blacklist()->isGlobalVariableBlacklisted($key)) {
                if (isset($globalVariables[$key])) {
                    $GLOBALS[$key] = $globalVariables[$key];
                } else {
                    unset($GLOBALS[$key]);
                }
            }
        }
    }

    /**
     * Restores all static attributes in user-defined classes from this snapshot.
     *
     * @param Snapshot $snapshot
     */
    public function restoreStaticAttributes(Snapshot $snapshot)
    {
        $current    = new Snapshot($snapshot->blacklist(), false, false, false, false, true, false, false, false, false);
        $newClasses = array_diff($current->classes(), $snapshot->classes());
        unset($current);

        foreach ($snapshot->staticAttributes() as $className => $staticAttributes) {
            foreach ($staticAttributes as $name => $value) {
                $reflector = new ReflectionProperty($className, $name);
                $reflector->setAccessible(true);
                $reflector->setValue($value);
            }
        }

        foreach ($newClasses as $className) {
            $class    = new \ReflectionClass($className);
            $defaults = $class->getDefaultProperties();

            foreach ($class->getProperties() as $attribute) {
                if (!$attribute->isStatic()) {
                    continue;
                }

                $name = $attribute->getName();

                if ($snapshot->blacklist()->isStaticAttributeBlacklisted($className, $name)) {
                    continue;
                }

                if (!isset($defaults[$name])) {
                    continue;
                }

                $attribute->setAccessible(true);
                $attribute->setValue($defaults[$name]);
            }
        }
    }

    /**
     * Restores a super-global variable array from this snapshot.
     *
     * @param Snapshot $snapshot
     * @param $superGlobalArray
     */
    private function restoreSuperGlobalArray(Snapshot $snapshot, $superGlobalArray)
    {
        $superGlobalVariables = $snapshot->superGlobalVariables();

        if (isset($GLOBALS[$superGlobalArray]) &&
            is_array($GLOBALS[$superGlobalArray]) &&
            isset($superGlobalVariables[$superGlobalArray])) {
            $keys = array_keys(
                array_merge(
                    $GLOBALS[$superGlobalArray],
                    $superGlobalVariables[$superGlobalArray]
                )
            );

            foreach ($keys as $key) {
                if (isset($superGlobalVariables[$superGlobalArray][$key])) {
                    $GLOBALS[$superGlobalArray][$key] = $superGlobalVariables[$superGlobalArray][$key];
                } else {
                    unset($GLOBALS[$superGlobalArray][$key]);
                }
            }
        }
    }
}
<?php
/*
 * This file is part of the GlobalState package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\GlobalState;

/**
 * Exports parts of a Snapshot as PHP code.
 */
class CodeExporter
{
    /**
     * @param  Snapshot $snapshot
     * @return string
     */
    public function constants(Snapshot $snapshot)
    {
        $result = '';

        foreach ($snapshot->constants() as $name => $value) {
            $result .= sprintf(
                'if (!defined(\'%s\')) define(\'%s\', %s);' . "\n",
                $name,
                $name,
                $this->exportVariable($value)
            );
        }

        return $result;
    }

    /**
     * @param  Snapshot $snapshot
     * @return string
     */
    public function iniSettings(Snapshot $snapshot)
    {
        $result = '';

        foreach ($snapshot->iniSettings() as $key => $value) {
            $result .= sprintf(
                '@ini_set(%s, %s);' . "\n",
                $this->exportVariable($key),
                $this->exportVariable($value)
            );
        }

        return $result;
    }

    /**
     * @param  mixed  $variable
     * @return string
     */
    private function exportVariable($variable)
    {
        if (is_scalar($variable) || is_null($variable) ||
            (is_array($variable) && $this->arrayOnlyContainsScalars($variable))) {
            return var_export($variable, true);
        }

        return 'unserialize(' . var_export(serialize($variable), true) . ')';
    }

    /**
     * @param  array $array
     * @return bool
     */
    private function arrayOnlyContainsScalars(array $array)
    {
        $result = true;

        foreach ($array as $element) {
            if (is_array($element)) {
                $result = self::arrayOnlyContainsScalars($element);
            } elseif (!is_scalar($element) && !is_null($element)) {
                $result = false;
            }

            if ($result === false) {
                break;
            }
        }

        return $result;
    }
}
<?php
/*
 * This file is part of the GlobalState package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\GlobalState;

use ReflectionClass;
use Serializable;

/**
 * A snapshot of global state.
 */
class Snapshot
{
    /**
     * @var Blacklist
     */
    private $blacklist;

    /**
     * @var array
     */
    private $globalVariables = array();

    /**
     * @var array
     */
    private $superGlobalArrays = array();

    /**
     * @var array
     */
    private $superGlobalVariables = array();

    /**
     * @var array
     */
    private $staticAttributes = array();

    /**
     * @var array
     */
    private $iniSettings = array();

    /**
     * @var array
     */
    private $includedFiles = array();

    /**
     * @var array
     */
    private $constants = array();

    /**
     * @var array
     */
    private $functions = array();

    /**
     * @var array
     */
    private $interfaces = array();

    /**
     * @var array
     */
    private $classes = array();

    /**
     * @var array
     */
    private $traits = array();

    /**
     * Creates a snapshot of the current global state.
     *
     * @param Blacklist $blacklist
     * @param bool      $includeGlobalVariables
     * @param bool      $includeStaticAttributes
     * @param bool      $includeConstants
     * @param bool      $includeFunctions
     * @param bool      $includeClasses
     * @param bool      $includeInterfaces
     * @param bool      $includeTraits
     * @param bool      $includeIniSettings
     * @param bool      $includeIncludedFiles
     */
    public function __construct(Blacklist $blacklist = null, $includeGlobalVariables = true, $includeStaticAttributes = true, $includeConstants = true, $includeFunctions = true, $includeClasses = true, $includeInterfaces = true, $includeTraits = true, $includeIniSettings = true, $includeIncludedFiles = true)
    {
        if ($blacklist === null) {
            $blacklist = new Blacklist;
        }

        $this->blacklist = $blacklist;

        if ($includeConstants) {
            $this->snapshotConstants();
        }

        if ($includeFunctions) {
            $this->snapshotFunctions();
        }

        if ($includeClasses || $includeStaticAttributes) {
            $this->snapshotClasses();
        }

        if ($includeInterfaces) {
            $this->snapshotInterfaces();
        }

        if ($includeGlobalVariables) {
            $this->setupSuperGlobalArrays();
            $this->snapshotGlobals();
        }

        if ($includeStaticAttributes) {
            $this->snapshotStaticAttributes();
        }

        if ($includeIniSettings) {
            $this->iniSettings = ini_get_all(null, false);
        }

        if ($includeIncludedFiles) {
            $this->includedFiles = get_included_files();
        }

        if (function_exists('get_declared_traits')) {
            $this->traits = get_declared_traits();
        }
    }

    /**
     * @return Blacklist
     */
    public function blacklist()
    {
        return $this->blacklist;
    }

    /**
     * @return array
     */
    public function globalVariables()
    {
        return $this->globalVariables;
    }

    /**
     * @return array
     */
    public function superGlobalVariables()
    {
        return $this->superGlobalVariables;
    }

    /**
     * Returns a list of all super-global variable arrays.
     *
     * @return array
     */
    public function superGlobalArrays()
    {
        return $this->superGlobalArrays;
    }

    /**
     * @return array
     */
    public function staticAttributes()
    {
        return $this->staticAttributes;
    }

    /**
     * @return array
     */
    public function iniSettings()
    {
        return $this->iniSettings;
    }

    /**
     * @return array
     */
    public function includedFiles()
    {
        return $this->includedFiles;
    }

    /**
     * @return array
     */
    public function constants()
    {
        return $this->constants;
    }

    /**
     * @return array
     */
    public function functions()
    {
        return $this->functions;
    }

    /**
     * @return array
     */
    public function interfaces()
    {
        return $this->interfaces;
    }

    /**
     * @return array
     */
    public function classes()
    {
        return $this->classes;
    }

    /**
     * @return array
     */
    public function traits()
    {
        return $this->traits;
    }

    /**
     * Creates a snapshot user-defined constants.
     */
    private function snapshotConstants()
    {
        $constants = get_defined_constants(true);

        if (isset($constants['user'])) {
            $this->constants = $constants['user'];
        }
    }

    /**
     * Creates a snapshot user-defined functions.
     */
    private function snapshotFunctions()
    {
        $functions = get_defined_functions();

        $this->functions = $functions['user'];
    }

    /**
     * Creates a snapshot user-defined classes.
     */
    private function snapshotClasses()
    {
        foreach (array_reverse(get_declared_classes()) as $className) {
            $class = new ReflectionClass($className);

            if (!$class->isUserDefined()) {
                break;
            }

            $this->classes[] = $className;
        }

        $this->classes = array_reverse($this->classes);
    }

    /**
     * Creates a snapshot user-defined interfaces.
     */
    private function snapshotInterfaces()
    {
        foreach (array_reverse(get_declared_interfaces()) as $interfaceName) {
            $class = new ReflectionClass($interfaceName);

            if (!$class->isUserDefined()) {
                break;
            }

            $this->interfaces[] = $interfaceName;
        }

        $this->interfaces = array_reverse($this->interfaces);
    }

    /**
     * Creates a snapshot of all global and super-global variables.
     */
    private function snapshotGlobals()
    {
        $superGlobalArrays = $this->superGlobalArrays();

        foreach ($superGlobalArrays as $superGlobalArray) {
            $this->snapshotSuperGlobalArray($superGlobalArray);
        }

        foreach (array_keys($GLOBALS) as $key) {
            if ($key != 'GLOBALS' &&
                !in_array($key, $superGlobalArrays) &&
                $this->canBeSerialized($GLOBALS[$key]) &&
                !$this->blacklist->isGlobalVariableBlacklisted($key)) {
                $this->globalVariables[$key] = unserialize(serialize($GLOBALS[$key]));
            }
        }
    }

    /**
     * Creates a snapshot a super-global variable array.
     *
     * @param $superGlobalArray
     */
    private function snapshotSuperGlobalArray($superGlobalArray)
    {
        $this->superGlobalVariables[$superGlobalArray] = array();

        if (isset($GLOBALS[$superGlobalArray]) && is_array($GLOBALS[$superGlobalArray])) {
            foreach ($GLOBALS[$superGlobalArray] as $key => $value) {
                $this->superGlobalVariables[$superGlobalArray][$key] = unserialize(serialize($value));
            }
        }
    }

    /**
     * Creates a snapshot of all static attributes in user-defined classes.
     */
    private function snapshotStaticAttributes()
    {
        foreach ($this->classes as $className) {
            $class    = new ReflectionClass($className);
            $snapshot = array();

            foreach ($class->getProperties() as $attribute) {
                if ($attribute->isStatic()) {
                    $name = $attribute->getName();

                    if ($this->blacklist->isStaticAttributeBlacklisted($className, $name)) {
                        continue;
                    }

                    $attribute->setAccessible(true);
                    $value = $attribute->getValue();

                    if ($this->canBeSerialized($value)) {
                        $snapshot[$name] = unserialize(serialize($value));
                    }
                }
            }

            if (!empty($snapshot)) {
                $this->staticAttributes[$className] = $snapshot;
            }
        }
    }

    /**
     * Returns a list of all super-global variable arrays.
     *
     * @return array
     */
    private function setupSuperGlobalArrays()
    {
        $this->superGlobalArrays = array(
            '_ENV',
            '_POST',
            '_GET',
            '_COOKIE',
            '_SERVER',
            '_FILES',
            '_REQUEST'
        );

        if (ini_get('register_long_arrays') == '1') {
            $this->superGlobalArrays = array_merge(
                $this->superGlobalArrays,
                array(
                    'HTTP_ENV_VARS',
                    'HTTP_POST_VARS',
                    'HTTP_GET_VARS',
                    'HTTP_COOKIE_VARS',
                    'HTTP_SERVER_VARS',
                    'HTTP_POST_FILES'
                )
            );
        }
    }

    /**
     * @param  mixed $variable
     * @return bool
     * @todo   Implement this properly
     */
    private function canBeSerialized($variable)
    {
        if (!is_object($variable)) {
            return !is_resource($variable);
        }

        if ($variable instanceof \stdClass) {
            return true;
        }

        $class = new ReflectionClass($variable);

        do {
            if ($class->isInternal()) {
                return $variable instanceof Serializable;
            }
        } while ($class = $class->getParentClass());

        return true;
    }
}
<?php
/*
 * This file is part of the GlobalState package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\GlobalState;

/**
 */
class RuntimeException extends \RuntimeException implements Exception
{
}
<?php
/*
 * This file is part of the GlobalState package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\GlobalState;

use ReflectionClass;

/**
 * A blacklist for global state elements that should not be snapshotted.
 */
class Blacklist
{
    /**
     * @var array
     */
    private $globalVariables = array();

    /**
     * @var array
     */
    private $classes = array();

    /**
     * @var array
     */
    private $classNamePrefixes = array();

    /**
     * @var array
     */
    private $parentClasses = array();

    /**
     * @var array
     */
    private $interfaces = array();

    /**
     * @var array
     */
    private $staticAttributes = array();

    /**
     * @param string $variableName
     */
    public function addGlobalVariable($variableName)
    {
        $this->globalVariables[$variableName] = true;
    }

    /**
     * @param string $className
     */
    public function addClass($className)
    {
        $this->classes[] = $className;
    }

    /**
     * @param string $className
     */
    public function addSubclassesOf($className)
    {
        $this->parentClasses[] = $className;
    }

    /**
     * @param string $interfaceName
     */
    public function addImplementorsOf($interfaceName)
    {
        $this->interfaces[] = $interfaceName;
    }

    /**
     * @param string $classNamePrefix
     */
    public function addClassNamePrefix($classNamePrefix)
    {
        $this->classNamePrefixes[] = $classNamePrefix;
    }

    /**
     * @param string $className
     * @param string $attributeName
     */
    public function addStaticAttribute($className, $attributeName)
    {
        if (!isset($this->staticAttributes[$className])) {
            $this->staticAttributes[$className] = array();
        }

        $this->staticAttributes[$className][$attributeName] = true;
    }

    /**
     * @param  string $variableName
     * @return bool
     */
    public function isGlobalVariableBlacklisted($variableName)
    {
        return isset($this->globalVariables[$variableName]);
    }

    /**
     * @param  string $className
     * @param  string $attributeName
     * @return bool
     */
    public function isStaticAttributeBlacklisted($className, $attributeName)
    {
        if (in_array($className, $this->classes)) {
            return true;
        }

        foreach ($this->classNamePrefixes as $prefix) {
            if (strpos($className, $prefix) === 0) {
                return true;
            }
        }

        $class = new ReflectionClass($className);

        foreach ($this->parentClasses as $type) {
            if ($class->isSubclassOf($type)) {
                return true;
            }
        }

        foreach ($this->interfaces as $type) {
            if ($class->implementsInterface($type)) {
                return true;
            }
        }

        if (isset($this->staticAttributes[$className][$attributeName])) {
            return true;
        }

        return false;
    }
}
<?php
/*
 * This file is part of the GlobalState package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\GlobalState;

/**
 */
interface Exception
{
}
{
    "name": "sebastian/object-enumerator",
    "description": "Traverses array structures and object graphs to enumerate all referenced objects",
    "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
    "license": "BSD-3-Clause",
    "authors": [
        {
            "name": "Sebastian Bergmann",
            "email": "sebastian@phpunit.de"
        }
    ],
    "require": {
        "php": ">=5.6",
        "sebastian/recursion-context": "~2.0"
    },
    "require-dev": {
        "phpunit/phpunit": "~5"
    },
    "autoload": {
        "classmap": [
            "src/"
        ]
    },
    "autoload-dev": {
        "classmap": [
            "tests/"
        ]
    },
    "extra": {
        "branch-alias": {
            "dev-master": "2.0.x-dev"
        }
    }
}
<?php
/*
 * This file is part of Object Enumerator.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\ObjectEnumerator;

class InvalidArgumentException extends \InvalidArgumentException implements Exception
{
}
<?php
/*
 * This file is part of Object Enumerator.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\ObjectEnumerator;

use SebastianBergmann\RecursionContext\Context;

/**
 * Traverses array structures and object graphs
 * to enumerate all referenced objects.
 */
class Enumerator
{
    /**
     * Returns an array of all objects referenced either
     * directly or indirectly by a variable.
     *
     * @param array|object $variable
     *
     * @return object[]
     */
    public function enumerate($variable)
    {
        if (!is_array($variable) && !is_object($variable)) {
            throw new InvalidArgumentException;
        }

        if (isset(func_get_args()[1])) {
            if (!func_get_args()[1] instanceof Context) {
                throw new InvalidArgumentException;
            }

            $processed = func_get_args()[1];
        } else {
            $processed = new Context;
        }

        $objects = [];

        if ($processed->contains($variable)) {
            return $objects;
        }

        $array = $variable;
        $processed->add($variable);

        if (is_array($variable)) {
            foreach ($array as $element) {
                if (!is_array($element) && !is_object($element)) {
                    continue;
                }

                $objects = array_merge(
                    $objects,
                    $this->enumerate($element, $processed)
                );
            }
        } else {
            $objects[] = $variable;
            $reflector = new \ReflectionObject($variable);

            foreach ($reflector->getProperties() as $attribute) {
                $attribute->setAccessible(true);

                try {
                    $value = $attribute->getValue($variable);
                } catch (\Throwable $e) {
                    continue;
                } catch (\Exception $e) {
                    continue;
                }

                if (!is_array($value) && !is_object($value)) {
                    continue;
                }

                $objects = array_merge(
                    $objects,
                    $this->enumerate($value, $processed)
                );
            }
        }

        return $objects;
    }
}
<?php
/*
 * This file is part of Object Enumerator.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\ObjectEnumerator;

interface Exception
{
}
{
    "name": "sebastian/code-unit-reverse-lookup",
    "description": "Looks up which function or method a line of code belongs to",
    "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
    "license": "BSD-3-Clause",
    "authors": [
        {
            "name": "Sebastian Bergmann",
            "email": "sebastian@phpunit.de"
        }
    ],
    "require": {
        "php": "^5.6 || ^7.0"
    },
    "require-dev": {
        "phpunit/phpunit": "^5.7 || ^6.0"
    },
    "autoload": {
        "classmap": [
            "src/"
        ]
    },
    "extra": {
        "branch-alias": {
            "dev-master": "1.0.x-dev"
        }
    }
}
<?php
/*
 * This file is part of code-unit-reverse-lookup.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\CodeUnitReverseLookup;

/**
 * @since Class available since Release 1.0.0
 */
class Wizard
{
    /**
     * @var array
     */
    private $lookupTable = [];

    /**
     * @var array
     */
    private $processedClasses = [];

    /**
     * @var array
     */
    private $processedFunctions = [];

    /**
     * @param string $filename
     * @param int    $lineNumber
     *
     * @return string
     */
    public function lookup($filename, $lineNumber)
    {
        if (!isset($this->lookupTable[$filename][$lineNumber])) {
            $this->updateLookupTable();
        }

        if (isset($this->lookupTable[$filename][$lineNumber])) {
            return $this->lookupTable[$filename][$lineNumber];
        } else {
            return $filename . ':' . $lineNumber;
        }
    }

    private function updateLookupTable()
    {
        $this->processClassesAndTraits();
        $this->processFunctions();
    }

    private function processClassesAndTraits()
    {
        foreach (array_merge(get_declared_classes(), get_declared_traits()) as $classOrTrait) {
            if (isset($this->processedClasses[$classOrTrait])) {
                continue;
            }

            $reflector = new \ReflectionClass($classOrTrait);

            foreach ($reflector->getMethods() as $method) {
                $this->processFunctionOrMethod($method);
            }

            $this->processedClasses[$classOrTrait] = true;
        }
    }

    private function processFunctions()
    {
        foreach (get_defined_functions()['user'] as $function) {
            if (isset($this->processedFunctions[$function])) {
                continue;
            }

            $this->processFunctionOrMethod(new \ReflectionFunction($function));

            $this->processedFunctions[$function] = true;
        }
    }

    /**
     * @param \ReflectionFunctionAbstract $functionOrMethod
     */
    private function processFunctionOrMethod(\ReflectionFunctionAbstract $functionOrMethod)
    {
        if ($functionOrMethod->isInternal()) {
            return;
        }

        $name = $functionOrMethod->getName();

        if ($functionOrMethod instanceof \ReflectionMethod) {
            $name = $functionOrMethod->getDeclaringClass()->getName() . '::' . $name;
        }

        if (!isset($this->lookupTable[$functionOrMethod->getFileName()])) {
            $this->lookupTable[$functionOrMethod->getFileName()] = [];
        }

        foreach (range($functionOrMethod->getStartLine(), $functionOrMethod->getEndLine()) as $line) {
            $this->lookupTable[$functionOrMethod->getFileName()][$line] = $name;
        }
    }
}
{
    "name": "sebastian/diff",
    "description": "Diff implementation",
    "keywords": ["diff"],
    "homepage": "https://github.com/sebastianbergmann/diff",
    "license": "BSD-3-Clause",
    "authors": [
        {
            "name": "Sebastian Bergmann",
            "email": "sebastian@phpunit.de"
        },
        {
            "name": "Kore Nordmann",
            "email": "mail@kore-nordmann.de"
        }
    ],
    "require": {
        "php": "^5.3.3 || ^7.0"
    },
    "require-dev": {
        "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
    },
    "autoload": {
        "classmap": [
            "src/"
        ]
    },
    "extra": {
        "branch-alias": {
            "dev-master": "1.4-dev"
        }
    }
}
<?php
/*
 * This file is part of sebastian/diff.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\Diff;

class Diff
{
    /**
     * @var string
     */
    private $from;

    /**
     * @var string
     */
    private $to;

    /**
     * @var Chunk[]
     */
    private $chunks;

    /**
     * @param string  $from
     * @param string  $to
     * @param Chunk[] $chunks
     */
    public function __construct($from, $to, array $chunks = array())
    {
        $this->from   = $from;
        $this->to     = $to;
        $this->chunks = $chunks;
    }

    /**
     * @return string
     */
    public function getFrom()
    {
        return $this->from;
    }

    /**
     * @return string
     */
    public function getTo()
    {
        return $this->to;
    }

    /**
     * @return Chunk[]
     */
    public function getChunks()
    {
        return $this->chunks;
    }

    /**
     * @param Chunk[] $chunks
     */
    public function setChunks(array $chunks)
    {
        $this->chunks = $chunks;
    }
}
<?php
/*
 * This file is part of sebastian/diff.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\Diff;

/**
 * Unified diff parser.
 */
class Parser
{
    /**
     * @param string $string
     *
     * @return Diff[]
     */
    public function parse($string)
    {
        $lines = \preg_split('(\r\n|\r|\n)', $string);

        if (!empty($lines) && $lines[\count($lines) - 1] == '') {
            \array_pop($lines);
        }

        $lineCount = \count($lines);
        $diffs     = array();
        $diff      = null;
        $collected = array();

        for ($i = 0; $i < $lineCount; ++$i) {
            if (\preg_match('(^---\\s+(?P<file>\\S+))', $lines[$i], $fromMatch) &&
                \preg_match('(^\\+\\+\\+\\s+(?P<file>\\S+))', $lines[$i + 1], $toMatch)) {
                if ($diff !== null) {
                    $this->parseFileDiff($diff, $collected);

                    $diffs[]   = $diff;
                    $collected = array();
                }

                $diff = new Diff($fromMatch['file'], $toMatch['file']);

                ++$i;
            } else {
                if (\preg_match('/^(?:diff --git |index [\da-f\.]+|[+-]{3} [ab])/', $lines[$i])) {
                    continue;
                }

                $collected[] = $lines[$i];
            }
        }

        if ($diff !== null && \count($collected)) {
            $this->parseFileDiff($diff, $collected);

            $diffs[] = $diff;
        }

        return $diffs;
    }

    /**
     * @param Diff  $diff
     * @param array $lines
     */
    private function parseFileDiff(Diff $diff, array $lines)
    {
        $chunks = array();
        $chunk  = null;

        foreach ($lines as $line) {
            if (\preg_match('/^@@\s+-(?P<start>\d+)(?:,\s*(?P<startrange>\d+))?\s+\+(?P<end>\d+)(?:,\s*(?P<endrange>\d+))?\s+@@/', $line, $match)) {
                $chunk = new Chunk(
                    $match['start'],
                    isset($match['startrange']) ? \max(1, $match['startrange']) : 1,
                    $match['end'],
                    isset($match['endrange']) ? \max(1, $match['endrange']) : 1
                );

                $chunks[]  = $chunk;
                $diffLines = array();

                continue;
            }

            if (\preg_match('/^(?P<type>[+ -])?(?P<line>.*)/', $line, $match)) {
                $type = Line::UNCHANGED;

                if ($match['type'] === '+') {
                    $type = Line::ADDED;
                } elseif ($match['type'] === '-') {
                    $type = Line::REMOVED;
                }

                $diffLines[] = new Line($type, $match['line']);

                if (null !== $chunk) {
                    $chunk->setLines($diffLines);
                }
            }
        }

        $diff->setChunks($chunks);
    }
}
<?php
/*
 * This file is part of sebastian/diff.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\Diff\LCS;

/**
 * Time-efficient implementation of longest common subsequence calculation.
 */
class TimeEfficientImplementation implements LongestCommonSubsequence
{
    /**
     * Calculates the longest common subsequence of two arrays.
     *
     * @param array $from
     * @param array $to
     *
     * @return array
     */
    public function calculate(array $from, array $to)
    {
        $common     = array();
        $fromLength = \count($from);
        $toLength   = \count($to);
        $width      = $fromLength + 1;
        $matrix     = new \SplFixedArray($width * ($toLength + 1));

        for ($i = 0; $i <= $fromLength; ++$i) {
            $matrix[$i] = 0;
        }

        for ($j = 0; $j <= $toLength; ++$j) {
            $matrix[$j * $width] = 0;
        }

        for ($i = 1; $i <= $fromLength; ++$i) {
            for ($j = 1; $j <= $toLength; ++$j) {
                $o          = ($j * $width) + $i;
                $matrix[$o] = \max(
                    $matrix[$o - 1],
                    $matrix[$o - $width],
                    $from[$i - 1] === $to[$j - 1] ? $matrix[$o - $width - 1] + 1 : 0
                );
            }
        }

        $i = $fromLength;
        $j = $toLength;

        while ($i > 0 && $j > 0) {
            if ($from[$i - 1] === $to[$j - 1]) {
                $common[] = $from[$i - 1];
                --$i;
                --$j;
            } else {
                $o = ($j * $width) + $i;

                if ($matrix[$o - $width] > $matrix[$o - 1]) {
                    --$j;
                } else {
                    --$i;
                }
            }
        }

        return \array_reverse($common);
    }
}
<?php
/*
 * This file is part of sebastian/diff.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\Diff\LCS;

/**
 * Interface for implementations of longest common subsequence calculation.
 */
interface LongestCommonSubsequence
{
    /**
     * Calculates the longest common subsequence of two arrays.
     *
     * @param array $from
     * @param array $to
     *
     * @return array
     */
    public function calculate(array $from, array $to);
}
<?php
/*
 * This file is part of sebastian/diff.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\Diff\LCS;

/**
 * Memory-efficient implementation of longest common subsequence calculation.
 */
class MemoryEfficientImplementation implements LongestCommonSubsequence
{
    /**
     * Calculates the longest common subsequence of two arrays.
     *
     * @param array $from
     * @param array $to
     *
     * @return array
     */
    public function calculate(array $from, array $to)
    {
        $cFrom = \count($from);
        $cTo   = \count($to);

        if ($cFrom === 0) {
            return array();
        }

        if ($cFrom === 1) {
            if (\in_array($from[0], $to, true)) {
                return array($from[0]);
            }

            return array();
        }

        $i         = (int) ($cFrom / 2);
        $fromStart = \array_slice($from, 0, $i);
        $fromEnd   = \array_slice($from, $i);
        $llB       = $this->length($fromStart, $to);
        $llE       = $this->length(\array_reverse($fromEnd), \array_reverse($to));
        $jMax      = 0;
        $max       = 0;

        for ($j = 0; $j <= $cTo; $j++) {
            $m = $llB[$j] + $llE[$cTo - $j];

            if ($m >= $max) {
                $max  = $m;
                $jMax = $j;
            }
        }

        $toStart = \array_slice($to, 0, $jMax);
        $toEnd   = \array_slice($to, $jMax);

        return \array_merge(
            $this->calculate($fromStart, $toStart),
            $this->calculate($fromEnd, $toEnd)
        );
    }

    /**
     * @param array $from
     * @param array $to
     *
     * @return array
     */
    private function length(array $from, array $to)
    {
        $current = \array_fill(0, \count($to) + 1, 0);
        $cFrom   = \count($from);
        $cTo     = \count($to);

        for ($i = 0; $i < $cFrom; $i++) {
            $prev = $current;

            for ($j = 0; $j < $cTo; $j++) {
                if ($from[$i] === $to[$j]) {
                    $current[$j + 1] = $prev[$j] + 1;
                } else {
                    $current[$j + 1] = \max($current[$j], $prev[$j + 1]);
                }
            }
        }

        return $current;
    }
}
<?php
/*
 * This file is part of sebastian/diff.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\Diff;

class Chunk
{
    /**
     * @var int
     */
    private $start;

    /**
     * @var int
     */
    private $startRange;

    /**
     * @var int
     */
    private $end;

    /**
     * @var int
     */
    private $endRange;

    /**
     * @var array
     */
    private $lines;

    /**
     * @param int   $start
     * @param int   $startRange
     * @param int   $end
     * @param int   $endRange
     * @param array $lines
     */
    public function __construct($start = 0, $startRange = 1, $end = 0, $endRange = 1, array $lines = array())
    {
        $this->start      = (int) $start;
        $this->startRange = (int) $startRange;
        $this->end        = (int) $end;
        $this->endRange   = (int) $endRange;
        $this->lines      = $lines;
    }

    /**
     * @return int
     */
    public function getStart()
    {
        return $this->start;
    }

    /**
     * @return int
     */
    public function getStartRange()
    {
        return $this->startRange;
    }

    /**
     * @return int
     */
    public function getEnd()
    {
        return $this->end;
    }

    /**
     * @return int
     */
    public function getEndRange()
    {
        return $this->endRange;
    }

    /**
     * @return array
     */
    public function getLines()
    {
        return $this->lines;
    }

    /**
     * @param array $lines
     */
    public function setLines(array $lines)
    {
        $this->lines = $lines;
    }
}
<?php
/*
 * This file is part of sebastian/diff.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\Diff;

use SebastianBergmann\Diff\LCS\LongestCommonSubsequence;
use SebastianBergmann\Diff\LCS\TimeEfficientImplementation;
use SebastianBergmann\Diff\LCS\MemoryEfficientImplementation;

/**
 * Diff implementation.
 */
class Differ
{
    /**
     * @var string
     */
    private $header;

    /**
     * @var bool
     */
    private $showNonDiffLines;

    /**
     * @param string $header
     * @param bool   $showNonDiffLines
     */
    public function __construct($header = "--- Original\n+++ New\n", $showNonDiffLines = true)
    {
        $this->header           = $header;
        $this->showNonDiffLines = $showNonDiffLines;
    }

    /**
     * Returns the diff between two arrays or strings as string.
     *
     * @param array|string             $from
     * @param array|string             $to
     * @param LongestCommonSubsequence $lcs
     *
     * @return string
     */
    public function diff($from, $to, LongestCommonSubsequence $lcs = null)
    {
        $from  = $this->validateDiffInput($from);
        $to    = $this->validateDiffInput($to);
        $diff  = $this->diffToArray($from, $to, $lcs);
        $old   = $this->checkIfDiffInOld($diff);
        $start = isset($old[0]) ? $old[0] : 0;
        $end   = \count($diff);

        if ($tmp = \array_search($end, $old)) {
            $end = $tmp;
        }

        return $this->getBuffer($diff, $old, $start, $end);
    }

    /**
     * Casts variable to string if it is not a string or array.
     *
     * @param mixed $input
     *
     * @return string
     */
    private function validateDiffInput($input)
    {
        if (!\is_array($input) && !\is_string($input)) {
            return (string) $input;
        }

        return $input;
    }

    /**
     * Takes input of the diff array and returns the old array.
     * Iterates through diff line by line,
     *
     * @param array $diff
     *
     * @return array
     */
    private function checkIfDiffInOld(array $diff)
    {
        $inOld = false;
        $i     = 0;
        $old   = array();

        foreach ($diff as $line) {
            if ($line[1] === 0 /* OLD */) {
                if ($inOld === false) {
                    $inOld = $i;
                }
            } elseif ($inOld !== false) {
                if (($i - $inOld) > 5) {
                    $old[$inOld] = $i - 1;
                }

                $inOld = false;
            }

            ++$i;
        }

        return $old;
    }

    /**
     * Generates buffer in string format, returning the patch.
     *
     * @param array $diff
     * @param array $old
     * @param int   $start
     * @param int   $end
     *
     * @return string
     */
    private function getBuffer(array $diff, array $old, $start, $end)
    {
        $buffer = $this->header;

        if (!isset($old[$start])) {
            $buffer = $this->getDiffBufferElementNew($diff, $buffer, $start);
            ++$start;
        }

        for ($i = $start; $i < $end; $i++) {
            if (isset($old[$i])) {
                $i      = $old[$i];
                $buffer = $this->getDiffBufferElementNew($diff, $buffer, $i);
            } else {
                $buffer = $this->getDiffBufferElement($diff, $buffer, $i);
            }
        }

        return $buffer;
    }

    /**
     * Gets individual buffer element.
     *
     * @param array  $diff
     * @param string $buffer
     * @param int    $diffIndex
     *
     * @return string
     */
    private function getDiffBufferElement(array $diff, $buffer, $diffIndex)
    {
        if ($diff[$diffIndex][1] === 1 /* ADDED */) {
            $buffer .= '+' . $diff[$diffIndex][0] . "\n";
        } elseif ($diff[$diffIndex][1] === 2 /* REMOVED */) {
            $buffer .= '-' . $diff[$diffIndex][0] . "\n";
        } elseif ($this->showNonDiffLines === true) {
            $buffer .= ' ' . $diff[$diffIndex][0] . "\n";
        }

        return $buffer;
    }

    /**
     * Gets individual buffer element with opening.
     *
     * @param array  $diff
     * @param string $buffer
     * @param int    $diffIndex
     *
     * @return string
     */
    private function getDiffBufferElementNew(array $diff, $buffer, $diffIndex)
    {
        if ($this->showNonDiffLines === true) {
            $buffer .= "@@ @@\n";
        }

        return $this->getDiffBufferElement($diff, $buffer, $diffIndex);
    }

    /**
     * Returns the diff between two arrays or strings as array.
     *
     * Each array element contains two elements:
     *   - [0] => mixed $token
     *   - [1] => 2|1|0
     *
     * - 2: REMOVED: $token was removed from $from
     * - 1: ADDED: $token was added to $from
     * - 0: OLD: $token is not changed in $to
     *
     * @param array|string             $from
     * @param array|string             $to
     * @param LongestCommonSubsequence $lcs
     *
     * @return array
     */
    public function diffToArray($from, $to, LongestCommonSubsequence $lcs = null)
    {
        if (\is_string($from)) {
            $fromMatches = $this->getNewLineMatches($from);
            $from        = $this->splitStringByLines($from);
        } elseif (\is_array($from)) {
            $fromMatches = array();
        } else {
            throw new \InvalidArgumentException('"from" must be an array or string.');
        }

        if (\is_string($to)) {
            $toMatches = $this->getNewLineMatches($to);
            $to        = $this->splitStringByLines($to);
        } elseif (\is_array($to)) {
            $toMatches = array();
        } else {
            throw new \InvalidArgumentException('"to" must be an array or string.');
        }

        list($from, $to, $start, $end) = self::getArrayDiffParted($from, $to);

        if ($lcs === null) {
            $lcs = $this->selectLcsImplementation($from, $to);
        }

        $common = $lcs->calculate(\array_values($from), \array_values($to));
        $diff   = array();

        if ($this->detectUnmatchedLineEndings($fromMatches, $toMatches)) {
            $diff[] = array(
                '#Warning: Strings contain different line endings!',
                0
            );
        }

        foreach ($start as $token) {
            $diff[] = array($token, 0 /* OLD */);
        }

        \reset($from);
        \reset($to);

        foreach ($common as $token) {
            while (($fromToken = \reset($from)) !== $token) {
                $diff[] = array(\array_shift($from), 2 /* REMOVED */);
            }

            while (($toToken = \reset($to)) !== $token) {
                $diff[] = array(\array_shift($to), 1 /* ADDED */);
            }

            $diff[] = array($token, 0 /* OLD */);

            \array_shift($from);
            \array_shift($to);
        }

        while (($token = \array_shift($from)) !== null) {
            $diff[] = array($token, 2 /* REMOVED */);
        }

        while (($token = \array_shift($to)) !== null) {
            $diff[] = array($token, 1 /* ADDED */);
        }

        foreach ($end as $token) {
            $diff[] = array($token, 0 /* OLD */);
        }

        return $diff;
    }

    /**
     * Get new strings denoting new lines from a given string.
     *
     * @param string $string
     *
     * @return array
     */
    private function getNewLineMatches($string)
    {
        \preg_match_all('(\r\n|\r|\n)', $string, $stringMatches);

        return $stringMatches;
    }

    /**
     * Checks if input is string, if so it will split it line-by-line.
     *
     * @param string $input
     *
     * @return array
     */
    private function splitStringByLines($input)
    {
        return \preg_split('(\r\n|\r|\n)', $input);
    }

    /**
     * @param array $from
     * @param array $to
     *
     * @return LongestCommonSubsequence
     */
    private function selectLcsImplementation(array $from, array $to)
    {
        // We do not want to use the time-efficient implementation if its memory
        // footprint will probably exceed this value. Note that the footprint
        // calculation is only an estimation for the matrix and the LCS method
        // will typically allocate a bit more memory than this.
        $memoryLimit = 100 * 1024 * 1024;

        if ($this->calculateEstimatedFootprint($from, $to) > $memoryLimit) {
            return new MemoryEfficientImplementation;
        }

        return new TimeEfficientImplementation;
    }

    /**
     * Calculates the estimated memory footprint for the DP-based method.
     *
     * @param array $from
     * @param array $to
     *
     * @return int|float
     */
    private function calculateEstimatedFootprint(array $from, array $to)
    {
        $itemSize = PHP_INT_SIZE === 4 ? 76 : 144;

        return $itemSize * \pow(\min(\count($from), \count($to)), 2);
    }

    /**
     * Returns true if line ends don't match on fromMatches and toMatches.
     *
     * @param array $fromMatches
     * @param array $toMatches
     *
     * @return bool
     */
    private function detectUnmatchedLineEndings(array $fromMatches, array $toMatches)
    {
        return isset($fromMatches[0], $toMatches[0]) &&
               \count($fromMatches[0]) === \count($toMatches[0]) &&
               $fromMatches[0] !== $toMatches[0];
    }

    /**
     * @param array $from
     * @param array $to
     *
     * @return array
     */
    private static function getArrayDiffParted(array &$from, array &$to)
    {
        $start = array();
        $end   = array();

        \reset($to);

        foreach ($from as $k => $v) {
            $toK = \key($to);

            if ($toK === $k && $v === $to[$k]) {
                $start[$k] = $v;

                unset($from[$k], $to[$k]);
            } else {
                break;
            }
        }

        \end($from);
        \end($to);

        do {
            $fromK = \key($from);
            $toK   = \key($to);

            if (null === $fromK || null === $toK || \current($from) !== \current($to)) {
                break;
            }

            \prev($from);
            \prev($to);

            $end = array($fromK => $from[$fromK]) + $end;
            unset($from[$fromK], $to[$toK]);
        } while (true);

        return array($from, $to, $start, $end);
    }
}
<?php
/*
 * This file is part of sebastian/diff.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\Diff;

class Line
{
    const ADDED     = 1;
    const REMOVED   = 2;
    const UNCHANGED = 3;

    /**
     * @var int
     */
    private $type;

    /**
     * @var string
     */
    private $content;

    /**
     * @param int    $type
     * @param string $content
     */
    public function __construct($type = self::UNCHANGED, $content = '')
    {
        $this->type    = $type;
        $this->content = $content;
    }

    /**
     * @return string
     */
    public function getContent()
    {
        return $this->content;
    }

    /**
     * @return int
     */
    public function getType()
    {
        return $this->type;
    }
}
{
    "name": "sebastian/recursion-context",
    "description": "Provides functionality to recursively process PHP variables",
    "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
    "license": "BSD-3-Clause",
    "authors": [
        {
            "name": "Sebastian Bergmann",
            "email": "sebastian@phpunit.de"
        },
        {
            "name": "Jeff Welch",
            "email": "whatthejeff@gmail.com"
        },
        {
            "name": "Adam Harvey",
            "email": "aharvey@php.net"
        }
    ],
    "require": {
        "php": ">=5.3.3"
    },
    "require-dev": {
        "phpunit/phpunit": "~4.4"
    },
    "autoload": {
        "classmap": [
            "src/"
        ]
    },
    "extra": {
        "branch-alias": {
            "dev-master": "2.0.x-dev"
        }
    }
}
<?php
/*
 * This file is part of the Recursion Context package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\RecursionContext;

/**
 */
final class InvalidArgumentException extends \InvalidArgumentException implements Exception
{
}
<?php
/*
 * This file is part of the Recursion Context package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\RecursionContext;

/**
 * A context containing previously processed arrays and objects
 * when recursively processing a value.
 */
final class Context
{
    /**
     * @var array[]
     */
    private $arrays;

    /**
     * @var \SplObjectStorage
     */
    private $objects;

    /**
     * Initialises the context
     */
    public function __construct()
    {
        $this->arrays  = array();
        $this->objects = new \SplObjectStorage;
    }

    /**
     * Adds a value to the context.
     *
     * @param array|object $value The value to add.
     *
     * @return int|string The ID of the stored value, either as a string or integer.
     *
     * @throws InvalidArgumentException Thrown if $value is not an array or object
     */
    public function add(&$value)
    {
        if (is_array($value)) {
            return $this->addArray($value);
        } elseif (is_object($value)) {
            return $this->addObject($value);
        }

        throw new InvalidArgumentException(
            'Only arrays and objects are supported'
        );
    }

    /**
     * Checks if the given value exists within the context.
     *
     * @param array|object $value The value to check.
     *
     * @return int|string|false The string or integer ID of the stored value if it has already been seen, or false if the value is not stored.
     *
     * @throws InvalidArgumentException Thrown if $value is not an array or object
     */
    public function contains(&$value)
    {
        if (is_array($value)) {
            return $this->containsArray($value);
        } elseif (is_object($value)) {
            return $this->containsObject($value);
        }

        throw new InvalidArgumentException(
            'Only arrays and objects are supported'
        );
    }

    /**
     * @param array $array
     *
     * @return bool|int
     */
    private function addArray(array &$array)
    {
        $key = $this->containsArray($array);

        if ($key !== false) {
            return $key;
        }

        $key            = count($this->arrays);
        $this->arrays[] = &$array;

        if (!isset($array[PHP_INT_MAX]) && !isset($array[PHP_INT_MAX - 1])) {
            $array[] = $key;
            $array[] = $this->objects;
        } else { /* cover the improbable case too */
            do {
                $key = random_int(PHP_INT_MIN, PHP_INT_MAX);
            } while (isset($array[$key]));

            $array[$key] = $key;

            do {
                $key = random_int(PHP_INT_MIN, PHP_INT_MAX);
            } while (isset($array[$key]));

            $array[$key] = $this->objects;
        }

        return $key;
    }

    /**
     * @param object $object
     *
     * @return string
     */
    private function addObject($object)
    {
        if (!$this->objects->contains($object)) {
            $this->objects->attach($object);
        }

        return spl_object_hash($object);
    }

    /**
     * @param array $array
     *
     * @return int|false
     */
    private function containsArray(array &$array)
    {
        $end = array_slice($array, -2);

        return isset($end[1]) && $end[1] === $this->objects ? $end[0] : false;
    }

    /**
     * @param object $value
     *
     * @return string|false
     */
    private function containsObject($value)
    {
        if ($this->objects->contains($value)) {
            return spl_object_hash($value);
        }

        return false;
    }

    public function __destruct()
    {
        foreach ($this->arrays as &$array) {
            if (is_array($array)) {
                array_pop($array);
                array_pop($array);
            }
        }
    }
}
<?php
/*
 * This file is part of the Recursion Context package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\RecursionContext;

/**
 */
interface Exception
{
}
{
    "name": "sebastian/version",
    "description": "Library that helps with managing the version number of Git-hosted PHP projects",
    "homepage": "https://github.com/sebastianbergmann/version",
    "license": "BSD-3-Clause",
    "authors": [
        {
            "name": "Sebastian Bergmann",
            "email": "sebastian@phpunit.de",
            "role": "lead"
        }
    ],
    "support": {
        "issues": "https://github.com/sebastianbergmann/version/issues"
    },
    "require": {
        "php": ">=5.6"
    },
    "autoload": {
        "classmap": [
            "src/"
        ]
    },
    "extra": {
        "branch-alias": {
            "dev-master": "2.0.x-dev"
        }
    }
}
<?php
/*
 * This file is part of the Version package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann;

/**
 * @since Class available since Release 1.0.0
 */
class Version
{
    /**
     * @var string
     */
    private $path;

    /**
     * @var string
     */
    private $release;

    /**
     * @var string
     */
    private $version;

    /**
     * @param string $release
     * @param string $path
     */
    public function __construct($release, $path)
    {
        $this->release = $release;
        $this->path    = $path;
    }

    /**
     * @return string
     */
    public function getVersion()
    {
        if ($this->version === null) {
            if (count(explode('.', $this->release)) == 3) {
                $this->version = $this->release;
            } else {
                $this->version = $this->release . '-dev';
            }

            $git = $this->getGitInformation($this->path);

            if ($git) {
                if (count(explode('.', $this->release)) == 3) {
                    $this->version = $git;
                } else {
                    $git = explode('-', $git);

                    $this->version = $this->release . '-' . end($git);
                }
            }
        }

        return $this->version;
    }

    /**
     * @param string $path
     *
     * @return bool|string
     */
    private function getGitInformation($path)
    {
        if (!is_dir($path . DIRECTORY_SEPARATOR . '.git')) {
            return false;
        }

        $process = proc_open(
            'git describe --tags',
            [
                1 => ['pipe', 'w'],
                2 => ['pipe', 'w'],
            ],
            $pipes,
            $path
        );

        if (!is_resource($process)) {
            return false;
        }

        $result = trim(stream_get_contents($pipes[1]));

        fclose($pipes[1]);
        fclose($pipes[2]);

        $returnCode = proc_close($process);

        if ($returnCode !== 0) {
            return false;
        }

        return $result;
    }
}
{
    "name": "sebastian/environment",
    "description": "Provides functionality to handle HHVM/PHP environments",
    "keywords": ["environment","hhvm","xdebug"],
    "homepage": "http://www.github.com/sebastianbergmann/environment",
    "license": "BSD-3-Clause",
    "authors": [
        {
            "name": "Sebastian Bergmann",
            "email": "sebastian@phpunit.de"
        }
    ],
    "prefer-stable": true,
    "require": {
        "php": "^5.6 || ^7.0"
    },
    "require-dev": {
        "phpunit/phpunit": "^5.0"
    },
    "autoload": {
        "classmap": [
            "src/"
        ]
    },
    "extra": {
        "branch-alias": {
            "dev-master": "2.0.x-dev"
        }
    }
}
<?php
/*
 * This file is part of the Environment package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\Environment;

/**
 * Utility class for HHVM/PHP environment handling.
 */
class Runtime
{
    /**
     * @var string
     */
    private static $binary;

    /**
     * Returns true when Xdebug is supported or
     * the runtime used is PHPDBG (PHP >= 7.0).
     *
     * @return bool
     */
    public function canCollectCodeCoverage()
    {
        return $this->hasXdebug() || $this->hasPHPDBGCodeCoverage();
    }

    /**
     * Returns the path to the binary of the current runtime.
     * Appends ' --php' to the path when the runtime is HHVM.
     *
     * @return string
     */
    public function getBinary()
    {
        // HHVM
        if (self::$binary === null && $this->isHHVM()) {
            if ((self::$binary = getenv('PHP_BINARY')) === false) {
                self::$binary = PHP_BINARY;
            }

            self::$binary = escapeshellarg(self::$binary) . ' --php';
        }

        // PHP >= 5.4.0
        if (self::$binary === null && defined('PHP_BINARY')) {
            if (PHP_BINARY !== '') {
                self::$binary = escapeshellarg(PHP_BINARY);
            }
        }

        // PHP < 5.4.0
        if (self::$binary === null) {
            if (PHP_SAPI == 'cli' && isset($_SERVER['_'])) {
                if (strpos($_SERVER['_'], 'phpunit') !== false) {
                    $file = file($_SERVER['_']);

                    if (strpos($file[0], ' ') !== false) {
                        $tmp          = explode(' ', $file[0]);
                        self::$binary = escapeshellarg(trim($tmp[1]));
                    } else {
                        self::$binary = escapeshellarg(ltrim(trim($file[0]), '#!'));
                    }
                } elseif (strpos(basename($_SERVER['_']), 'php') !== false) {
                    self::$binary = escapeshellarg($_SERVER['_']);
                }
            }
        }

        if (self::$binary === null) {
            $possibleBinaryLocations = [
                PHP_BINDIR . '/php',
                PHP_BINDIR . '/php-cli.exe',
                PHP_BINDIR . '/php.exe'
            ];

            foreach ($possibleBinaryLocations as $binary) {
                if (is_readable($binary)) {
                    self::$binary = escapeshellarg($binary);
                    break;
                }
            }
        }

        if (self::$binary === null) {
            self::$binary = 'php';
        }

        return self::$binary;
    }

    /**
     * @return string
     */
    public function getNameWithVersion()
    {
        return $this->getName() . ' ' . $this->getVersion();
    }

    /**
     * @return string
     */
    public function getName()
    {
        if ($this->isHHVM()) {
            return 'HHVM';
        } elseif ($this->isPHPDBG()) {
            return 'PHPDBG';
        } else {
            return 'PHP';
        }
    }

    /**
     * @return string
     */
    public function getVendorUrl()
    {
        if ($this->isHHVM()) {
            return 'http://hhvm.com/';
        } else {
            return 'https://secure.php.net/';
        }
    }

    /**
     * @return string
     */
    public function getVersion()
    {
        if ($this->isHHVM()) {
            return HHVM_VERSION;
        } else {
            return PHP_VERSION;
        }
    }

    /**
     * Returns true when the runtime used is PHP and Xdebug is loaded.
     *
     * @return bool
     */
    public function hasXdebug()
    {
        return ($this->isPHP() || $this->isHHVM()) && extension_loaded('xdebug');
    }

    /**
     * Returns true when the runtime used is HHVM.
     *
     * @return bool
     */
    public function isHHVM()
    {
        return defined('HHVM_VERSION');
    }

    /**
     * Returns true when the runtime used is PHP without the PHPDBG SAPI.
     *
     * @return bool
     */
    public function isPHP()
    {
        return !$this->isHHVM() && !$this->isPHPDBG();
    }

    /**
     * Returns true when the runtime used is PHP with the PHPDBG SAPI.
     *
     * @return bool
     */
    public function isPHPDBG()
    {
        return PHP_SAPI === 'phpdbg' && !$this->isHHVM();
    }

    /**
     * Returns true when the runtime used is PHP with the PHPDBG SAPI
     * and the phpdbg_*_oplog() functions are available (PHP >= 7.0).
     *
     * @return bool
     */
    public function hasPHPDBGCodeCoverage()
    {
        return $this->isPHPDBG() && function_exists('phpdbg_start_oplog');
    }
}
<?php
/*
 * This file is part of the Environment package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\Environment;

/**
 */
class Console
{
    const STDIN  = 0;
    const STDOUT = 1;
    const STDERR = 2;

    /**
     * Returns true if STDOUT supports colorization.
     *
     * This code has been copied and adapted from
     * Symfony\Component\Console\Output\OutputStream.
     *
     * @return bool
     */
    public function hasColorSupport()
    {
        if (DIRECTORY_SEPARATOR == '\\') {
            return false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM');
        }

        if (!defined('STDOUT')) {
            return false;
        }

        return $this->isInteractive(STDOUT);
    }

    /**
     * Returns the number of columns of the terminal.
     *
     * @return int
     */
    public function getNumberOfColumns()
    {
        if (DIRECTORY_SEPARATOR == '\\') {
            $columns = 80;

            if (preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim(getenv('ANSICON')), $matches)) {
                $columns = $matches[1];
            } elseif (function_exists('proc_open')) {
                $process = proc_open(
                    'mode CON',
                    [
                        1 => ['pipe', 'w'],
                        2 => ['pipe', 'w']
                    ],
                    $pipes,
                    null,
                    null,
                    ['suppress_errors' => true]
                );

                if (is_resource($process)) {
                    $info = stream_get_contents($pipes[1]);

                    fclose($pipes[1]);
                    fclose($pipes[2]);
                    proc_close($process);

                    if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
                        $columns = $matches[2];
                    }
                }
            }

            return $columns - 1;
        }

        if (!$this->isInteractive(self::STDIN)) {
            return 80;
        }

        if (function_exists('shell_exec') && preg_match('#\d+ (\d+)#', shell_exec('stty size'), $match) === 1) {
            if ((int) $match[1] > 0) {
                return (int) $match[1];
            }
        }

        if (function_exists('shell_exec') && preg_match('#columns = (\d+);#', shell_exec('stty'), $match) === 1) {
            if ((int) $match[1] > 0) {
                return (int) $match[1];
            }
        }

        return 80;
    }

    /**
     * Returns if the file descriptor is an interactive terminal or not.
     *
     * @param int|resource $fileDescriptor
     *
     * @return bool
     */
    public function isInteractive($fileDescriptor = self::STDOUT)
    {
        return function_exists('posix_isatty') && @posix_isatty($fileDescriptor);
    }
}
{
    "name": "sebastian/exporter",
    "description": "Provides the functionality to export PHP variables for visualization",
    "keywords": ["exporter","export"],
    "homepage": "http://www.github.com/sebastianbergmann/exporter",
    "license": "BSD-3-Clause",
    "authors": [
        {
            "name": "Sebastian Bergmann",
            "email": "sebastian@phpunit.de"
        },
        {
            "name": "Jeff Welch",
            "email": "whatthejeff@gmail.com"
        },
        {
            "name": "Volker Dusch",
            "email": "github@wallbash.com"
        },
        {
            "name": "Adam Harvey",
            "email": "aharvey@php.net"
        },
        {
            "name": "Bernhard Schussek",
            "email": "bschussek@2bepublished.at"
        }
    ],
    "require": {
        "php": ">=5.3.3",
        "sebastian/recursion-context": "~2.0"
    },
    "require-dev": {
        "phpunit/phpunit": "~4.4",
        "ext-mbstring": "*"
    },
    "autoload": {
        "classmap": [
            "src/"
        ]
    },
    "extra": {
        "branch-alias": {
            "dev-master": "2.0.x-dev"
        }
    }
}

<?php
/*
 * This file is part of the Exporter package.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\Exporter;

use SebastianBergmann\RecursionContext\Context;

/**
 * A nifty utility for visualizing PHP variables.
 *
 * <code>
 * <?php
 * use SebastianBergmann\Exporter\Exporter;
 *
 * $exporter = new Exporter;
 * print $exporter->export(new Exception);
 * </code>
 */
class Exporter
{
    /**
     * Exports a value as a string
     *
     * The output of this method is similar to the output of print_r(), but
     * improved in various aspects:
     *
     *  - NULL is rendered as "null" (instead of "")
     *  - TRUE is rendered as "true" (instead of "1")
     *  - FALSE is rendered as "false" (instead of "")
     *  - Strings are always quoted with single quotes
     *  - Carriage returns and newlines are normalized to \n
     *  - Recursion and repeated rendering is treated properly
     *
     * @param  mixed  $value
     * @param  int    $indentation The indentation level of the 2nd+ line
     * @return string
     */
    public function export($value, $indentation = 0)
    {
        return $this->recursiveExport($value, $indentation);
    }

    /**
     * @param  mixed   $data
     * @param  Context $context
     * @return string
     */
    public function shortenedRecursiveExport(&$data, Context $context = null)
    {
        $result   = array();
        $exporter = new self();

        if (!$context) {
            $context = new Context;
        }

        $array = $data;
        $context->add($data);

        foreach ($array as $key => $value) {
            if (is_array($value)) {
                if ($context->contains($data[$key]) !== false) {
                    $result[] = '*RECURSION*';
                }

                else {
                    $result[] = sprintf(
                        'array(%s)',
                        $this->shortenedRecursiveExport($data[$key], $context)
                    );
                }
            }

            else {
                $result[] = $exporter->shortenedExport($value);
            }
        }

        return implode(', ', $result);
    }

    /**
     * Exports a value into a single-line string
     *
     * The output of this method is similar to the output of
     * SebastianBergmann\Exporter\Exporter::export().
     *
     * Newlines are replaced by the visible string '\n'.
     * Contents of arrays and objects (if any) are replaced by '...'.
     *
     * @param  mixed  $value
     * @return string
     * @see    SebastianBergmann\Exporter\Exporter::export
     */
    public function shortenedExport($value)
    {
        if (is_string($value)) {
            $string = $this->export($value);

            if (function_exists('mb_strlen')) {
                if (mb_strlen($string) > 40) {
                    $string = mb_substr($string, 0, 30) . '...' . mb_substr($string, -7);
                }
            } else {
                if (strlen($string) > 40) {
                    $string = substr($string, 0, 30) . '...' . substr($string, -7);
                }
            }

            return str_replace("\n", '\n', $string);
        }

        if (is_object($value)) {
            return sprintf(
                '%s Object (%s)',
                get_class($value),
                count($this->toArray($value)) > 0 ? '...' : ''
            );
        }

        if (is_array($value)) {
            return sprintf(
                'Array (%s)',
                count($value) > 0 ? '...' : ''
            );
        }

        return $this->export($value);
    }

    /**
     * Converts an object to an array containing all of its private, protected
     * and public properties.
     *
     * @param  mixed $value
     * @return array
     */
    public function toArray($value)
    {
        if (!is_object($value)) {
            return (array) $value;
        }

        $array = array();

        foreach ((array) $value as $key => $val) {
            // properties are transformed to keys in the following way:
            // private   $property => "\0Classname\0property"
            // protected $property => "\0*\0property"
            // public    $property => "property"
            if (preg_match('/^\0.+\0(.+)$/', $key, $matches)) {
                $key = $matches[1];
            }

            // See https://github.com/php/php-src/commit/5721132
            if ($key === "\0gcdata") {
                continue;
            }

            $array[$key] = $val;
        }

        // Some internal classes like SplObjectStorage don't work with the
        // above (fast) mechanism nor with reflection in Zend.
        // Format the output similarly to print_r() in this case
        if ($value instanceof \SplObjectStorage) {
            // However, the fast method does work in HHVM, and exposes the
            // internal implementation. Hide it again.
            if (property_exists('\SplObjectStorage', '__storage')) {
                unset($array['__storage']);
            } elseif (property_exists('\SplObjectStorage', 'storage')) {
                unset($array['storage']);
            }

            if (property_exists('\SplObjectStorage', '__key')) {
                unset($array['__key']);
            }

            foreach ($value as $key => $val) {
                $array[spl_object_hash($val)] = array(
                    'obj' => $val,
                    'inf' => $value->getInfo(),
                );
            }
        }

        return $array;
    }

    /**
     * Recursive implementation of export
     *
     * @param  mixed                                       $value       The value to export
     * @param  int                                         $indentation The indentation level of the 2nd+ line
     * @param  \SebastianBergmann\RecursionContext\Context $processed   Previously processed objects
     * @return string
     * @see    SebastianBergmann\Exporter\Exporter::export
     */
    protected function recursiveExport(&$value, $indentation, $processed = null)
    {
        if ($value === null) {
            return 'null';
        }

        if ($value === true) {
            return 'true';
        }

        if ($value === false) {
            return 'false';
        }

        if (is_float($value) && floatval(intval($value)) === $value) {
            return "$value.0";
        }

        if (is_resource($value)) {
            return sprintf(
                'resource(%d) of type (%s)',
                $value,
                get_resource_type($value)
            );
        }

        if (is_string($value)) {
            // Match for most non printable chars somewhat taking multibyte chars into account
            if (preg_match('/[^\x09-\x0d\x1b\x20-\xff]/', $value)) {
                return 'Binary String: 0x' . bin2hex($value);
            }

            return "'" .
            str_replace(array("\r\n", "\n\r", "\r"), array("\n", "\n", "\n"), $value) .
            "'";
        }

        $whitespace = str_repeat(' ', 4 * $indentation);

        if (!$processed) {
            $processed = new Context;
        }

        if (is_array($value)) {
            if (($key = $processed->contains($value)) !== false) {
                return 'Array &' . $key;
            }

            $array  = $value;
            $key    = $processed->add($value);
            $values = '';

            if (count($array) > 0) {
                foreach ($array as $k => $v) {
                    $values .= sprintf(
                        '%s    %s => %s' . "\n",
                        $whitespace,
                        $this->recursiveExport($k, $indentation),
                        $this->recursiveExport($value[$k], $indentation + 1, $processed)
                    );
                }

                $values = "\n" . $values . $whitespace;
            }

            return sprintf('Array &%s (%s)', $key, $values);
        }

        if (is_object($value)) {
            $class = get_class($value);

            if ($hash = $processed->contains($value)) {
                return sprintf('%s Object &%s', $class, $hash);
            }

            $hash   = $processed->add($value);
            $values = '';
            $array  = $this->toArray($value);

            if (count($array) > 0) {
                foreach ($array as $k => $v) {
                    $values .= sprintf(
                        '%s    %s => %s' . "\n",
                        $whitespace,
                        $this->recursiveExport($k, $indentation),
                        $this->recursiveExport($v, $indentation + 1, $processed)
                    );
                }

                $values = "\n" . $values . $whitespace;
            }

            return sprintf('%s Object &%s (%s)', $class, $hash, $values);
        }

        return var_export($value, true);
    }
}
#!/usr/bin/env php
<?php
/*
 * This file is part of resource-operations.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

$functions         = require __DIR__ . '/arginfo.php';
$resourceFunctions = [];

foreach ($functions as $function => $arguments) {
    foreach ($arguments as $argument) {
        if ($argument == 'resource') {
            $resourceFunctions[] = $function;
        }
    }
}

$resourceFunctions = array_unique($resourceFunctions);
sort($resourceFunctions);

$buffer = <<<EOT
<?php
/*
 * This file is part of resource-operations.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\ResourceOperations;

class ResourceOperations
{
    /**
     * @return string[]
     */
    public static function getFunctions()
    {
        return [

EOT;

foreach ($resourceFunctions as $function) {
    $buffer .= sprintf("            '%s',\n", $function);
}

$buffer .= <<< EOT
        ];
    }
}

EOT;

file_put_contents(__DIR__ . '/../src/ResourceOperations.php', $buffer);

{
    "name": "sebastian/resource-operations",
    "description": "Provides a list of PHP built-in functions that operate on resources",
    "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
    "license": "BSD-3-Clause",
    "authors": [
        {
            "name": "Sebastian Bergmann",
            "email": "sebastian@phpunit.de"
        }
    ],
    "require": {
        "php": ">=5.6.0"
    },
    "autoload": {
        "classmap": [
            "src/"
        ]
    },
    "extra": {
        "branch-alias": {
            "dev-master": "1.0.x-dev"
        }
    }
}

<?php
/*
 * This file is part of resource-operations.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SebastianBergmann\ResourceOperations;

class ResourceOperations
{
    /**
     * @return string[]
     */
    public static function getFunctions()
    {
        return [
            'Directory::close',
            'Directory::read',
            'Directory::rewind',
            'HttpResponse::getRequestBodyStream',
            'HttpResponse::getStream',
            'MongoGridFSCursor::__construct',
            'MongoGridFSFile::getResource',
            'MysqlndUhConnection::stmtInit',
            'MysqlndUhConnection::storeResult',
            'MysqlndUhConnection::useResult',
            'PDF_new',
            'PDO::pgsqlLOBOpen',
            'RarEntry::getStream',
            'SQLite3::openBlob',
            'XMLWriter::openMemory',
            'XMLWriter::openURI',
            'ZipArchive::getStream',
            'bbcode_create',
            'bzopen',
            'crack_opendict',
            'cubrid_connect',
            'cubrid_connect_with_url',
            'cubrid_get_query_timeout',
            'cubrid_lob2_bind',
            'cubrid_lob2_close',
            'cubrid_lob2_export',
            'cubrid_lob2_import',
            'cubrid_lob2_new',
            'cubrid_lob2_read',
            'cubrid_lob2_seek',
            'cubrid_lob2_seek64',
            'cubrid_lob2_size',
            'cubrid_lob2_size64',
            'cubrid_lob2_tell',
            'cubrid_lob2_tell64',
            'cubrid_lob2_write',
            'cubrid_pconnect',
            'cubrid_pconnect_with_url',
            'cubrid_prepare',
            'cubrid_query',
            'cubrid_set_query_timeout',
            'cubrid_unbuffered_query',
            'curl_copy_handle',
            'curl_getinfo',
            'curl_init',
            'curl_multi_add_handle',
            'curl_multi_close',
            'curl_multi_exec',
            'curl_multi_getcontent',
            'curl_multi_info_read',
            'curl_multi_init',
            'curl_multi_remove_handle',
            'curl_multi_select',
            'curl_multi_setopt',
            'curl_pause',
            'curl_reset',
            'curl_setopt',
            'curl_setopt_array',
            'curl_share_close',
            'curl_share_init',
            'curl_share_setopt',
            'curl_unescape',
            'cyrus_connect',
            'db2_column_privileges',
            'db2_columns',
            'db2_connect',
            'db2_exec',
            'db2_foreign_keys',
            'db2_next_result',
            'db2_pconnect',
            'db2_prepare',
            'db2_primary_keys',
            'db2_procedure_columns',
            'db2_procedures',
            'db2_special_columns',
            'db2_statistics',
            'db2_table_privileges',
            'db2_tables',
            'dba_fetch',
            'dba_fetch 1',
            'dba_open',
            'dba_popen',
            'dbplus_aql',
            'dbplus_open',
            'dbplus_rcreate',
            'dbplus_ropen',
            'dbplus_rquery',
            'dbplus_sql',
            'deflate_init',
            'dio_open',
            'eio_busy',
            'eio_cancel',
            'eio_chmod',
            'eio_chown',
            'eio_close',
            'eio_custom',
            'eio_dup2',
            'eio_fallocate',
            'eio_fchmod',
            'eio_fchown',
            'eio_fdatasync',
            'eio_fstat',
            'eio_fstatvfs',
            'eio_fsync',
            'eio_ftruncate',
            'eio_futime',
            'eio_get_last_error',
            'eio_grp',
            'eio_grp_add',
            'eio_grp_cancel',
            'eio_grp_limit',
            'eio_link',
            'eio_lstat',
            'eio_mkdir',
            'eio_mknod',
            'eio_nop',
            'eio_open',
            'eio_read',
            'eio_readahead',
            'eio_readdir',
            'eio_readlink',
            'eio_realpath',
            'eio_rename',
            'eio_rmdir',
            'eio_seek',
            'eio_sendfile',
            'eio_stat',
            'eio_statvfs',
            'eio_symlink',
            'eio_sync',
            'eio_sync_file_range',
            'eio_syncfs',
            'eio_truncate',
            'eio_unlink',
            'eio_utime',
            'eio_write',
            'enchant_broker_free_dict',
            'enchant_broker_init',
            'enchant_broker_request_dict',
            'enchant_broker_request_pwl_dict',
            'event_base_new',
            'event_base_reinit',
            'event_buffer_new',
            'event_new',
            'event_priority_set',
            'event_timer_set',
            'expect_popen',
            'fam_monitor_collection',
            'fam_monitor_directory',
            'fam_monitor_file',
            'fam_open',
            'fann_cascadetrain_on_data',
            'fann_cascadetrain_on_file',
            'fann_clear_scaling_params',
            'fann_copy',
            'fann_create_from_file',
            'fann_create_shortcut_array',
            'fann_create_standard',
            'fann_create_standard_array',
            'fann_create_train',
            'fann_create_train_from_callback',
            'fann_descale_input',
            'fann_descale_output',
            'fann_descale_train',
            'fann_destroy',
            'fann_destroy_train',
            'fann_duplicate_train_data',
            'fann_get_MSE',
            'fann_get_activation_function',
            'fann_get_activation_steepness',
            'fann_get_bias_array',
            'fann_get_bit_fail',
            'fann_get_bit_fail_limit',
            'fann_get_cascade_activation_functions',
            'fann_get_cascade_activation_functions_count',
            'fann_get_cascade_activation_steepnesses',
            'fann_get_cascade_activation_steepnesses_count',
            'fann_get_cascade_candidate_change_fraction',
            'fann_get_cascade_candidate_limit',
            'fann_get_cascade_candidate_stagnation_epochs',
            'fann_get_cascade_max_cand_epochs',
            'fann_get_cascade_max_out_epochs',
            'fann_get_cascade_min_cand_epochs',
            'fann_get_cascade_min_out_epochs',
            'fann_get_cascade_num_candidate_groups',
            'fann_get_cascade_num_candidates',
            'fann_get_cascade_output_change_fraction',
            'fann_get_cascade_output_stagnation_epochs',
            'fann_get_cascade_weight_multiplier',
            'fann_get_connection_array',
            'fann_get_connection_rate',
            'fann_get_errno',
            'fann_get_errstr',
            'fann_get_layer_array',
            'fann_get_learning_momentum',
            'fann_get_learning_rate',
            'fann_get_network_type',
            'fann_get_num_input',
            'fann_get_num_layers',
            'fann_get_num_output',
            'fann_get_quickprop_decay',
            'fann_get_quickprop_mu',
            'fann_get_rprop_decrease_factor',
            'fann_get_rprop_delta_max',
            'fann_get_rprop_delta_min',
            'fann_get_rprop_delta_zero',
            'fann_get_rprop_increase_factor',
            'fann_get_sarprop_step_error_shift',
            'fann_get_sarprop_step_error_threshold_factor',
            'fann_get_sarprop_temperature',
            'fann_get_sarprop_weight_decay_shift',
            'fann_get_total_connections',
            'fann_get_total_neurons',
            'fann_get_train_error_function',
            'fann_get_train_stop_function',
            'fann_get_training_algorithm',
            'fann_init_weights',
            'fann_length_train_data',
            'fann_merge_train_data',
            'fann_num_input_train_data',
            'fann_num_output_train_data',
            'fann_randomize_weights',
            'fann_read_train_from_file',
            'fann_reset_errno',
            'fann_reset_errstr',
            'fann_run',
            'fann_save',
            'fann_save_train',
            'fann_scale_input',
            'fann_scale_input_train_data',
            'fann_scale_output',
            'fann_scale_output_train_data',
            'fann_scale_train',
            'fann_scale_train_data',
            'fann_set_activation_function',
            'fann_set_activation_function_hidden',
            'fann_set_activation_function_layer',
            'fann_set_activation_function_output',
            'fann_set_activation_steepness',
            'fann_set_activation_steepness_hidden',
            'fann_set_activation_steepness_layer',
            'fann_set_activation_steepness_output',
            'fann_set_bit_fail_limit',
            'fann_set_callback',
            'fann_set_cascade_activation_functions',
            'fann_set_cascade_activation_steepnesses',
            'fann_set_cascade_candidate_change_fraction',
            'fann_set_cascade_candidate_limit',
            'fann_set_cascade_candidate_stagnation_epochs',
            'fann_set_cascade_max_cand_epochs',
            'fann_set_cascade_max_out_epochs',
            'fann_set_cascade_min_cand_epochs',
            'fann_set_cascade_min_out_epochs',
            'fann_set_cascade_num_candidate_groups',
            'fann_set_cascade_output_change_fraction',
            'fann_set_cascade_output_stagnation_epochs',
            'fann_set_cascade_weight_multiplier',
            'fann_set_error_log',
            'fann_set_input_scaling_params',
            'fann_set_learning_momentum',
            'fann_set_learning_rate',
            'fann_set_output_scaling_params',
            'fann_set_quickprop_decay',
            'fann_set_quickprop_mu',
            'fann_set_rprop_decrease_factor',
            'fann_set_rprop_delta_max',
            'fann_set_rprop_delta_min',
            'fann_set_rprop_delta_zero',
            'fann_set_rprop_increase_factor',
            'fann_set_sarprop_step_error_shift',
            'fann_set_sarprop_step_error_threshold_factor',
            'fann_set_sarprop_temperature',
            'fann_set_sarprop_weight_decay_shift',
            'fann_set_scaling_params',
            'fann_set_train_error_function',
            'fann_set_train_stop_function',
            'fann_set_training_algorithm',
            'fann_set_weight',
            'fann_set_weight_array',
            'fann_shuffle_train_data',
            'fann_subset_train_data',
            'fann_test',
            'fann_test_data',
            'fann_train',
            'fann_train_epoch',
            'fann_train_on_data',
            'fann_train_on_file',
            'fbsql_connect',
            'fbsql_db_query',
            'fbsql_list_dbs',
            'fbsql_list_fields',
            'fbsql_list_tables',
            'fbsql_pconnect',
            'fbsql_query',
            'fdf_create',
            'fdf_open',
            'fdf_open_string',
            'finfo::buffer',
            'finfo_buffer',
            'finfo_close',
            'finfo_file',
            'finfo_open',
            'finfo_set_flags',
            'fopen',
            'fsockopen',
            'ftp_alloc',
            'ftp_cdup',
            'ftp_chdir',
            'ftp_chmod',
            'ftp_close',
            'ftp_connect',
            'ftp_delete',
            'ftp_exec',
            'ftp_fget',
            'ftp_fput',
            'ftp_get',
            'ftp_get_option',
            'ftp_login',
            'ftp_mdtm',
            'ftp_mkdir',
            'ftp_nb_continue',
            'ftp_nb_fget',
            'ftp_nb_fput',
            'ftp_nb_get',
            'ftp_nb_put',
            'ftp_nlist',
            'ftp_pasv',
            'ftp_put',
            'ftp_pwd',
            'ftp_raw',
            'ftp_rawlist',
            'ftp_rename',
            'ftp_rmdir',
            'ftp_set_option',
            'ftp_site',
            'ftp_size',
            'ftp_ssl_connect',
            'ftp_systype',
            'gnupg_init',
            'gupnp_context_new',
            'gupnp_control_point_new',
            'gupnp_device_info_get_service',
            'gupnp_root_device_new',
            'gzopen',
            'hash_copy',
            'hash_final',
            'hash_init',
            'hash_update',
            'hash_update_file',
            'hash_update_stream',
            'http_get_request_body_stream',
            'ibase_blob_create',
            'ibase_blob_open',
            'ibase_blob_open 1',
            'ibase_connect',
            'ibase_pconnect',
            'ibase_prepare',
            'ibase_service_attach',
            'ibase_set_event_handler',
            'ibase_set_event_handler 1',
            'ibase_trans',
            'ifx_connect',
            'ifx_pconnect',
            'ifx_prepare',
            'ifx_query',
            'imageaffine',
            'imageconvolution',
            'imagecreate',
            'imagecreatefromgd',
            'imagecreatefromgd2',
            'imagecreatefromgd2part',
            'imagecreatefromgif',
            'imagecreatefromjpeg',
            'imagecreatefrompng',
            'imagecreatefromstring',
            'imagecreatefromwbmp',
            'imagecreatefromwebp',
            'imagecreatefromxbm',
            'imagecreatefromxpm',
            'imagecreatetruecolor',
            'imagegrabscreen',
            'imagegrabwindow',
            'imagepalettetotruecolor',
            'imagepsloadfont',
            'imagerotate',
            'imagescale',
            'imap_open',
            'inflate_init',
            'ingres_connect',
            'ingres_pconnect',
            'inotify_init',
            'kadm5_init_with_password',
            'ldap_connect',
            'ldap_first_entry',
            'ldap_first_reference',
            'ldap_list',
            'ldap_next_entry',
            'ldap_next_reference',
            'ldap_read',
            'ldap_search',
            'm_initconn',
            'mailparse_msg_create',
            'mailparse_msg_get_part',
            'mailparse_msg_parse_file',
            'maxdb::use_result',
            'maxdb_connect',
            'maxdb_embedded_connect',
            'maxdb_init',
            'maxdb_stmt::result_metadata',
            'maxdb_stmt_result_metadata',
            'maxdb_use_result',
            'mcrypt_module_open',
            'msg_get_queue',
            'msql_connect',
            'msql_db_query',
            'msql_list_dbs',
            'msql_list_fields',
            'msql_list_tables',
            'msql_pconnect',
            'msql_query',
            'mssql_connect',
            'mssql_init',
            'mssql_pconnect',
            'mysql_connect',
            'mysql_db_query',
            'mysql_list_dbs',
            'mysql_list_fields',
            'mysql_list_processes',
            'mysql_list_tables',
            'mysql_pconnect',
            'mysql_query',
            'mysql_unbuffered_query',
            'mysqlnd_uh_convert_to_mysqlnd',
            'ncurses_new_panel',
            'ncurses_newpad',
            'ncurses_newwin',
            'ncurses_panel_above',
            'ncurses_panel_below',
            'ncurses_panel_window',
            'newt_button',
            'newt_button_bar',
            'newt_checkbox',
            'newt_checkbox_tree',
            'newt_checkbox_tree_multi',
            'newt_compact_button',
            'newt_create_grid',
            'newt_entry',
            'newt_form',
            'newt_form_get_current',
            'newt_grid_basic_window',
            'newt_grid_h_close_stacked',
            'newt_grid_h_stacked',
            'newt_grid_simple_window',
            'newt_grid_v_close_stacked',
            'newt_grid_v_stacked',
            'newt_label',
            'newt_listbox',
            'newt_listitem',
            'newt_radio_get_current',
            'newt_radiobutton',
            'newt_run_form',
            'newt_scale',
            'newt_textbox',
            'newt_textbox_reflowed',
            'newt_vertical_scrollbar',
            'oci_connect',
            'oci_get_implicit_resultset',
            'oci_new_connect',
            'oci_new_cursor',
            'oci_parse',
            'oci_pconnect',
            'odbc_columnprivileges',
            'odbc_columns',
            'odbc_connect',
            'odbc_exec',
            'odbc_foreignkeys',
            'odbc_gettypeinfo',
            'odbc_pconnect',
            'odbc_prepare',
            'odbc_primarykeys',
            'odbc_procedurecolumns',
            'odbc_procedures',
            'odbc_specialcolumns',
            'odbc_statistics',
            'odbc_tableprivileges',
            'odbc_tables',
            'openal_buffer_create',
            'openal_context_create',
            'openal_device_open',
            'openal_source_create',
            'openal_stream',
            'openssl_csr_new',
            'openssl_csr_sign',
            'openssl_pkey_get_private',
            'openssl_pkey_get_public',
            'openssl_pkey_new',
            'openssl_x509_read',
            'pfsockopen',
            'pg_cancel_query',
            'pg_client_encoding',
            'pg_close',
            'pg_connect',
            'pg_connect_poll',
            'pg_connection_busy',
            'pg_connection_reset',
            'pg_connection_status',
            'pg_consume_input',
            'pg_copy_from',
            'pg_copy_to',
            'pg_dbname',
            'pg_end_copy',
            'pg_escape_bytea',
            'pg_escape_identifier',
            'pg_escape_identifier 1',
            'pg_escape_literal',
            'pg_escape_string',
            'pg_execute',
            'pg_execute 1',
            'pg_flush',
            'pg_free_result',
            'pg_get_notify',
            'pg_get_pid',
            'pg_get_result',
            'pg_host',
            'pg_last_error',
            'pg_last_notice',
            'pg_lo_create',
            'pg_lo_export',
            'pg_lo_import',
            'pg_lo_open',
            'pg_lo_unlink',
            'pg_options',
            'pg_parameter_status',
            'pg_pconnect',
            'pg_ping',
            'pg_port',
            'pg_prepare',
            'pg_prepare 1',
            'pg_put_line',
            'pg_query',
            'pg_query 1',
            'pg_query_params',
            'pg_query_params 1',
            'pg_send_execute',
            'pg_send_prepare',
            'pg_send_query',
            'pg_send_query_params',
            'pg_set_client_encoding',
            'pg_set_client_encoding 1',
            'pg_set_error_verbosity',
            'pg_socket',
            'pg_trace',
            'pg_transaction_status',
            'pg_tty',
            'pg_untrace',
            'pg_version',
            'php_user_filter::filter',
            'popen',
            'proc_open',
            'ps_new',
            'px_new',
            'radius_acct_open',
            'radius_auth_open',
            'radius_salt_encrypt_attr',
            'rpm_open',
            'sem_get',
            'shm_attach',
            'socket_accept',
            'socket_create',
            'socket_create_listen',
            'socket_recvmsg',
            'socket_sendmsg',
            'sqlite_open',
            'sqlite_popen',
            'sqlsrv_begin_transaction',
            'sqlsrv_cancel',
            'sqlsrv_client_info',
            'sqlsrv_close',
            'sqlsrv_commit',
            'sqlsrv_connect',
            'sqlsrv_execute',
            'sqlsrv_fetch',
            'sqlsrv_fetch_array',
            'sqlsrv_fetch_object',
            'sqlsrv_field_metadata',
            'sqlsrv_free_stmt',
            'sqlsrv_get_field',
            'sqlsrv_has_rows',
            'sqlsrv_next_result',
            'sqlsrv_num_fields',
            'sqlsrv_num_rows',
            'sqlsrv_prepare',
            'sqlsrv_query',
            'sqlsrv_rollback',
            'sqlsrv_rows_affected',
            'sqlsrv_send_stream_data',
            'sqlsrv_server_info',
            'ssh2_auth_agent',
            'ssh2_connect',
            'ssh2_exec',
            'ssh2_fetch_stream',
            'ssh2_publickey_init',
            'ssh2_sftp',
            'ssh2_sftp_chmod',
            'ssh2_shell',
            'ssh2_tunnel',
            'stomp_connect',
            'streamWrapper::stream_cast',
            'stream_bucket_new',
            'stream_context_create',
            'stream_context_get_default',
            'stream_context_set_default',
            'stream_filter_append',
            'stream_filter_prepend',
            'stream_socket_accept',
            'stream_socket_client',
            'stream_socket_server',
            'svn_fs_apply_text',
            'svn_fs_begin_txn2',
            'svn_fs_file_contents',
            'svn_fs_revision_root',
            'svn_fs_txn_root',
            'svn_repos_create',
            'svn_repos_fs',
            'svn_repos_fs_begin_txn_for_commit',
            'svn_repos_open',
            'sybase_connect',
            'sybase_pconnect',
            'sybase_unbuffered_query',
            'tmpfile',
            'udm_alloc_agent',
            'udm_alloc_agent_array',
            'udm_find',
            'unlink',
            'w32api_init_dtype',
            'wddx_packet_start',
            'xml_parser_create',
            'xml_parser_create_ns',
            'xml_parser_free',
            'xml_parser_get_option',
            'xml_parser_set_option',
            'xmlrpc_server_create',
            'xmlwriter_open_memory',
            'xmlwriter_open_uri',
            'xslt_create',
            'zip_open',
            'zip_read',
        ];
    }
}
<?php

error_reporting(E_ALL | E_STRICT);

// Set the default timezone. While this doesn't cause any tests to fail, PHP
// complains if it is not set in 'date.timezone' of php.ini.
date_default_timezone_set('UTC');

// Ensure that composer has installed all dependencies
if (!file_exists(dirname(__DIR__) . '/composer.lock')) {
    die("Dependencies must be installed using composer:\n\nphp composer.phar install --dev\n\n"
        . "See http://getcomposer.org for help with installing composer\n");
}

// Include the composer autoloader
$autoloader = require_once dirname(__DIR__) . '/vendor/autoload.php';

$client = \Elasticsearch\ClientBuilder::fromConfig([
    'hosts' => [$_SERVER['ES_TEST_HOST']]
]);

$count = 0;
while (!$client->ping()) {
    $count += 1;

    if ($count > 15) {
        echo "Live cluster could not be found in 15s!\nContents of elasticsearch.log:\n\n";

        $dir = new DirectoryIterator(dirname(__DIR__));

        foreach ($dir as $fileinfo) {
            if ($fileinfo->isDir() && !$fileinfo->isDot()) {
                if (strpos($fileinfo->getFilename(), "elasticsearch") === 0) {
                    $log = file_get_contents(dirname(__DIR__)."/$fileinfo/logs/elasticsearch.log");
                    echo $log;
                    break;
                }

            }
        }

        throw new \Exception();
    }
    sleep(1);
}

<?php

error_reporting(E_ALL | E_STRICT);

// Set the default timezone. While this doesn't cause any tests to fail, PHP
// complains if it is not set in 'date.timezone' of php.ini.
date_default_timezone_set('UTC');

// Ensure that composer has installed all dependencies
if (!file_exists(dirname(__DIR__) . '/composer.lock')) {
    die("Dependencies must be installed using composer:\n\nphp composer.phar install --dev\n\n"
        . "See http://getcomposer.org for help with installing composer\n");
}

echo "Base directory: ". dirname(__DIR__)."\n";

// Include the composer autoloader
$autoloader = require_once(dirname(__DIR__) . '/vendor/autoload.php');

$gitWrapper = new \GitWrapper\GitWrapper();
echo "Git cwd: ".dirname(__DIR__) . "/util/elasticsearch\n";
$git = $gitWrapper->workingCopy(dirname(__DIR__) . '/util/elasticsearch');

echo "Update elasticsearch submodule\n";
$git->fetchAll(array('verbose' => true));

$hash = $_SERVER['TEST_BUILD_REF'];
echo "Checkout yaml tests (hash: $hash)\n";
$git->checkout($hash, array('force' => true, 'quiet' => true));
<?php

// Ensure that composer has installed all dependencies
if (!file_exists(dirname(__DIR__) . '/composer.lock')) {
    die("Dependencies must be installed using composer:\n\nphp composer.phar install --dev\n\n"
        . "See http://getcomposer.org for help with installing composer\n");
}

// Include the composer autoloader
$autoloader = require_once dirname(__DIR__) . '/vendor/autoload.php';


use Sami\Sami;
use Symfony\Component\Finder\Finder;

$iterator = Finder::create()
    ->files()
    ->name('*Namespace.php')
    ->name("Client.php")
    ->name("ClientBuilder.php")
    ->notName("AbstractNamespace.php")
    ->in(__DIR__.'/../src/');

return new Sami($iterator, array(
    'theme'                => 'asciidoc',
    'template_dirs'        => array(__DIR__.'/docstheme/'),
    'title'                => 'Elasticsearch-php',
    'build_dir'            => __DIR__.'/../docs/build',
    'cache_dir'            => __DIR__.'/cache/',
));#!/usr/bin/env php
<?php

require_once '../vendor/autoload.php';

$loader = new Twig_Loader_Filesystem('templates');
$twig = new Twig_Environment($loader);
$template = $twig->loadTemplate('endpoint.twig');

$counter = 0;


function getApiPath()
{
    $path = dirname(__FILE__).'/elasticsearch/rest-api-spec/api';
    if (file_exists($path) !== true) {
        $path = dirname(__FILE__).'/elasticsearch/rest-api-spec/src/main/resources/rest-api-spec/api';
    }
    return $path;
}


if ($handle = opendir(getApiPath())) {
    while (false !== ($entry = readdir($handle))) {
        if ($entry != "." && $entry != "..") {
            generateTemplate($entry, $template);
        }
    }
    closedir($handle);
}

function processURLPaths($data)
{
    $final = array();

    $containsType = false;
    $containsIndex = false;
    foreach ($data['url']['paths'] as $path) {
        $params = array();
        preg_match_all('/{(.*?)}/', $path, $params);
        $params = $params[1];
        $count = count($params);
        $parsedPath = str_replace('}', '', $path);
        $parsedPath = str_replace('{', '$', $parsedPath);

        if (array_search('index', $params) !== false) {
            $containsIndex = true;
        }

        if (array_search('type', $params) !== false) {
            $containsType = true;
        }

        $duplicate = false;
        foreach ($final as $existing) {
            if ($existing['params'] === $params) {
                $duplicate = true;
            }
        }

        if ($duplicate !== true) {
            $final[] = array(
                'path'   => $path,
                'parsedPath' => $parsedPath,
                'params' => $params,
                'count'  => $count
            );
        }
    }

    /*
    foreach ($final as &$existing) {
        if ($containsIndex === true && array_search('index', $existing['params']) === false && array_search('type', $existing['params']) !== false) {
            $existing['parsedPath'] = '/_all'.$existing['parsedPath'];
        }
    }
    */

    usort($final, function ($a, $b) {
            if ($a['count'] == $b['count']) {
                return 0;
            }

            return ($a['count'] > $b['count']) ? -1 : 1;
        });

    return $final;
}

function getDefaultPath($path)
{
    if ($path['count'] === 0) {
        return $path['path'];
    } else {
        $final = str_replace('}', '', $path['path']);
        $final = str_replace('{', '$', $final);

        return $final;
    }
}

function forbid($key, $value)
{
    $forbid = array(
        'GET' => array(
            '/_nodes/hotthreads',
            '/_nodes/{node_id}/hotthreads',
            '/_nodes/{metric}'
        ),
        'HEAD' => array(),
        'PUT' => array(
            '/{index}/{type}/_mapping'
        ),
        'POST' => array(
            '/_all/{type}/_bulk',
            '/_all/{type}/_mget'
        ),
        'DELETE' => array(
            '/{index}/{type}',
            '/{index}/{type}/_mapping'
        ),
        'QS' => array(
            'operation_threading',
            'field_data'
        )
    );

    if (isset($forbid['key']) && $forbid['key'] === $value) {
        return true;
    } else {
        return false;
    }
}

function generateTemplate($path, $template)
{
    $ignoreList = array(
        'index.json', 'bulk.json'
    );

    if (array_search($path, $ignoreList) !== false) {
        return;
    }

    $path = getApiPath().DIRECTORY_SEPARATOR.$path;
    $json = file_get_contents($path);
    $data = json_decode($json, true);

    reset($data);
    $namespace = key($data);
    $data = $data[$namespace];
    $namespace = explode('.', $namespace);

    $underscoreNamespace = array(
        'get',
        'put',
        'post',
        'delete',
        'exists',
        'update',
        'create'
    );

    $exceptions = array(
        'delete_by_query',
        'update_by_query',
    );

    if (strpos($namespace[count($namespace)-1], '_')) {
        $temp = explode('_', $namespace[count($namespace)-1]);

        if (array_search($temp[0], $underscoreNamespace) !== false && array_search($namespace[count($namespace)-1], $exceptions) === false) {
            $namespace[count($namespace)-1] = $temp[1];
            $namespace[] = $temp[0];
        } else {
            $namespace[count($namespace)-1] = str_replace('_', '', $namespace[count($namespace)-1]);
        }
    }

    $data['url']['processed'] = processURLPaths($data);
    $data['url']['default'] = getDefaultPath($data['url']['processed'][count($data['url']['processed'])-1]);
    if (count($data['methods']) > 1) {
        if (in_array("GET", $data['methods'])) {
            $data['methods'] = "GET";
        } else {
            $data['methods'] = $data['methods'][0];
        }
    }
    $renderVars = array(
        'json'      => $json,
        'data'      => $data,
        'namespace' => $namespace,
        'className' => ucfirst($namespace[count($namespace)-1]),
    );

    $ret = $template->render($renderVars);

    $dir = __DIR__.'/output/'.implode('/', array_map('ucfirst', array_splice($namespace, 0, count($namespace)-1)));

    if (substr($dir, -1) !== '/') {
        $dir .= '/';
    }
    if (!file_exists($dir)) {
        echo 'making dir: '.$dir."\n\n";
        $oldumask = umask(0);
        mkdir($dir, 0777, true);
        umask($oldumask);
    }

    echo $dir."\n\n";
    $path = $dir.$renderVars['className'].'.php';
    echo $path."\n\n";

    file_put_contents($path, $ret);
}
{
  "name": "elasticsearch/elasticsearch",
  "description": "PHP Client for Elasticsearch",
  "keywords": ["search","client", "elasticsearch"],
  "type": "library",
  "license": "Apache-2.0",
  "authors": [
    {
      "name": "Zachary Tong"
    }
  ],
  "require": {
    "php": "^5.6|^7.0",
    "psr/log": "~1.0",
    "guzzlehttp/ringphp" : "~1.0"
  },
  "require-dev": {
    "phpunit/phpunit": "^4.7|^5.4",
    "mockery/mockery": "0.9.4",
    "symfony/yaml": "^2.8",
    "symfony/finder": "^2.8",
    "cpliakas/git-wrapper": "~1.0",
    "sami/sami": "~3.2",
    "doctrine/inflector": "^1.1"
  },
  "suggest": {
    "ext-curl": "*",
    "monolog/monolog": "Allows for client-level logging and tracing"
  },
  "autoload": {
    "psr-4": {
      "Elasticsearch\\": "src/Elasticsearch/"
    }
  },
  "autoload-dev": {
    "psr-4": {
      "Elasticsearch\\Tests\\":      "tests/Elasticsearch/Tests/"
    }
  }
}
<?php

namespace Elasticsearch\Endpoints;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Render
 *
 * @category Elasticsearch
 * @package Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */

class RenderSearchTemplate extends AbstractEndpoint
{
    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;
        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        $id = $this->id;

        $uri   = "/_render/template";

        if (isset($id) === true) {
            $uri = "/_render/template/$id";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array();
    }

    /**
     * @return array
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     */
    public function getBody()
    {
        return $this->body;
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cat;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Recovery
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cat
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Recovery extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $uri   = "/_cat/recovery";

        if (isset($index) === true) {
            $uri = "/_cat/recovery/$index";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'bytes',
            'local',
            'master_timeout',
            'h',
            'help',
            'v',
            's',
            'format',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cat;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Pendingtasks
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cat
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class PendingTasks extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $uri   = "/_cat/pending_tasks";

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'local',
            'master_timeout',
            'h',
            'help',
            'v',
            's',
            'format',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cat;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class ThreadPool
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cat
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */

class ThreadPool extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $uri   = "/_cat/thread_pool";

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'local',
            'master_timeout',
            'h',
            'help',
            'v',
            'full_id',
            'size',
            'thread_pool_patterns',
            's',
            'format',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cat;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Templates
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cat
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Templates extends AbstractEndpoint
{
    private $name;

    /**
     * @param string $name
     * @return Templates
     */
    public function setName($name)
    {
        $this->name = $name;
        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        if (isset($this->name)) {
            return "/_cat/templates/{$this->name}";
        } else {
            return "/_cat/templates";
        }
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'format',
            'node_id',
            'actions',
            'detailed',
            'parent_node',
            'parent_task',
            'h',
            'help',
            'v',
            's',
            'local',
            'master_timeout',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cat;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Shards
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cat
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Shards extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $uri   = "/_cat/shards";

        if (isset($index) === true) {
            $uri = "/_cat/shards/$index";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'bytes',
            'local',
            'master_timeout',
            'h',
            'help',
            'v',
            's',
            'format',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cat;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Health
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cat
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Health extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $uri   = "/_cat/health";

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'local',
            'master_timeout',
            'h',
            'help',
            'ts',
            'v',
            's',
            'format',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cat;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Master
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cat
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Master extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $uri   = "/_cat/master";

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'local',
            'master_timeout',
            'h',
            'help',
            'v',
            's',
            'format',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cat;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Tasks
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cat
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Tasks extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        return "/_cat/tasks";
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'format',
            'node_id',
            'actions',
            'detailed',
            'parent_node',
            'parent_task',
            'h',
            'help',
            'v',
            's'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cat;

use Elasticsearch\Common\Exceptions\RuntimeException;
use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Snapshots
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cat
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Snapshots extends AbstractEndpoint
{
    private $repository;

    /**
     * @param $fields
     *
     * @return $this
     */
    public function setRepository($repository)
    {
        if (isset($repository) !== true) {
            return $this;
        }

        $this->repository = $repository;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $repository = $this->repository;
        if (isset($this->repository) === true) {
            return "/_cat/snapshots/$repository/";
        }

        return "/_cat/snapshots/";
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'local',
            'master_timeout',
            'h',
            'help',
            'v',
            's',
            'format',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cat;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class NodeAttrs
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cat
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class NodeAttrs extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $uri   = "/_cat/nodeattrs";

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'local',
            'master_timeout',
            'h',
            'help',
            'v',
            's',
            'format',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cat;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Health
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cat
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Plugins extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $uri   = "/_cat/plugins";

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'local',
            'master_timeout',
            'h',
            'help',
            'v',
            's',
            'format',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cat;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Allocation
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cat
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Allocation extends AbstractEndpoint
{
    // A comma-separated list of node IDs or names to limit the returned information
    private $node_id;

    /**
     * @param $node_id
     *
     * @return $this
     */
    public function setNodeId($node_id)
    {
        if (isset($node_id) !== true) {
            return $this;
        }

        $this->node_id = $node_id;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $node_id = $this->node_id;
        $uri   = "/_cat/allocation";

        if (isset($node_id) === true) {
            $uri = "/_cat/allocation/$node_id";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'bytes',
            'local',
            'master_timeout',
            'h',
            'help',
            'v',
            's',
            'format',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cat;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Help
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cat
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Help extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $uri   = "/_cat";

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'help',
            's',
            'format',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cat;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Count
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cat
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Count extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $uri   = "/_cat/count";

        if (isset($index) === true) {
            $uri = "/_cat/count/$index";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'local',
            'master_timeout',
            'h',
            'help',
            'v',
            's',
            'format',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cat;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Aliases
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cat
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Aliases extends AbstractEndpoint
{
    // A comma-separated list of alias names to return
    private $name;

    /**
     * @param $name
     *
     * @return $this
     */
    public function setName($name)
    {
        if (isset($name) !== true) {
            return $this;
        }

        $this->name = $name;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $name = $this->name;
        $uri   = "/_cat/aliases";

        if (isset($name) === true) {
            $uri = "/_cat/aliases/$name";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'local',
            'master_timeout',
            'h',
            'help',
            'v',
            'format',
            's',
            'format',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php
/**
 * User: zach
 * Date: 01/12/2015
 * Time: 14:34:49 pm
 */

namespace Elasticsearch\Endpoints\Cat;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Segments
 *
 * @category Elasticsearch
 * @package Elasticsearch\Endpoints\Cat
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */

class Segments extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $uri   = "/_cat/segments";

        if (isset($index) === true) {
            $uri = "/_cat/segments/$index";
        }

        return $uri;
    }


    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'h',
            'help',
            'v',
            's',
            'format',
        );
    }


    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cat;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Indices
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cat
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */

class Indices extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $uri   = "/_cat/indices";

        if (isset($index) === true) {
            $uri = "/_cat/indices/$index";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'bytes',
            'local',
            'master_timeout',
            'h',
            'help',
            'pri',
            'v',
            'health',
            's',
            'format',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cat;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Fielddata
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cat
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Fielddata extends AbstractEndpoint
{
    private $fields;

    /**
     * @param $fields
     *
     * @return $this
     */
    public function setFields($fields)
    {
        if (isset($fields) !== true) {
            return $this;
        }

        $this->fields = $fields;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $fields = $this->fields;
        $uri   = "/_cat/fielddata";

        if (isset($fields) === true) {
            $uri = "/_cat/fielddata/$fields";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'local',
            'master_timeout',
            'h',
            'help',
            'v',
            's',
            'format',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cat;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Nodes
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cat
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Nodes extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $uri   = "/_cat/nodes";

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'local',
            'master_timeout',
            'h',
            'help',
            'v',
            's',
            'full_id',
            'format',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cat;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Repositories
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cat
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Repositories extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $uri   = "/_cat/repositories";

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'local',
            'master_timeout',
            'h',
            'help',
            'v',
            's',
            'format',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints;

/**
 * Class Info
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Info extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $uri   = "/";

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints;

use Elasticsearch\Common\Exceptions;

/**
 * Class Explain
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Explain extends AbstractEndpoint
{
    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->id) !== true) {
            throw new Exceptions\RuntimeException(
                'id is required for Explain'
            );
        }
        if (isset($this->index) !== true) {
            throw new Exceptions\RuntimeException(
                'index is required for Explain'
            );
        }
        if (isset($this->type) !== true) {
            throw new Exceptions\RuntimeException(
                'type is required for Explain'
            );
        }
        $id = $this->id;
        $index = $this->index;
        $type = $this->type;
        $uri   = "/$index/$type/$id/_explain";

        if (isset($index) === true && isset($type) === true && isset($id) === true) {
            $uri = "/$index/$type/$id/_explain";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'analyze_wildcard',
            'analyzer',
            'default_operator',
            'df',
            'fields',
            'lenient',
            'lowercase_expanded_terms',
            'parent',
            'preference',
            'q',
            'routing',
            'source',
            '_source',
            '_source_exclude',
            '_source_include',
            'stored_fields'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints;

/**
 * Class Search
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class SearchShards extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $type = $this->type;
        $uri   = "/_search_shards";

        if (isset($index) === true && isset($type) === true) {
            $uri = "/$index/$type/_search_shards";
        } elseif (isset($index) === true) {
            $uri = "/$index/_search_shards";
        } elseif (isset($type) === true) {
            $uri = "/_all/$type/_search_shards";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'preference',
            'routing',
            'local',
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Snapshot\Repository;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Get
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Snapshot\Repository
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Get extends AbstractEndpoint
{
    // A comma-separated list of repository names
    private $repository;

    /**
     * @param $repository
     *
     * @return $this
     */
    public function setRepository($repository)
    {
        if (isset($repository) !== true) {
            return $this;
        }

        $this->repository = $repository;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $repository = $this->repository;
        $uri   = "/_snapshot";

        if (isset($repository) === true) {
            $uri = "/_snapshot/$repository";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'master_timeout',
            'local',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Snapshot\Repository;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Create
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Snapshot\Repository
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Create extends AbstractEndpoint
{
    // A repository name
    private $repository;

    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @param $repository
     *
     * @return $this
     */
    public function setRepository($repository)
    {
        if (isset($repository) !== true) {
            return $this;
        }

        $this->repository = $repository;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->repository) !== true) {
            throw new Exceptions\RuntimeException(
                'repository is required for Create'
            );
        }
        $repository = $this->repository;
        $uri   = "/_snapshot/$repository";

        if (isset($repository) === true) {
            $uri = "/_snapshot/$repository";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'master_timeout',
            'timeout',
        );
    }

    /**
     * @return array
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     */
    public function getBody()
    {
        if (isset($this->body) !== true) {
            throw new Exceptions\RuntimeException('Body is required for Create Repository');
        }

        return $this->body;
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'PUT';
    }
}
<?php

namespace Elasticsearch\Endpoints\Snapshot\Repository;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Delete
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Snapshot\Repository
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Delete extends AbstractEndpoint
{
    // A comma-separated list of repository names
    private $repository;

    /**
     * @param $repository
     *
     * @return $this
     */
    public function setRepository($repository)
    {
        if (isset($repository) !== true) {
            return $this;
        }

        $this->repository = $repository;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->repository) !== true) {
            throw new Exceptions\RuntimeException(
                'repository is required for Delete'
            );
        }
        $repository = $this->repository;
        $uri   = "/_snapshot/$repository";

        if (isset($repository) === true) {
            $uri = "/_snapshot/$repository";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'master_timeout',
            'timeout',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'DELETE';
    }
}
<?php

namespace Elasticsearch\Endpoints\Snapshot\Repository;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Verify
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Snapshot\Repository
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Verify extends AbstractEndpoint
{
    // A comma-separated list of repository names
    private $repository;

    /**
     * @param $repository
     *
     * @return $this
     */
    public function setRepository($repository)
    {
        if (isset($repository) !== true) {
            return $this;
        }

        $this->repository = $repository;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        $repository = $this->repository;
        if (isset($this->repository) !== true) {
            throw new Exceptions\RuntimeException(
                'repository is required for Verify'
            );
        }

        $uri   = "/_snapshot/$repository/_verify";

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'master_timeout',
            'local',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'POST';
    }
}
<?php

namespace Elasticsearch\Endpoints\Snapshot;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Get
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Snapshot
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Get extends AbstractEndpoint
{
    // A comma-separated list of repository names
    private $repository;

    // A comma-separated list of snapshot names
    private $snapshot;

    /**
     * @param $repository
     *
     * @return $this
     */
    public function setRepository($repository)
    {
        if (isset($repository) !== true) {
            return $this;
        }

        $this->repository = $repository;

        return $this;
    }

    /**
     * @param $snapshot
     *
     * @return $this
     */
    public function setSnapshot($snapshot)
    {
        if (isset($snapshot) !== true) {
            return $this;
        }

        $this->snapshot = $snapshot;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->repository) !== true) {
            throw new Exceptions\RuntimeException(
                'repository is required for Get'
            );
        }
        if (isset($this->snapshot) !== true) {
            throw new Exceptions\RuntimeException(
                'snapshot is required for Get'
            );
        }
        $repository = $this->repository;
        $snapshot = $this->snapshot;
        $uri   = "/_snapshot/$repository/$snapshot";

        if (isset($repository) === true && isset($snapshot) === true) {
            $uri = "/_snapshot/$repository/$snapshot";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'master_timeout',
            'ignore_unavailable'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Snapshot;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Create
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Snapshot
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Create extends AbstractEndpoint
{
    // A repository name
    private $repository;

    // A snapshot name
    private $snapshot;

    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @param $repository
     *
     * @return $this
     */
    public function setRepository($repository)
    {
        if (isset($repository) !== true) {
            return $this;
        }

        $this->repository = $repository;

        return $this;
    }

    /**
     * @param $snapshot
     *
     * @return $this
     */
    public function setSnapshot($snapshot)
    {
        if (isset($snapshot) !== true) {
            return $this;
        }

        $this->snapshot = $snapshot;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->repository) !== true) {
            throw new Exceptions\RuntimeException(
                'repository is required for Create'
            );
        }
        if (isset($this->snapshot) !== true) {
            throw new Exceptions\RuntimeException(
                'snapshot is required for Create'
            );
        }
        $repository = $this->repository;
        $snapshot = $this->snapshot;
        $uri   = "/_snapshot/$repository/$snapshot";

        if (isset($repository) === true && isset($snapshot) === true) {
            $uri = "/_snapshot/$repository/$snapshot";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'master_timeout',
            'wait_for_completion',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'PUT';
    }
}
<?php

namespace Elasticsearch\Endpoints\Snapshot;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Status
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Snapshot
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Status extends AbstractEndpoint
{
    // A comma-separated list of repository names
    private $repository;

    // A comma-separated list of snapshot names
    private $snapshot;

    /**
     * @param $repository
     *
     * @return $this
     */
    public function setRepository($repository)
    {
        if (isset($repository) !== true) {
            return $this;
        }

        $this->repository = $repository;

        return $this;
    }

    /**
     * @param $snapshot
     *
     * @return $this
     */
    public function setSnapshot($snapshot)
    {
        if (isset($snapshot) !== true) {
            return $this;
        }

        $this->snapshot = $snapshot;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->snapshot) === true && isset($this->repository) !== true) {
            throw new Exceptions\RuntimeException(
                'Repository param must be provided if snapshot param is set'
            );
        }

        $repository = $this->repository;
        $snapshot   = $this->snapshot;
        $uri        = "/_snapshot/_status";

        if (isset($repository) === true && isset($snapshot) === true) {
            $uri = "/_snapshot/$repository/$snapshot/_status";
        } elseif (isset($repository) === true) {
            $uri = "/_snapshot/$repository/_status";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'master_timeout',
            'ignore_unavailable'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Snapshot;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Restore
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Snapshot
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Restore extends AbstractEndpoint
{
    // A repository name
    private $repository;

    // A snapshot name
    private $snapshot;

    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @param $repository
     *
     * @return $this
     */
    public function setRepository($repository)
    {
        if (isset($repository) !== true) {
            return $this;
        }

        $this->repository = $repository;

        return $this;
    }

    /**
     * @param $snapshot
     *
     * @return $this
     */
    public function setSnapshot($snapshot)
    {
        if (isset($snapshot) !== true) {
            return $this;
        }

        $this->snapshot = $snapshot;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->repository) !== true) {
            throw new Exceptions\RuntimeException(
                'repository is required for Restore'
            );
        }
        if (isset($this->snapshot) !== true) {
            throw new Exceptions\RuntimeException(
                'snapshot is required for Restore'
            );
        }
        $repository = $this->repository;
        $snapshot = $this->snapshot;
        $uri   = "/_snapshot/$repository/$snapshot/_restore";

        if (isset($repository) === true && isset($snapshot) === true) {
            $uri = "/_snapshot/$repository/$snapshot/_restore";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'master_timeout',
            'wait_for_completion',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'POST';
    }
}
<?php

namespace Elasticsearch\Endpoints\Snapshot;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Delete
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Snapshot
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Delete extends AbstractEndpoint
{
    // A repository name
    private $repository;

    // A snapshot name
    private $snapshot;

    /**
     * @param $repository
     *
     * @return $this
     */
    public function setRepository($repository)
    {
        if (isset($repository) !== true) {
            return $this;
        }

        $this->repository = $repository;

        return $this;
    }

    /**
     * @param $snapshot
     *
     * @return $this
     */
    public function setSnapshot($snapshot)
    {
        if (isset($snapshot) !== true) {
            return $this;
        }

        $this->snapshot = $snapshot;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->repository) !== true) {
            throw new Exceptions\RuntimeException(
                'repository is required for Delete'
            );
        }
        if (isset($this->snapshot) !== true) {
            throw new Exceptions\RuntimeException(
                'snapshot is required for Delete'
            );
        }
        $repository = $this->repository;
        $snapshot = $this->snapshot;
        $uri   = "/_snapshot/$repository/$snapshot";

        if (isset($repository) === true && isset($snapshot) === true) {
            $uri = "/_snapshot/$repository/$snapshot";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'master_timeout',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'DELETE';
    }
}
<?php

namespace Elasticsearch\Endpoints;

use Elasticsearch\Common\Exceptions;

/**
 * Class Update
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Update extends AbstractEndpoint
{
    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->id) !== true) {
            throw new Exceptions\RuntimeException(
                'id is required for Update'
            );
        }
        if (isset($this->index) !== true) {
            throw new Exceptions\RuntimeException(
                'index is required for Update'
            );
        }
        if (isset($this->type) !== true) {
            throw new Exceptions\RuntimeException(
                'type is required for Update'
            );
        }
        $id = $this->id;
        $index = $this->index;
        $type = $this->type;
        $uri   = "/$index/$type/$id/_update";

        if (isset($index) === true && isset($type) === true && isset($id) === true) {
            $uri = "/$index/$type/$id/_update";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'consistency',
            'fields',
            'lang',
            'parent',
            'refresh',
            'replication',
            'retry_on_conflict',
            'routing',
            'script',
            'timeout',
            'timestamp',
            'ttl',
            'version',
            'version_type',
            '_source'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'POST';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cluster\Settings;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Get
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cluster\Settings
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */

class Get extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $uri   = "/_cluster/settings";

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'flat_settings',
            'master_timeout',
            'timeout',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cluster\Settings;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Put
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cluster\Settings
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Put extends AbstractEndpoint
{
    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $uri   = "/_cluster/settings";

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'flat_settings',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'PUT';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cluster;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Pendingtasks
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cluster
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class PendingTasks extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $uri   = "/_cluster/pending_tasks";

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'local',
            'master_timeout',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cluster;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Reroute
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cluster
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Reroute extends AbstractEndpoint
{
    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $uri   = "/_cluster/reroute";

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'dry_run',
            'filter_metadata',
            'master_timeout',
            'timeout',
            'explain',
            'metric'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'POST';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cluster;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Health
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cluster
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Health extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $uri   = "/_cluster/health";

        if (isset($index) === true) {
            $uri = "/_cluster/health/$index";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'level',
            'local',
            'master_timeout',
            'timeout',
            'wait_for_active_shards',
            'wait_for_nodes',
            'wait_for_relocating_shards',
            'wait_for_status',
            'wait_for_events',
            'wait_for_no_relocating_shards'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cluster\Nodes;

/**
 * Class Info
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cluster\Nodes
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Info extends AbstractNodesEndpoint
{
    // A comma-separated list of metrics you wish returned. Leave empty to return all.
    private $metric;

    /**
     * @param $metric
     *
     * @return $this
     */
    public function setMetric($metric)
    {
        if (isset($metric) !== true) {
            return $this;
        }

        if (is_array($metric) === true) {
            $metric = implode(",", $metric);
        }

        $this->metric = $metric;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $node_id = $this->nodeID;
        $metric = $this->metric;
        $uri   = "/_nodes";

        if (isset($node_id) === true && isset($metric) === true) {
            $uri = "/_nodes/$node_id/$metric";
        } elseif (isset($metric) === true) {
            $uri = "/_nodes/$metric";
        } elseif (isset($node_id) === true) {
            $uri = "/_nodes/$node_id";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'flat_settings',
            'human',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cluster\Nodes;

/**
 * Class Hotthreads
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cluster\Nodes
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class HotThreads extends AbstractNodesEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $node_id = $this->nodeID;
        $uri   = "/_cluster/nodes/hotthreads";

        if (isset($node_id) === true) {
            $uri = "/_cluster/nodes/$node_id/hotthreads";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'interval',
            'snapshots',
            'threads',
            'type',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cluster\Nodes;

/**
 * Class Stats
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cluster\Nodes
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Stats extends AbstractNodesEndpoint
{
    // Limit the information returned to the specified metrics
    private $metric;

    // Limit the information returned for `indices` metric to the specific index metrics. Isn&#039;t used if `indices` (or `all`) metric isn&#039;t specified.
    private $indexMetric;

    /**
     * @param $metric
     *
     * @return $this
     */
    public function setMetric($metric)
    {
        if (isset($metric) !== true) {
            return $this;
        }

        if (is_array($metric) === true) {
            $metric = implode(",", $metric);
        }

        $this->metric = $metric;

        return $this;
    }

    /**
     * @param $indexMetric
     *
     * @return $this
     */
    public function setIndexMetric($indexMetric)
    {
        if (isset($indexMetric) !== true) {
            return $this;
        }

        if (is_array($indexMetric) === true) {
            $indexMetric = implode(",", $indexMetric);
        }

        $this->indexMetric = $indexMetric;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $metric = $this->metric;
        $index_metric = $this->indexMetric;
        $node_id = $this->nodeID;
        $uri   = "/_nodes/stats";

        if (isset($node_id) === true && isset($metric) === true && isset($index_metric) === true) {
            $uri = "/_nodes/$node_id/stats/$metric/$index_metric";
        } elseif (isset($metric) === true && isset($index_metric) === true) {
            $uri = "/_nodes/stats/$metric/$index_metric";
        } elseif (isset($node_id) === true && isset($metric) === true) {
            $uri = "/_nodes/$node_id/stats/$metric";
        } elseif (isset($metric) === true) {
            $uri = "/_nodes/stats/$metric";
        } elseif (isset($node_id) === true) {
            $uri = "/_nodes/$node_id/stats";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'completion_fields',
            'fielddata_fields',
            'fields',
            'groups',
            'human',
            'level',
            'types',
            'include_segment_file_sizes',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cluster\Nodes;

/**
 * Class Shutdown
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cluster\Nodes
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Shutdown extends AbstractNodesEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $node_id = $this->nodeID;
        $uri   = "/_shutdown";

        if (isset($node_id) === true) {
            $uri = "/_cluster/nodes/$node_id/_shutdown";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'delay',
            'exit',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'POST';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cluster\Nodes;

use Elasticsearch\Common\Exceptions\InvalidArgumentException;
use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class AbstractNodesEndpoint
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cluster\Nodes
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
abstract class AbstractNodesEndpoint extends AbstractEndpoint
{
    /** @var  string  A comma-separated list of node IDs or names to limit the returned information; use `_local` to return information from the node you&#039;re connecting to, leave empty to get information from all nodes */
    protected $nodeID;

    /**
     * @param $nodeID
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     *
     * @return $this
     */
    public function setNodeID($nodeID)
    {
        if (isset($nodeID) !== true) {
            return $this;
        }

        if (!(is_array($nodeID) === true || is_string($nodeID) === true)) {
            throw new InvalidArgumentException("invalid node_id");
        }

        if (is_array($nodeID) === true) {
            $nodeID = implode(',', $nodeID);
        }

        $this->nodeID = $nodeID;

        return $this;
    }
}
<?php

namespace Elasticsearch\Endpoints\Cluster;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class AllocationExplain
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cluster
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class AllocationExplain extends AbstractEndpoint
{

    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        return "/_cluster/allocation/explain";
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'include_yes_decisions',
            'include_disk_info',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cluster;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class State
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cluster
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class State extends AbstractEndpoint
{
    // Limit the information returned to the specified metrics
    private $metric;

    /**
     * @param $metric
     *
     * @return $this
     */
    public function setMetric($metric)
    {
        if (isset($metric) !== true) {
            return $this;
        }

        if (is_array($metric) === true) {
            $metric = implode(",", $metric);
        }

        $this->metric = $metric;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $metric = $this->metric;
        $uri   = "/_cluster/state";

        if (isset($metric) === true && isset($index) === true) {
            $uri = "/_cluster/state/$metric/$index";
        } elseif (isset($metric) === true) {
            $uri = "/_cluster/state/$metric";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'local',
            'master_timeout',
            'flat_settings',
            'index_templates',
            'expand_wildcards',
            'ignore_unavailable',
            'allow_no_indices'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Cluster;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Stats
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cluster
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Stats extends AbstractEndpoint
{
    // A comma-separated list of node IDs or names to limit the returned information; use `_local` to return information from the node you&#039;re connecting to, leave empty to get information from all nodes
    private $nodeID;

    /**
     * @param $node_id
     *
     * @return $this
     */
    public function setNodeID($node_id)
    {
        if (isset($node_id) !== true) {
            return $this;
        }

        $this->nodeID = $node_id;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $node_id = $this->nodeID;
        $uri   = "/_cluster/stats";

        if (isset($node_id) === true) {
            $uri = "/_cluster/stats/nodes/$node_id";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'flat_settings',
            'human',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints;

use Elasticsearch\Common\Exceptions;

/**
 * Class UpdateByQuery
 *
 * @category Elasticsearch
 * @package Elasticsearch\Endpoints *
 * @author   Zachary Tong <zachary.tong@elasticsearch.com>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elasticsearch.org
 */
class UpdateByQuery extends AbstractEndpoint
{
    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        if (is_array($body) !== true) {
            throw new Exceptions\InvalidArgumentException(
                'Body must be an array'
            );
        }
        $this->body = $body;

        return $this;
    }


    /**
     * @throws \Elasticsearch\Common\Exceptions\BadMethodCallException
     * @return string
     */
    public function getURI()
    {
        if (!$this->index) {
            throw new Exceptions\RuntimeException(
                'index is required for UpdateByQuery'
            );
        }

        $uri = "/{$this->index}/_update_by_query";
        if ($this->type) {
            $uri = "/{$this->index}/{$this->type}/_update_by_query";
        }

        return $uri;
    }


    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return [
            'analyzer',
            'analyze_wildcard',
            'default_operator',
            'df',
            'explain',
            'fields',
            'fielddata_fields',
            'from',
            'ignore_unavailable',
            'allow_no_indices',
            'conflicts',
            'expand_wildcards',
            'lenient',
            'lowercase_expanded_terms',
            'preference',
            'q',
            'routing',
            'scroll',
            'search_type',
            'search_timeout',
            'size',
            'sort',
            '_source',
            '_source_exclude',
            '_source_include',
            'terminate_after',
            'stats',
            'suggest_field',
            'suggest_mode',
            'suggest_size',
            'suggest_text',
            'timeout',
            'track_scores',
            'version',
            'version_type',
            'request_cache',
            'refresh',
            'consistency',
            'scroll_size',
            'wait_for_completion',
            'slices',
        ];
    }


    /**
     * @return string
     */
    public function getMethod()
    {
        return 'POST';
    }
}
<?php

namespace Elasticsearch\Endpoints;

use Elasticsearch\Common\Exceptions;

/**
 * Class TermVectors
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class TermVectors extends AbstractEndpoint
{
    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->index) !== true) {
           throw new Exceptions\RuntimeException(
               'index is required for TermVectors'
           );
       }
       if (isset($this->type) !== true) {
           throw new Exceptions\RuntimeException(
               'type is required for TermVectors'
           );
       }
       if (isset($this->id) !== true && isset($this->body['doc']) !== true) {
           throw new Exceptions\RuntimeException(
               'id or doc is required for TermVectors'
           );
       }

       $index = $this->index;
       $type  = $this->type;
       $id    = $this->id;
       $uri   = "/$index/$type/_termvectors";

       if ($id !== null) {
           $uri = "/$index/$type/$id/_termvectors";
       }

       return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'term_statistics',
            'field_statistics',
            'fields',
            'offsets',
            'positions',
            'payloads',
            'preference',
            'routing',
            'parent',
            'realtime'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'POST';
    }
}
<?php

namespace Elasticsearch\Endpoints;

use Elasticsearch\Serializers\SerializerInterface;
use Elasticsearch\Transport;

/**
 * Interface BulkEndpointInterface
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
interface BulkEndpointInterface
{
    /**
     * Constructor
     *
     * @param SerializerInterface $serializer A serializer
     */
    public function __construct(SerializerInterface $serializer);
}
<?php

namespace Elasticsearch\Endpoints\Tasks;

use Elasticsearch\Common\Exceptions;
use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Get
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Tasks
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Get extends AbstractEndpoint
{
    private $taskId;

    /**
     * @param string $taskId
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setTaskId($taskId)
    {
        if (isset($taskId) !== true) {
            return $this;
        }

        $this->taskId = $taskId;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->taskId) === true) {
            return "/_tasks/{$this->taskId}";
        }

        return "/_tasks";
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'wait_for_completion'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Tasks;

use Elasticsearch\Common\Exceptions;
use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class TasksLists
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Tasks
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class TasksList extends AbstractEndpoint
{

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        return "/_tasks";
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'node_id',
            'actions',
            'detailed',
            'parent_node',
            'parent_task',
            'wait_for_completion',
            'group_by',
            'task_id'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Tasks;

use Elasticsearch\Common\Exceptions;
use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Cancel
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Tasks
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Cancel extends AbstractEndpoint
{
    private $taskId;

    /**
     * @param string $taskId
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setTaskId($taskId)
    {
        if (isset($taskId) !== true) {
            return $this;
        }

        $this->taskId = $taskId;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->id) === true) {
            return "/_tasks/{$this->taskId}/_cancel";
        }

        return "/_tasks/_cancel";
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'node_id',
            'actions',
            'parent_node',
            'parent_task',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'POST';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class ShardStores
 *
 * @category Elasticsearch
 * @package Elasticsearch\Endpoints\Indices
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */

class ShardStores extends AbstractEndpoint
{
    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $uri   = "/_shard_stores";

        if (isset($index) === true) {
            $uri = "/$index/_shard_stores";
        }

        return $uri;
    }


    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'status',
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
            'operation_threading'
        );
    }


    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Recovery
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Recovery extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $uri   = "/_recovery";

        if (isset($index) === true) {
            $uri = "/$index/_recovery";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'detailed',
            'active_only',
            'human'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices\Mapping;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class GetField
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices\Mapping
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class GetField extends AbstractEndpoint
{
    /** @var  string */
    private $fields;

    /**
     * @param string|array $fields
     *
     * @return $this
     */
    public function setFields($fields)
    {
        if (isset($fields) !== true) {
            return $this;
        }

        if (is_array($fields) === true) {
            $fields = implode(",", $fields);
        }

        $this->fields = $fields;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->fields) !== true) {
            throw new Exceptions\RuntimeException(
                'fields is required for Get Field Mapping'
            );
        }
        $uri = $this->getOptionalURI('_mapping/field');

        return $uri.'/'.$this->fields;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'include_defaults',
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
            'local'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices\Mapping;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Get
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices\Mapping
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Get extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $type = $this->type;
        $uri   = "/_mapping";

        if (isset($index) === true && isset($type) === true) {
            $uri = "/$index/_mapping/$type";
        } elseif (isset($type) === true) {
            $uri = "/_mapping/$type";
        } elseif (isset($index) === true) {
            $uri = "/$index/_mapping";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
            'wildcard_expansion',
            'local',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices\Mapping;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Put
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices\Mapping
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Put extends AbstractEndpoint
{
    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->type) !== true) {
            throw new Exceptions\RuntimeException(
                'type is required for Put'
            );
        }
        $index = $this->index;
        $type = $this->type;
        $uri   = "/_mapping/$type";

        if (isset($index) === true && isset($type) === true) {
            $uri = "/$index/$type/_mapping";
        } elseif (isset($type) === true) {
            $uri = "/_mapping/$type";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'ignore_conflicts',
            'timeout',
            'master_timeout',
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
            'update_all_types'
        );
    }

    /**
     * @return array
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     */
    public function getBody()
    {
        if (isset($this->body) !== true) {
            throw new Exceptions\RuntimeException('Body is required for Put Mapping');
        }

        return $this->body;
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'PUT';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices\Mapping;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Delete
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices\Mapping
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Delete extends AbstractEndpoint
{
    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->index) !== true) {
            throw new Exceptions\RuntimeException(
                'index is required for Delete'
            );
        }
        if (isset($this->type) !== true) {
            throw new Exceptions\RuntimeException(
                'type is required for Delete'
            );
        }
        $index = $this->index;
        $type = $this->type;
        $uri   = "/$index/$type/_mapping";

        if (isset($index) === true && isset($type) === true) {
            $uri = "/$index/$type/_mapping";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'master_timeout',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'DELETE';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices\Settings;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Get
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices\Settings
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Get extends AbstractEndpoint
{
    // The name of the settings that should be included
    private $name;

    /**
     * @param $name
     *
     * @return $this
     */
    public function setName($name)
    {
        if (isset($name) !== true) {
            return $this;
        }

        $this->name = $name;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $name = $this->name;
        $uri   = "/_settings";

        if (isset($index) === true && isset($name) === true) {
            $uri = "/$index/_settings/$name";
        } elseif (isset($name) === true) {
            $uri = "/_settings/$name";
        } elseif (isset($index) === true) {
            $uri = "/$index/_settings";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
            'flat_settings',
            'local',
            'include_defaults'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices\Settings;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Put
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices\Settings
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Put extends AbstractEndpoint
{
    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $uri   = "/_settings";

        if (isset($index) === true) {
            $uri = "/$index/_settings";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'master_timeout',
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
            'flat_settings',
            'preserve_existing'
        );
    }

    /**
     * @return array
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     */
    public function getBody()
    {
        if (isset($this->body) !== true) {
            throw new Exceptions\RuntimeException('Body is required for Put Settings');
        }

        return $this->body;
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'PUT';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices\Validate;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Query
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices\Validate
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Query extends AbstractEndpoint
{
    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        return $this->getOptionalURI('_validate/query');
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'explain',
            'ignore_indices',
            'operation_threading',
            'source',
            'q',
            'df',
            'default_operator',
            'analyzer',
            'analyze_wildcard',
            'lenient',
            'lowercase_expanded_terms'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices\Alias;

use Elasticsearch\Common\Exceptions\InvalidArgumentException;
use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class AbstractAliasEndpoint
 *
 * @category Elasticsearch
 * @package Elasticsearch\Endpoints\Indices\Alias
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
abstract class AbstractAliasEndpoint extends AbstractEndpoint
{
    /** @var null|string */
    protected $name = null;

    /**
     * @param $name
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     *
     * @return $this
     */
    public function setName($name)
    {
        if (is_string($name) !== true) {
            throw new InvalidArgumentException('Name must be a string');
        }
        $this->name = urlencode($name);

        return $this;
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices\Alias;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Get
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices\Alias
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Get extends AbstractEndpoint
{
    // A comma-separated list of alias names to return
    private $name;

    /**
     * @param $name
     *
     * @return $this
     */
    public function setName($name)
    {
        if (isset($name) !== true) {
            return $this;
        }

        $this->name = $name;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $name = $this->name;
        $uri   = "/_alias";

        if (isset($index) === true && isset($name) === true) {
            $uri = "/$index/_alias/$name";
        } elseif (isset($index) === true) {
            $uri = "/$index/_alias";
        } elseif (isset($name) === true) {
            $uri = "/_alias/$name";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
            'local',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices\Alias;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Put
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices\Alias
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Put extends AbstractEndpoint
{
    // The name of the alias to be created or updated
    private $name;

    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @param $name
     *
     * @return $this
     */
    public function setName($name)
    {
        if (isset($name) !== true) {
            return $this;
        }

        $this->name = $name;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->name) !== true) {
            throw new Exceptions\RuntimeException(
                'name is required for Put'
            );
        }

        if (isset($this->index) !== true) {
            throw new Exceptions\RuntimeException(
                'index is required for Put'
            );
        }
        $index = $this->index;
        $name = $this->name;
        $uri = "/$index/_alias/$name";

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'timeout',
            'master_timeout',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'PUT';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices\Alias;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Delete
 *
 * @category Elasticsearch
 * @package Elasticsearch\Endpoints\Indices\Alias
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Delete extends AbstractEndpoint
{
    // A comma-separated list of aliases to delete (supports wildcards); use `_all` to delete all aliases for the specified indices.
    private $name;

    /**
     * @param $name
     *
     * @return $this
     */
    public function setName($name)
    {
        if (isset($name) !== true) {
            return $this;
        }

        $this->name = $name;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->index) !== true) {
            throw new Exceptions\RuntimeException(
                'index is required for Delete'
            );
        }
        if (isset($this->name) !== true) {
            throw new Exceptions\RuntimeException(
                'name is required for Delete'
            );
        }
        $index = $this->index;
        $name = $this->name;
        $uri   = "/$index/_alias/$name";

        if (isset($index) === true && isset($name) === true) {
            $uri = "/$index/_alias/$name";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'timeout',
            'master_timeout',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'DELETE';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices\Alias;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Exists
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices\Alias
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Exists extends AbstractEndpoint
{
    // A comma-separated list of alias names to return
    private $name;

    /**
     * @param $name
     *
     * @return $this
     */
    public function setName($name)
    {
        if (isset($name) !== true) {
            return $this;
        }

        $this->name = $name;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $name = $this->name;
        $uri   = "/_alias/$name";

        if (isset($index) === true && isset($name) === true) {
            $uri = "/$index/_alias/$name";
        } elseif (isset($index) === true) {
            $uri = "/$index/_alias";
        } elseif (isset($name) === true) {
            $uri = "/_alias/$name";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
            'local',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'HEAD';
    }
}
<?php
/**
 * User: zach
 * Date: 01/20/2014
 * Time: 14:34:49 pm
 */

namespace Elasticsearch\Endpoints\Indices\Upgrade;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Post
 *
 * @category Elasticsearch
 * @package Elasticsearch\Endpoints\Indices
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */

class Get extends AbstractEndpoint
{

    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $uri   = "/_upgrade";

        if (isset($index) === true) {
            $uri = "/$index/_upgrade";
        }


        return $uri;
    }


    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'wait_for_completion',
            'only_ancient_segments',
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
        );
    }


    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php
/**
 * User: zach
 * Date: 01/20/2014
 * Time: 14:34:49 pm
 */

namespace Elasticsearch\Endpoints\Indices\Upgrade;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Post
 *
 * @category Elasticsearch
 * @package Elasticsearch\Endpoints\Indices
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */

class Post extends AbstractEndpoint
{

    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $uri   = "/_upgrade";

        if (isset($index) === true) {
            $uri = "/$index/_upgrade";
        }


        return $uri;
    }


    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'wait_for_completion',
            'only_ancient_segments',
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
        );
    }


    /**
     * @return string
     */
    public function getMethod()
    {
        return 'POST';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices\Cache;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Clear
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices\Cache
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Clear extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $uri   = "/_cache/clear";

        if (isset($index) === true) {
            $uri = "/$index/_cache/clear";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'field_data',
            'fielddata',
            'fields',
            'filter',
            'filter_cache',
            'filter_keys',
            'id',
            'id_cache',
            'index',
            'recycler',
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
            'request_cache',
            'request'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'POST';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Analyze
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Analyze extends AbstractEndpoint
{
    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $uri   = "/_analyze";

        if (isset($index) === true) {
            $uri = "/$index/_analyze";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'analyzer',
            'field',
            'filter',
            'index',
            'prefer_local',
            'text',
            'tokenizer',
            'format',
            'char_filter',
            'explain',
            'attributes',
            'format'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Get
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Get
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Get extends AbstractEndpoint
{
    private $feature;

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->index) !== true) {
            throw new Exceptions\RuntimeException(
                'index is required for Get'
            );
        }
        $index   = $this->index;
        $feature = $this->feature;
        $uri     = "/$index";

        if (isset($feature) === true) {
            $uri = "/$index/$feature";
        }

        return $uri;
    }

    public function setFeature($feature)
    {
        if (isset($feature) !== true) {
            return $this;
        }

        if (is_array($feature) === true) {
            $feature = implode(",", $feature);
        }

        $this->feature = $feature;

        return $this;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'local',
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
            'human'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices\Exists;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Types
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices\Exists
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Types extends AbstractEndpoint
{
    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->index) !== true) {
            throw new Exceptions\RuntimeException(
                'index is required for Types Exists'
            );
        }

        if (isset($this->type) !== true) {
            throw new Exceptions\RuntimeException(
                'type is required for Types Exists'
            );
        }

        $index = $this->index;
        $type  = $this->type;
        $uri   = "/$index/$type";

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'HEAD';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class ClearCache
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class ClearCache extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $uri   = "/_cache/clear";

        if (isset($index) === true) {
            $uri = "/$index/_cache/clear";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'field_data',
            'fielddata',
            'fields',
            'filter',
            'filter_cache',
            'filter_keys',
            'id',
            'id_cache',
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
            'index',
            'recycler',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Shrink.
 *
 * @category Elasticsearch
 *
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 *
 * @link     http://elastic.co
 */
class Shrink extends AbstractEndpoint
{
    // The name of the target index to shrink into
    private $target;
    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     *
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @param $target
     *
     * @return $this
     */
    public function setTarget($target)
    {
        if (isset($target) !== true) {
            return $this;
        }
        $this->target = $target;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\BadMethodCallException
     *
     * @return string
     */
    public function getURI()
    {
        if (isset($this->index) !== true) {
            throw new Exceptions\RuntimeException(
                'index is required for Shrink'
            );
        }
        if (isset($this->target) !== true) {
            throw new Exceptions\RuntimeException(
                'target is required for Shrink'
            );
        }
        $index = $this->index;
        $target = $this->target;
        $uri = "/$index/_shrink/$target";
        if (isset($index) === true && isset($target) === true) {
            $uri = "/$index/_shrink/$target";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'timeout',
            'master_timeout',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        //TODO Fix Me!
        return 'PUT';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices\Template;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Get
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices\Template
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Get extends AbstractEndpoint
{
    // The name of the template
    private $name;

    /**
     * @param $name
     *
     * @return $this
     */
    public function setName($name)
    {
        if (isset($name) !== true) {
            return $this;
        }

        $this->name = $name;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        $name = $this->name;
        $uri   = "/_template";

        if (isset($name) === true) {
            $uri = "/_template/$name";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'flat_settings',
            'local',
            'master_timeout'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices\Template;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Put
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices\Template
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Put extends AbstractEndpoint
{
    // The name of the template
    private $name;

    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @param $name
     *
     * @return $this
     */
    public function setName($name)
    {
        if (isset($name) !== true) {
            return $this;
        }

        $this->name = $name;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->name) !== true) {
            throw new Exceptions\RuntimeException(
                'name is required for Put'
            );
        }
        $name = $this->name;
        $uri   = "/_template/$name";

        if (isset($name) === true) {
            $uri = "/_template/$name";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'order',
            'timeout',
            'master_timeout',
            'flat_settings',
            'create'
        );
    }

    /**
     * @return array
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     */
    public function getBody()
    {
        if (isset($this->body) !== true) {
            throw new Exceptions\RuntimeException('Body is required for Put Template');
        }

        return $this->body;
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'PUT';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices\Template;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Delete
 *
 * @category Elasticsearch
 * @package Elasticsearch\Endpoints\Indices\Template
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */

class Delete extends AbstractEndpoint
{
    // The name of the template
    private $name;

    /**
     * @param $name
     *
     * @return $this
     */
    public function setName($name)
    {
        if (isset($name) !== true) {
            return $this;
        }

        $this->name = $name;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->name) !== true) {
            throw new Exceptions\RuntimeException(
                'name is required for Delete'
            );
        }
        $name = $this->name;
        $uri   = "/_template/$name";

        if (isset($name) === true) {
            $uri = "/_template/$name";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'timeout',
            'master_timeout',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'DELETE';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices\Template;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class AbstractTemplateEndpoint
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices\Template
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
abstract class AbstractTemplateEndpoint extends AbstractEndpoint
{
    /** @var  string */
    protected $name;

    /**
     * @param $name
     *
     * @return $this
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices\Template;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Exists
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices\Template
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Exists extends AbstractEndpoint
{
    // The name of the template
    private $name;

    /**
     * @param $name
     *
     * @return $this
     */
    public function setName($name)
    {
        if (isset($name) !== true) {
            return $this;
        }

        $this->name = $name;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->name) !== true) {
            throw new Exceptions\RuntimeException(
                'name is required for Exists'
            );
        }
        $name = $this->name;
        $uri   = "/_template/$name";

        if (isset($name) === true) {
            $uri = "/_template/$name";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'local',
            'master_timeout'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'HEAD';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Create
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Create extends AbstractEndpoint
{
    /**
     * @param array|object $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->index) !== true) {
            throw new Exceptions\RuntimeException(
                'index is required for Create'
            );
        }
        $index = $this->index;
        $uri   = "/$index";

        if (isset($index) === true) {
            $uri = "/$index";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'timeout',
            'master_timeout',
            'update_all_types',
            'wait_for_active_shards'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'PUT';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices\Field;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Get
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices\Field
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Get extends AbstractEndpoint
{
    // A comma-separated list of fields
    private $field;

    /**
     * @param $field
     *
     * @return $this
     */
    public function setField($field)
    {
        if (isset($field) !== true) {
            return $this;
        }

        $this->field = $field;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->field) !== true) {
            throw new Exceptions\RuntimeException(
                'field is required for Get'
            );
        }
        $index = $this->index;
        $type = $this->type;
        $field = $this->field;
        $uri   = "/_mapping/field/$field";

        if (isset($index) === true && isset($type) === true && isset($field) === true) {
            $uri = "/$index/_mapping/$type/field/$field";
        } elseif (isset($type) === true && isset($field) === true) {
            $uri = "/_mapping/$type/field/$field";
        } elseif (isset($index) === true && isset($field) === true) {
            $uri = "/$index/_mapping/field/$field";
        } elseif (isset($field) === true) {
            $uri = "/_mapping/field/$field";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'include_defaults',
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
            'local',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Stats
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Stats extends AbstractEndpoint
{
    // Limit the information returned the specific metrics.
    private $metric;

    /**
     * @param $metric
     *
     * @return $this
     */
    public function setMetric($metric)
    {
        if (isset($metric) !== true) {
            return $this;
        }

        if (is_array($metric)) {
            $metric = implode(",", $metric);
        }

        $this->metric = $metric;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $metric = $this->metric;
        $uri   = "/_stats";

        if (isset($index) === true && isset($metric) === true) {
            $uri = "/$index/_stats/$metric";
        } elseif (isset($index) === true) {
            $uri = "/$index/_stats";
        } elseif (isset($metric) === true) {
            $uri = "/_stats/$metric";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'completion_fields',
            'fielddata_fields',
            'fields',
            'groups',
            'human',
            'level',
            'types',
            'metric',
            'include_segment_file_sizes'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices\Type;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Exists
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices\Type
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Exists extends AbstractEndpoint
{
    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->index) !== true) {
            throw new Exceptions\RuntimeException(
                'index is required for Exists'
            );
        }
        if (isset($this->type) !== true) {
            throw new Exceptions\RuntimeException(
                'type is required for Exists'
            );
        }
        $uri   = "/{$this->index}/_mapping/{$this->type}";

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
            'local',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'HEAD';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class ForceMerge
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class ForceMerge extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $uri   = "/_forcemerge";

        if (isset($index) === true) {
            $uri = "/$index/_forcemerge";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'flush',
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
            'max_num_segments',
            'only_expunge_deletes',
            'operation_threading',
            'wait_for_merge',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'POST';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Status
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Status extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $uri   = "/_status";

        if (isset($index) === true) {
            $uri = "/$index/_status";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
            'human',
            'operation_threading',
            'recovery',
            'snapshot',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class ValidateQuery
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class ValidateQuery extends AbstractEndpoint
{
    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $type = $this->type;
        $uri   = "/_validate/query";

        if (isset($index) === true && isset($type) === true) {
            $uri = "/$index/$type/_validate/query";
        } elseif (isset($index) === true) {
            $uri = "/$index/_validate/query";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'explain',
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
            'operation_threading',
            'source',
            'q',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Rollover
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Rollover extends AbstractEndpoint
{
    private $alias;
    private $newIndex;

    /**
     * @param string $alias
     *
     * @return $this
     */
    public function setAlias($alias)
    {
        if ($alias === null) {
            return $this;
        }

        $this->alias = urlencode($alias);
        return $this;
    }

    /**
     * @param string $newIndex
     *
     * @return $this
     */
    public function setNewIndex($newIndex)
    {
        if ($newIndex === null) {
            return $this;
        }

        $this->newIndex = urlencode($newIndex);
        return $this;
    }

    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->alias) !== true) {
            throw new Exceptions\RuntimeException(
                'alias name is required for Rollover'
            );
        }

        $uri = "/{$this->alias}/_rollover";

        if (isset($this->newIndex) === true) {
            $uri .= "/{$this->newIndex}";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'timeout',
            'master_timeout',
            'wait_for_active_shards',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'POST';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Flush
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Flush extends AbstractEndpoint
{
    protected $synced = false;

    public function setSynced($synced)
    {
        $this->synced = $synced;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $uri   = "/_flush";

        if (isset($index) === true) {
            $uri = "/$index/_flush";
        }

        if ($this->synced === true) {
            $uri .= "/synced";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'force',
            'full',
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
            'wait_if_ongoing'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Delete
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Delete extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $uri   = "/$index";

        if (isset($index) === true) {
            $uri = "/$index";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'timeout',
            'master_timeout',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'DELETE';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Exists
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Exists extends AbstractEndpoint
{
    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->index) !== true) {
            throw new Exceptions\RuntimeException(
                'index is required for Exists'
            );
        }
        $index = $this->index;
        $uri   = "/$index";

        if (isset($index) === true) {
            $uri = "/$index";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
            'local',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'HEAD';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Seal
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Seal extends AbstractEndpoint
{
    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $uri   = "/_seal";

        if (isset($index) === true) {
            $uri = "/$index/_seal";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array();
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'POST';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Open
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Open extends AbstractEndpoint
{
    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->index) !== true) {
            throw new Exceptions\RuntimeException(
                'index is required for Open'
            );
        }
        $index = $this->index;
        $uri   = "/$index/_open";

        if (isset($index) === true) {
            $uri = "/$index/_open";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'timeout',
            'master_timeout',
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'POST';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Segments
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Segments extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $uri   = "/_segments";

        if (isset($index) === true) {
            $uri = "/$index/_segments";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
            'human',
            'operation_threading',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices\Aliases;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Update
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices\Aliases
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Update extends AbstractEndpoint
{
    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $uri   = "/_aliases";

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'timeout',
            'master_timeout',
        );
    }

    /**
     * @return array
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     */
    public function getBody()
    {
        if (isset($this->body) !== true) {
            throw new Exceptions\RuntimeException('Body is required for Update Aliases');
        }

        return $this->body;
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'POST';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices\Aliases;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Get
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices\Aliases
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Get extends AbstractEndpoint
{
    // A comma-separated list of alias names to filter
    private $name;

    /**
     * @param $name
     *
     * @return $this
     */
    public function setName($name)
    {
        if (isset($name) !== true) {
            return $this;
        }

        $this->name = $name;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $name = $this->name;
        $uri   = "/_aliases";

        if (isset($index) === true && isset($name) === true) {
            $uri = "/$index/_aliases/$name";
        } elseif (isset($name) === true) {
            $uri = "/_aliases/$name";
        } elseif (isset($index) === true) {
            $uri = "/$index/_aliases";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'timeout',
            'local',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Snapshotindex
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Snapshotindex extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $uri   = "/_gateway/snapshot";

        if (isset($index) === true) {
            $uri = "/$index/_gateway/snapshot";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'POST';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Refresh
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Refresh extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $uri   = "/_refresh";

        if (isset($index) === true) {
            $uri = "/$index/_refresh";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
            'force',
            'operation_threading',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices\Gateway;

use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Snapshot
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices\Gateway
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Snapshot extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $uri   = "/_gateway/snapshot";

        if (isset($index) === true) {
            $uri = "/$index/_gateway/snapshot";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'POST';
    }
}
<?php

namespace Elasticsearch\Endpoints\Indices;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Close
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Close extends AbstractEndpoint
{
    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->index) !== true) {
            throw new Exceptions\RuntimeException(
                'index is required for Close'
            );
        }
        $index = $this->index;
        $uri   = "/$index/_close";

        if (isset($index) === true) {
            $uri = "/$index/_close";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'timeout',
            'master_timeout',
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'POST';
    }
}
<?php

namespace Elasticsearch\Endpoints;

use Elasticsearch\Common\Exceptions;

/**
 * Class MTermVectors
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class MTermVectors extends AbstractEndpoint
{
    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        return $this->getOptionalURI('_mtermvectors');
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'ids',
            'term_statistics',
            'field_statistics',
            'fields',
            'offsets',
            'positions',
            'payloads',
            'preference',
            'routing',
            'parent',
            'realtime'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'POST';
    }
}
<?php

namespace Elasticsearch\Endpoints;

use Elasticsearch\Common\Exceptions;

/**
 * Class Percolate
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Percolate extends AbstractEndpoint
{
    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->index) !== true) {
            throw new Exceptions\RuntimeException(
                'index is required for Percolate'
            );
        }
        if (isset($this->type) !== true) {
            throw new Exceptions\RuntimeException(
                'type is required for Percolate'
            );
        }
        $index = $this->index;
        $type  = $this->type;
        $id    = $this->id;
        $uri   = "/$index/$type/_percolate";

        if (isset($id) === true) {
            $uri = "/$index/$type/$id/_percolate";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'routing',
            'preference',
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
            'percolate_index',
            'percolate_type',
            'version',
            'version_type',
            'percolate_format'
        );
    }

    /**
     * @return array
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     */
    public function getBody()
    {
        return $this->body;
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints;

use Elasticsearch\Common\Exceptions;

/**
 * Class Deletebyquery
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class DeleteByQuery extends AbstractEndpoint
{
    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (!$this->index) {
            throw new Exceptions\RuntimeException(
                'index is required for Deletebyquery'
            );
        }

        $uri = "/{$this->index}/_delete_by_query";
        if ($this->type) {
            $uri = "/{$this->index}/{$this->type}/_delete_by_query";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            '_source',
            '_source_exclude',
            '_source_include',
            'allow_no_indices',
            'analyze_wildcard',
            'analyzer',
            'conflicts',
            'default_operator',
            'df',
            'expand_wildcards',
            'from',
            'ignore_unavailable',
            'lenient',
            'preference',
            'query',
            'q',
            'refresh',
            'request_cache',
            'requests_per_second',
            'routing',
            'scroll',
            'scroll_size',
            'search_timeout',
            'search_type',
            'size',
            'slices',
            'sort',
            'stats',
            'terminate_after',
            'timeout',
            'version',
            'wait_for_active_shards',
            'wait_for_completion',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'POST';
    }
}
<?php

namespace Elasticsearch\Endpoints;

use Elasticsearch\Common\Exceptions;

/**
 * Class Index
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Index extends AbstractEndpoint
{
    /** @var bool  */
    private $createIfAbsent = false;

    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @return $this
     */
    public function createIfAbsent()
    {
        $this->createIfAbsent = true;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->index) !== true) {
            throw new Exceptions\RuntimeException(
                'index is required for Index'
            );
        }

        if (isset($this->type) !== true) {
            throw new Exceptions\RuntimeException(
                'type is required for Index'
            );
        }

        $id    = $this->id;
        $index = $this->index;
        $type  = $this->type;
        $uri   = "/$index/$type";

        if (isset($id) === true) {
            $uri = "/$index/$type/$id";
        }
        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'consistency',
            'op_type',
            'parent',
            'percolate',
            'refresh',
            'replication',
            'routing',
            'timeout',
            'timestamp',
            'ttl',
            'version',
            'version_type',
            'pipeline'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        if (isset($this->id) === true) {
            return 'PUT';
        } else {
            return 'POST';
        }
    }

    /**
     * @return array
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     */
    public function getBody()
    {
        if (isset($this->body) !== true) {
            throw new Exceptions\RuntimeException('Document body must be set for index request');
        } else {
            return $this->body;
        }
    }
}
<?php

namespace Elasticsearch\Endpoints;

use Elasticsearch\Common\Exceptions;

/**
 * Class Get
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Get extends AbstractEndpoint
{
    /** @var bool  */
    private $returnOnlySource = false;

    /** @var bool  */
    private $checkOnlyExistance = false;

    /**
     * @return $this
     */
    public function returnOnlySource()
    {
        $this->returnOnlySource = true;

        return $this;
    }

    /**
     * @return $this
     */
    public function checkOnlyExistance()
    {
        $this->checkOnlyExistance = true;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->id) !== true) {
            throw new Exceptions\RuntimeException(
                'id is required for Get'
            );
        }
        if (isset($this->index) !== true) {
            throw new Exceptions\RuntimeException(
                'index is required for Get'
            );
        }
        if (isset($this->type) !== true) {
            throw new Exceptions\RuntimeException(
                'type is required for Get'
            );
        }
        $id = $this->id;
        $index = $this->index;
        $type = $this->type;
        $uri   = "/$index/$type/$id";

        if (isset($index) === true && isset($type) === true && isset($id) === true) {
            $uri = "/$index/$type/$id";
        }

        if ($this->returnOnlySource === true) {
            $uri .= '/_source';
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'fields',
            'parent',
            'preference',
            'realtime',
            'refresh',
            'routing',
            '_source',
            '_source_exclude',
            '_source_include',
            'version',
            'version_type',
            'stored_fields'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        if ($this->checkOnlyExistance === true) {
            return 'HEAD';
        } else {
            return 'GET';
        }
    }
}
<?php

namespace Elasticsearch\Endpoints;

use Elasticsearch\Common\Exceptions;

/**
 * Class Suggest
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Suggest extends AbstractEndpoint
{
    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $uri   = "/_suggest";

        if (isset($index) === true) {
            $uri = "/$index/_suggest";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
            'preference',
            'routing',
            'source',
        );
    }

    /**
     * @return array
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     */
    public function getBody()
    {
        if (isset($this->body) !== true) {
            throw new Exceptions\RuntimeException('Body is required for Suggest');
        }

        return $this->body;
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'POST';
    }
}
<?php

namespace Elasticsearch\Endpoints\Ingest;

use Elasticsearch\Common\Exceptions;
use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Simulate
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Ingest
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Simulate extends AbstractEndpoint
{
    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->id) === true) {
            return "/_ingest/pipeline/{$this->id}/_simulate";
        }
        return "/_ingest/pipeline/_simulate";
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'verbose',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Ingest\Pipeline;

use Elasticsearch\Common\Exceptions;
use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Get
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Ingest
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Get extends AbstractEndpoint
{
    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->id) !== true) {
            return '/_ingest/pipeline/*';
        }

        $id = $this->id;

        return "/_ingest/pipeline/$id";
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'master_timeout'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Ingest\Pipeline;

use Elasticsearch\Common\Exceptions;
use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Put
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Ingest
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Put extends AbstractEndpoint
{
    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->id) !== true) {
            throw new Exceptions\RuntimeException(
                'id is required for PutPipeline'
            );
        }
        $id = $this->id;
        $uri = "/_ingest/pipeline/$id";

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'master_timeout',
            'timeout'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'PUT';
    }
}
<?php

namespace Elasticsearch\Endpoints\Ingest\Pipeline;

use Elasticsearch\Common\Exceptions;
use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Delete
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Ingest
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Delete extends AbstractEndpoint
{
    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->id) !== true) {
            throw new Exceptions\RuntimeException(
                'id is required for DeletePipeline'
            );
        }
        $id = $this->id;
        $uri = "/_ingest/pipeline/$id";

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'master_timeout',
            'timeout'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'DELETE';
    }
}
<?php

namespace Elasticsearch\Endpoints\Source;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Get
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Source
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Get extends AbstractEndpoint
{
    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->id) !== true) {
            throw new Exceptions\RuntimeException(
                'id is required for Get'
            );
        }
        if (isset($this->index) !== true) {
            throw new Exceptions\RuntimeException(
                'index is required for Get'
            );
        }
        if (isset($this->type) !== true) {
            throw new Exceptions\RuntimeException(
                'type is required for Get'
            );
        }
        $id = $this->id;
        $index = $this->index;
        $type = $this->type;
        $uri   = "/$index/$type/$id/_source";

        if (isset($index) === true && isset($type) === true && isset($id) === true) {
            $uri = "/$index/$type/$id/_source";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'parent',
            'preference',
            'realtime',
            'refresh',
            'routing',
            '_source',
            '_source_exclude',
            '_source_include',
            'version',
            'version_type',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Template;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Get
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Template
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Get extends AbstractEndpoint
{
    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->id) !== true) {
            throw new Exceptions\RuntimeException(
                'id is required for Get'
            );
        }
        $templateId = $this->id;
        $uri  = "/_search/template/$templateId";

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array();
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Template;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Put
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Template
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Put extends AbstractEndpoint
{
    /**
     * @param array $body
     *
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->id) !== true) {
            throw new Exceptions\RuntimeException(
                'id is required for Put'
            );
        }

        $templateId = $this->id;
        $uri  = "/_search/template/$templateId";

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array();
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'PUT';
    }
}
<?php

namespace Elasticsearch\Endpoints\Template;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Delete
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Template
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Delete extends AbstractEndpoint
{
    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->id) !== true) {
            throw new Exceptions\RuntimeException(
                'id is required for Delete'
            );
        }
        $templateId   = $this->id;
        $uri  = "/_search/template/$templateId";

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array();
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'DELETE';
    }
}
<?php

namespace Elasticsearch\Endpoints;

use Elasticsearch\Common\Exceptions;

/**
 * Class Create
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Create extends AbstractEndpoint
{
    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->index) !== true) {
            throw new Exceptions\RuntimeException(
                'index is required for Create'
            );
        }

        if (isset($this->type) !== true) {
            throw new Exceptions\RuntimeException(
                'type is required for Create'
            );
        }

        if (isset($this->id) !== true) {
            throw new Exceptions\RuntimeException(
                'id is required for Create'
            );
        }

        $id    = $this->id;
        $index = $this->index;
        $type  = $this->type;
        return "/$index/$type/$id/_create";
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'consistency',
            'op_type',
            'parent',
            'percolate',
            'refresh',
            'replication',
            'routing',
            'timeout',
            'timestamp',
            'ttl',
            'version',
            'version_type',
            'pipeline'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'PUT';
    }

    /**
     * @return array
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     */
    public function getBody()
    {
        if (isset($this->body) !== true) {
            throw new Exceptions\RuntimeException('Document body must be set for create request');
        } else {
            return $this->body;
        }
    }
}
<?php

namespace Elasticsearch\Endpoints;

use Elasticsearch\Common\Exceptions\InvalidArgumentException;
use Elasticsearch\Common\Exceptions;

/**
 * Class Search
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Search extends AbstractEndpoint
{
    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $type = $this->type;
        $uri   = "/_search";

        if (isset($index) === true && isset($type) === true) {
            $uri = "/$index/$type/_search";
        } elseif (isset($index) === true) {
            $uri = "/$index/_search";
        } elseif (isset($type) === true) {
            $uri = "/_all/$type/_search";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'analyzer',
            'analyze_wildcard',
            'default_operator',
            'df',
            'explain',
            'from',
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
            'indices_boost',
            'lenient',
            'lowercase_expanded_terms',
            'preference',
            'q',
            'query_cache',
            'request_cache',
            'routing',
            'scroll',
            'search_type',
            'size',
            'slice',
            'sort',
            'source',
            '_source',
            '_source_exclude',
            '_source_include',
            'stats',
            'suggest_field',
            'suggest_mode',
            'suggest_size',
            'suggest_text',
            'timeout',
            'version',
            'fielddata_fields',
            'docvalue_fields',
            'filter_path',
            'terminate_after',
            'stored_fields',
            'batched_reduce_size',
            'typed_keys'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Script;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Get
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Script
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Get extends AbstractEndpoint
{
    /** @var  String */
    private $lang;

    /**
     * @param $lang
     *
     * @return $this
     */
    public function setLang($lang)
    {
        if (isset($lang) !== true) {
            return $this;
        }

        $this->lang = $lang;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->lang) !== true) {
            throw new Exceptions\RuntimeException(
                'lang is required for Put'
            );
        }
        if (isset($this->id) !== true) {
            throw new Exceptions\RuntimeException(
                'id is required for put'
            );
        }
        $id   = $this->id;
        $lang = $this->lang;
        $uri  = "/_scripts/$lang/$id";

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'version_type',
            'version'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints\Script;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Put
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Script
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Put extends AbstractEndpoint
{
    /** @var  String */
    private $lang;

    /**
     * @param $lang
     *
     * @return $this
     */
    public function setLang($lang)
    {
        if (isset($lang) !== true) {
            return $this;
        }

        $this->lang = $lang;

        return $this;
    }

    /**
     * @param array $body
     *
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->lang) !== true) {
            throw new Exceptions\RuntimeException(
                'lang is required for Put'
            );
        }
        if (isset($this->id) !== true) {
            throw new Exceptions\RuntimeException(
                'id is required for put'
            );
        }
        $id   = $this->id;
        $lang = $this->lang;
        $uri  = "/_scripts/$lang/$id";

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'version_type',
            'version',
            'op_type'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'PUT';
    }
}
<?php

namespace Elasticsearch\Endpoints\Script;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Common\Exceptions;

/**
 * Class Delete
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Script
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Delete extends AbstractEndpoint
{
    /** @var  String */
    private $lang;

    /**
     * @param $lang
     *
     * @return $this
     */
    public function setLang($lang)
    {
        if (isset($lang) !== true) {
            return $this;
        }

        $this->lang = $lang;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->lang) !== true) {
            throw new Exceptions\RuntimeException(
                'lang is required for Put'
            );
        }
        if (isset($this->id) !== true) {
            throw new Exceptions\RuntimeException(
                'id is required for put'
            );
        }
        $id   = $this->id;
        $lang = $this->lang;
        $uri  = "/_scripts/$lang/$id";

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'version',
            'version_type'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'DELETE';
    }
}
<?php

namespace Elasticsearch\Endpoints;

use Elasticsearch\Serializers\SerializerInterface;
use Elasticsearch\Transport;

/**
 * Class MPercolate
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class MPercolate extends AbstractEndpoint implements BulkEndpointInterface
{
    /**
     * @param SerializerInterface $serializer
     */
    public function __construct(SerializerInterface $serializer)
    {
        $this->serializer = $serializer;
    }

    /**
     * @param string|array $body
     *
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        if (is_array($body) === true) {
            $bulkBody = "";
            foreach ($body as $item) {
                $bulkBody .= $this->serializer->serialize($item)."\n";
            }
            $body = $bulkBody;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        return $this->getOptionalURI('_mpercolate');
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'POST';
    }
}
<?php

namespace Elasticsearch\Endpoints;

use Elasticsearch\Common\Exceptions\UnexpectedValueException;
use Elasticsearch\Serializers\SerializerInterface;
use Elasticsearch\Transport;
use Exception;
use GuzzleHttp\Ring\Future\FutureArrayInterface;

/**
 * Class AbstractEndpoint
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
abstract class AbstractEndpoint
{
    /** @var array  */
    protected $params = array();

    /** @var  string */
    protected $index = null;

    /** @var  string */
    protected $type = null;

    /** @var  string|int */
    protected $id = null;

    /** @var  string */
    protected $method = null;

    /** @var  array */
    protected $body = null;

    /** @var array  */
    private $options = [];

    /** @var  SerializerInterface */
    protected $serializer;

    /**
     * @return string[]
     */
    abstract public function getParamWhitelist();

    /**
     * @return string
     */
    abstract public function getURI();

    /**
     * @return string
     */
    abstract public function getMethod();


    /**
     * Set the parameters for this endpoint
     *
     * @param string[] $params Array of parameters
     * @return $this
     */
    public function setParams($params)
    {
        if (is_object($params) === true) {
            $params = (array) $params;
        }

        $this->checkUserParams($params);
        $params = $this->convertCustom($params);
        $this->extractOptions($params);
        $this->params = $this->convertArraysToStrings($params);

        return $this;
    }

    /**
     * @return array
     */
    public function getParams()
    {
        return $this->params;
    }

    /**
     * @return array
     */
    public function getOptions()
    {
        return $this->options;
    }

    /**
     * @return string|null
     */
    public function getIndex()
    {
        return $this->index;
    }

    /**
     * @param string $index
     *
     * @return $this
     */
    public function setIndex($index)
    {
        if ($index === null) {
            return $this;
        }

        if (is_array($index) === true) {
            $index = array_map('trim', $index);
            $index = implode(",", $index);
        }

        $this->index = urlencode($index);

        return $this;
    }

    /**
     * @return string|null
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * @param string $type
     *
     * @return $this
     */
    public function setType($type)
    {
        if ($type === null) {
            return $this;
        }

        if (is_array($type) === true) {
            $type = array_map('trim', $type);
            $type = implode(",", $type);
        }

        $this->type = urlencode($type);

        return $this;
    }

    /**
     * @param int|string $docID
     *
     * @return $this
     */
    public function setID($docID)
    {
        if ($docID === null) {
            return $this;
        }

        $this->id = urlencode($docID);

        return $this;
    }

    /**
     * @return array
     */
    public function getBody()
    {
        return $this->body;
    }

    /**
     * @param string $endpoint
     *
     * @return string
     */
    protected function getOptionalURI($endpoint)
    {
        $uri = array();
        $uri[] = $this->getOptionalIndex();
        $uri[] = $this->getOptionalType();
        $uri[] = $endpoint;
        $uri =  array_filter($uri);

        return '/' . implode('/', $uri);
    }

    /**
     * @return string
     */
    private function getOptionalIndex()
    {
        if (isset($this->index) === true) {
            return $this->index;
        } else {
            return '_all';
        }
    }

    /**
     * @return string
     */
    private function getOptionalType()
    {
        if (isset($this->type) === true) {
            return $this->type;
        } else {
            return '';
        }
    }

    /**
     * @param array $params
     *
     * @throws \Elasticsearch\Common\Exceptions\UnexpectedValueException
     */
    private function checkUserParams($params)
    {
        if (isset($params) !== true) {
            return; //no params, just return.
        }

        $whitelist = array_merge($this->getParamWhitelist(), array('client', 'custom', 'filter_path', 'human'));

        $invalid = array_diff(array_keys($params), $whitelist);
        if (count($invalid) > 0) {
            sort($invalid);
            sort($whitelist);
            throw new UnexpectedValueException(sprintf(
                (count($invalid) > 1 ? '"%s" are not valid parameters.' : '"%s" is not a valid parameter.').' Allowed parameters are "%s"',
                implode('", "', $invalid),
                implode('", "', $whitelist)
            ));
        }
    }

    /**
     * @param $params       Note: this is passed by-reference!
     */
    private function extractOptions(&$params)
    {
        // Extract out client options, then start transforming
        if (isset($params['client']) === true) {
            $this->options['client'] = $params['client'];
            unset($params['client']);
        }

        $ignore = isset($this->options['client']['ignore']) ? $this->options['client']['ignore'] : null;
        if (isset($ignore) === true) {
            if (is_string($ignore)) {
                $this->options['client']['ignore'] = explode(",", $ignore);
            } elseif (is_array($ignore)) {
                $this->options['client']['ignore'] = $ignore;
            } else {
                $this->options['client']['ignore'] = [$ignore];
            }
        }
    }

    private function convertCustom($params)
    {
        if (isset($params['custom']) === true) {
            foreach ($params['custom'] as $k => $v) {
                $params[$k] = $v;
            }
            unset($params['custom']);
        }

        return $params;
    }

    private function convertArraysToStrings($params)
    {
        foreach ($params as $key => &$value) {
            if (!($key === 'client' || $key == 'custom') && is_array($value) === true) {
                if ($this->isNestedArray($value) !== true) {
                    $value = implode(",", $value);
                }
            }
        }

        return $params;
    }

    private function isNestedArray($a)
    {
        foreach ($a as $v) {
            if (is_array($v)) {
                return true;
            }
        }

        return false;
    }
}
<?php

namespace Elasticsearch\Endpoints;

use Elasticsearch\Common\Exceptions\InvalidArgumentException;
use Elasticsearch\Common\Exceptions;

/**
 * Class FieldCaps
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class FieldCaps extends AbstractEndpoint
{
    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;
        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;

        if (isset($index) === true ) {
            return "/$index/_field_caps";
        } else {
            return "/_field_caps";
        }
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'fields',
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints;

use Elasticsearch\Common\Exceptions;

/**
 * Class CountPercolate
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class CountPercolate extends AbstractEndpoint
{
    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->index) !== true) {
            throw new Exceptions\RuntimeException(
                'index is required for CountPercolate'
            );
        }

        if (isset($this->type) !== true) {
            throw new Exceptions\RuntimeException(
                'type is required for CountPercolate'
            );
        }

        $index = $this->index;
        $type  = $this->type;
        $id    = $this->id;
        $uri   = "/$index/$type/_percolate/count";

        if (isset($id) === true) {
            $uri = "/$index/$type/$id/_percolate/count";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'routing',
            'preference',
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
            'percolate_index',
            'percolate_type',
            'version',
            'version_type'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints;

use Elasticsearch\Common\Exceptions;

/**
 * Class Count
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Count extends AbstractEndpoint
{
    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $type = $this->type;
        $uri   = "/_count";

        if (isset($index) === true && isset($type) === true) {
            $uri = "/$index/$type/_count";
        } elseif (isset($type) === true) {
            $uri = "/_all/$type/_count";
        } elseif (isset($index) === true) {
            $uri = "/$index/_count";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
            'min_score',
            'preference',
            'routing',
            'source',
            'q',
            'df',
            'default_operator',
            'analyzer',
            'lowercase_expanded_terms',
            'analyze_wildcard',
            'lenient',
            'lowercase_expanded_terms',
            'terminate_after'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints;

use Elasticsearch\Common\Exceptions;

/**
 * Class Clearscroll
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class ClearScroll extends AbstractEndpoint
{
    // A comma-separated list of scroll IDs to clear
    private $scrollId;

    /**
     * @param $scroll_id
     *
     * @return $this
     */
    public function setScrollId($scrollId)
    {
        if (isset($scrollId) !== true) {
            return $this;
        }

        $this->scrollId = $scrollId;

        return $this;
    }

    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        return "/_search/scroll/";
    }

    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @return array
     */
    public function getBody()
    {
        if (isset($this->body)) {
            return $this->body;
        }
        if (is_array($this->scrollId)) {
            return ['scroll_id' => $this->scrollId];
        }
        return ['scroll_id' => [$this->scrollId]];
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'DELETE';
    }
}
<?php

namespace Elasticsearch\Endpoints;

/**
 * Class Ping
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Ping extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        $uri   = "/";

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'HEAD';
    }
}
<?php

namespace Elasticsearch\Endpoints;

/**
 * Class Reindex
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Indices
 * @author   Augustin Husson <husson.augustin@gmail.com>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Reindex extends AbstractEndpoint
{

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'slices',
            'refresh',
            'timeout',
            'consistency',
            'wait_for_completion',
            'requests_per_second',
        );
    }

    /**
     * @return string
     */
    public function getURI()
    {
        return '/_reindex';
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'POST';
    }

    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }
}
<?php

namespace Elasticsearch\Endpoints;

use Elasticsearch\Common\Exceptions;

/**
 * Class Delete
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Delete extends AbstractEndpoint
{
    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->id) !== true) {
            throw new Exceptions\RuntimeException(
                'id is required for Delete'
            );
        }
        if (isset($this->index) !== true) {
            throw new Exceptions\RuntimeException(
                'index is required for Delete'
            );
        }
        if (isset($this->type) !== true) {
            throw new Exceptions\RuntimeException(
                'type is required for Delete'
            );
        }
        $id = $this->id;
        $index = $this->index;
        $type = $this->type;
        $uri   = "/$index/$type/$id";

        if (isset($index) === true && isset($type) === true && isset($id) === true) {
            $uri = "/$index/$type/$id";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'consistency',
            'parent',
            'refresh',
            'replication',
            'routing',
            'timeout',
            'version',
            'version_type',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'DELETE';
    }
}
<?php

namespace Elasticsearch\Endpoints;

use Elasticsearch\Serializers\SerializerInterface;
use Elasticsearch\Transport;

/**
 * Class Bulk
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Bulk extends AbstractEndpoint implements BulkEndpointInterface
{
    /**
     * @param SerializerInterface $serializer
     */
    public function __construct(SerializerInterface $serializer)
    {
        $this->serializer = $serializer;
    }

    /**
     * @param string|array|\Traversable $body
     *
     * @return $this
     */
    public function setBody($body)
    {
        if (empty($body)) {
            return $this;
        }

        if (is_array($body) === true || $body instanceof \Traversable) {
            foreach ($body as $item) {
                $this->body .= $this->serializer->serialize($item) . "\n";
            }
        } else {
            $this->body = $body;
        }

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        return $this->getOptionalURI('_bulk');
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'consistency',
            'refresh',
            'replication',
            'type',
            'fields',
            'pipeline',
            '_source',
            '_source_include',
            '_source_exclude',
            'pipeline'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'POST';
    }
}
<?php

namespace Elasticsearch\Endpoints;

use Elasticsearch\Common\Exceptions\InvalidArgumentException;
use Elasticsearch\Common\Exceptions;

/**
 * Class SearchTemplate
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class SearchTemplate extends AbstractEndpoint
{
    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $type = $this->type;
        $uri   = "/_search/template";

        if (isset($index) === true && isset($type) === true) {
            $uri = "/$index/$type/_search/template";
        } elseif (isset($index) === true) {
            $uri = "/$index/_search/template";
        } elseif (isset($type) === true) {
            $uri = "/_all/$type/_search/template";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
            'preference',
            'routing',
            'scroll',
            'search_type'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints;

use Elasticsearch\Common\Exceptions;

/**
 * Class Exists
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Exists extends AbstractEndpoint
{
    /**
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     * @return string
     */
    public function getURI()
    {
        if (isset($this->id) !== true) {
            throw new Exceptions\RuntimeException(
                'id is required for Exists'
            );
        }
        if (isset($this->index) !== true) {
            throw new Exceptions\RuntimeException(
                'index is required for Exists'
            );
        }
        if (isset($this->type) !== true) {
            throw new Exceptions\RuntimeException(
                'type is required for Exists'
            );
        }
        $id = $this->id;
        $index = $this->index;
        $type = $this->type;
        $uri   = "/$index/$type/$id";

        if (isset($index) === true && isset($type) === true && isset($id) === true) {
            $uri = "/$index/$type/$id";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'parent',
            'preference',
            'realtime',
            'refresh',
            'routing',
            'version',
            'stored_fields'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'HEAD';
    }
}
<?php

namespace Elasticsearch\Endpoints;

use Elasticsearch\Common\Exceptions;
use Elasticsearch\Serializers\SerializerInterface;
use Elasticsearch\Transport;

/**
 * Class MsearchTemplate
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class MsearchTemplate extends AbstractEndpoint
{
    /**
     * @param SerializerInterface $serializer
     */
    public function __construct(SerializerInterface $serializer)
    {
        $this->serializer = $serializer;
    }

    /**
     * @param array|string $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        if (is_array($body) === true) {
            $bulkBody = "";
            foreach ($body as $item) {
                $bulkBody .= $this->serializer->serialize($item)."\n";
            }
            $body = $bulkBody;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $type = $this->type;
        $uri   = "/_msearch/template";

        if (isset($index) === true && isset($type) === true) {
            $uri = "/$index/$type/_msearch/template";
        } elseif (isset($index) === true) {
            $uri = "/$index/_msearch/template";
        } elseif (isset($type) === true) {
            $uri = "/_all/$type/_msearch/template";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'search_type',
            'typed_keys',
            'max_concurrent_searches'
        );
    }

    /**
     * @return array
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     */
    public function getBody()
    {
        if (isset($this->body) !== true) {
            throw new Exceptions\RuntimeException('Body is required for MSearch');
        }

        return $this->body;
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints;

use Elasticsearch\Common\Exceptions;

/**
 * Class FieldStats
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class FieldStats extends AbstractEndpoint
{

    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $uri   = "/_field_stats";

        if (isset($index) === true) {
            $uri = "/$index/_field_stats";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'fields',
            'level',
            'ignore_unavailable',
            'allow_no_indices',
            'expand_wildcards',
            'fields'
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints;

use Elasticsearch\Common\Exceptions;
use Elasticsearch\Serializers\SerializerInterface;
use Elasticsearch\Transport;

/**
 * Class Msearch
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Msearch extends AbstractEndpoint
{
    /**
     * @param SerializerInterface $serializer
     */
    public function __construct(SerializerInterface $serializer)
    {
        $this->serializer = $serializer;
    }

    /**
     * @param array|string $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        if (is_array($body) === true) {
            $bulkBody = "";
            foreach ($body as $item) {
                $bulkBody .= $this->serializer->serialize($item)."\n";
            }
            $body = $bulkBody;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $type = $this->type;
        $uri   = "/_msearch";

        if (isset($index) === true && isset($type) === true) {
            $uri = "/$index/$type/_msearch";
        } elseif (isset($index) === true) {
            $uri = "/$index/_msearch";
        } elseif (isset($type) === true) {
            $uri = "/_all/$type/_msearch";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'search_type',
            'typed_keys',
        );
    }

    /**
     * @return array
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     */
    public function getBody()
    {
        if (isset($this->body) !== true) {
            throw new Exceptions\RuntimeException('Body is required for MSearch');
        }

        return $this->body;
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints;

use Elasticsearch\Common\Exceptions;

/**
 * Class Scroll
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Scroll extends AbstractEndpoint
{

    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @return array
     */
    public function getBody()
    {
        return $this->body;
    }

    /**
     * @param $scroll
     *
     * @return $this
     */
    public function setScroll($scroll)
    {
        if (isset($scroll) !== true) {
            return $this;
        }

        $this->body['scroll'] = $scroll;

        return $this;
    }

    /**
     * @param $scroll_id
     *
     * @return $this
     */
    public function setScrollId($scroll_id)
    {
        if (isset($scroll_id) !== true) {
            return $this;
        }

        $this->body['scroll_id'] = $scroll_id;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $uri   = "/_search/scroll";
        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'scroll',
        );
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Endpoints;

use Elasticsearch\Common\Exceptions;

/**
 * Class Mget
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Mget extends AbstractEndpoint
{
    /**
     * @param array $body
     *
     * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException
     * @return $this
     */
    public function setBody($body)
    {
        if (isset($body) !== true) {
            return $this;
        }

        $this->body = $body;

        return $this;
    }

    /**
     * @return string
     */
    public function getURI()
    {
        $index = $this->index;
        $type = $this->type;
        $uri   = "/_mget";

        if (isset($index) === true && isset($type) === true) {
            $uri = "/$index/$type/_mget";
        } elseif (isset($index) === true) {
            $uri = "/$index/_mget";
        } elseif (isset($type) === true) {
            $uri = "/_all/$type/_mget";
        }

        return $uri;
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array(
            'fields',
            'preference',
            'realtime',
            'refresh',
            '_source',
            '_source_exclude',
            '_source_include',
            'routing',
            'stored_fields'
        );
    }

    /**
     * @return array
     * @throws \Elasticsearch\Common\Exceptions\RuntimeException
     */
    public function getBody()
    {
        if (isset($this->body) !== true) {
            throw new Exceptions\RuntimeException('Body is required for MGet');
        }

        return $this->body;
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'POST';
    }
}
<?php

namespace Elasticsearch\Endpoints\Remote;
use Elasticsearch\Endpoints\AbstractEndpoint;

/**
 * Class Info
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Endpoints\Cluster\Nodes
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Info extends AbstractEndpoint
{
    /**
     * @return string
     */
    public function getURI()
    {
        return "/_remote/info";
    }

    /**
     * @return string[]
     */
    public function getParamWhitelist()
    {
        return array();
    }

    /**
     * @return string
     */
    public function getMethod()
    {
        return 'GET';
    }
}
<?php

namespace Elasticsearch\Namespaces;

use Elasticsearch\Common\Exceptions\Missing404Exception;
use Elasticsearch\Common\Exceptions\RoutingMissingException;
use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Transport;
use GuzzleHttp\Ring\Future\FutureArrayInterface;

/**
 * Trait AbstractNamespace
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Namespaces
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
trait BooleanRequestWrapper
{
    /**
     * Perform Request
     *
     * @param  AbstractEndpoint $endpoint The Endpoint to perform this request against
     *
     * @throws Missing404Exception
     * @throws RoutingMissingException
     */
    public static function performRequest(AbstractEndpoint $endpoint, Transport $transport)
    {
        try {
            $response = $transport->performRequest(
                $endpoint->getMethod(),
                $endpoint->getURI(),
                $endpoint->getParams(),
                $endpoint->getBody(),
                $endpoint->getOptions()
            );

            $response = $transport->resultOrFuture($response, $endpoint->getOptions());
            if (!($response instanceof FutureArrayInterface)) {
                if ($response['status'] === 200) {
                    return true;
                } else {
                    return false;
                }
            } else {
                // async mode, can't easily resolve this...punt to user
                return $response;
            }
        } catch (Missing404Exception $exception) {
            return false;
        } catch (RoutingMissingException $exception) {
            return false;
        }
    }
}
<?php

namespace Elasticsearch\Namespaces;

/**
 * Class SnapshotNamespace
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Namespaces\SnapshotNamespace
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class SnapshotNamespace extends AbstractNamespace
{
    /**
     * $params['master_timeout'] = (time) Explicit operation timeout for connection to master node
     *        ['wait_for_completion'] = (bool) Should this request wait until the operation has completed before returning
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function create($params = array())
    {
        $repository = $this->extractArgument($params, 'repository');
        $snapshot = $this->extractArgument($params, 'snapshot');
        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Snapshot\Create $endpoint */
        $endpoint = $endpointBuilder('Snapshot\Create');
        $endpoint->setRepository($repository)
                 ->setSnapshot($snapshot)
                 ->setParams($params)
                 ->setBody($body);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['master_timeout'] = (time) Explicit operation timeout for connection to master node
     *        ['timeout'] = (time) Explicit operation timeout
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function createRepository($params = array())
    {
        $repository = $this->extractArgument($params, 'repository');
        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Snapshot\Repository\Create $endpoint */
        $endpoint = $endpointBuilder('Snapshot\Repository\Create');
        $endpoint->setRepository($repository)
                 ->setBody($body)
                 ->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['master_timeout'] = (time) Explicit operation timeout for connection to master node
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function delete($params = array())
    {
        $repository = $this->extractArgument($params, 'repository');
        $snapshot = $this->extractArgument($params, 'snapshot');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Snapshot\Delete $endpoint */
        $endpoint = $endpointBuilder('Snapshot\Delete');
        $endpoint->setRepository($repository)
                 ->setSnapshot($snapshot)
                 ->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['master_timeout'] = (time) Explicit operation timeout for connection to master node
     *        ['timeout'] = (time) Explicit operation timeout
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function deleteRepository($params = array())
    {
        $repository = $this->extractArgument($params, 'repository');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Snapshot\Repository\Delete $endpoint */
        $endpoint = $endpointBuilder('Snapshot\Repository\Delete');
        $endpoint->setRepository($repository)
                 ->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['master_timeout'] = (time) Explicit operation timeout for connection to master node
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function get($params = array())
    {
        $repository = $this->extractArgument($params, 'repository');
        $snapshot = $this->extractArgument($params, 'snapshot');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Snapshot\Get $endpoint */
        $endpoint = $endpointBuilder('Snapshot\Get');
        $endpoint->setRepository($repository)
                 ->setSnapshot($snapshot)
                 ->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['master_timeout'] = (time) Explicit operation timeout for connection to master node
     *        ['timeout'] = (time) Explicit operation timeout
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function getRepository($params = array())
    {
        $repository = $this->extractArgument($params, 'repository');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Snapshot\Repository\Get $endpoint */
        $endpoint = $endpointBuilder('Snapshot\Repository\Get');
        $endpoint->setRepository($repository)
                 ->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['master_timeout'] = (time) Explicit operation timeout for connection to master node
     *        ['wait_for_completion'] = (bool) Should this request wait until the operation has completed before returning
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function restore($params = array())
    {
        $repository = $this->extractArgument($params, 'repository');
        $snapshot = $this->extractArgument($params, 'snapshot');
        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Snapshot\Restore $endpoint */
        $endpoint = $endpointBuilder('Snapshot\Restore');
        $endpoint->setRepository($repository)
                 ->setSnapshot($snapshot)
                 ->setParams($params)
                 ->setBody($body);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['master_timeout'] = (time) Explicit operation timeout for connection to master node
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function status($params = array())
    {
        $repository = $this->extractArgument($params, 'repository');
        $snapshot = $this->extractArgument($params, 'snapshot');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Snapshot\Status $endpoint */
        $endpoint = $endpointBuilder('Snapshot\Status');
        $endpoint->setRepository($repository)
                 ->setSnapshot($snapshot)
                 ->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['master_timeout'] = (time) Explicit operation timeout for connection to master node
     *        ['timeout'] = (time) Explicit operation timeout
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function verifyRepository($params = array())
    {
        $repository = $this->extractArgument($params, 'repository');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Snapshot\Repository\Verify $endpoint */
        $endpoint = $endpointBuilder('Snapshot\Repository\Verify');
        $endpoint->setRepository($repository)
                 ->setParams($params);

        return $this->performRequest($endpoint);
    }
}
<?php

namespace Elasticsearch\Namespaces;

/**
 * Class CatNamespace
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Namespaces\CatNamespace
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class CatNamespace extends AbstractNamespace
{
    /**
     * $params['local']          = (bool) Return local information, do not retrieve the state from master node (default: false)
     *        ['master_timeout'] = (time) Explicit operation timeout for connection to master node
     *        ['h']              = (list) Comma-separated list of column names to display
     *        ['help']           = (bool) Return help information
     *        ['v']              = (bool) Verbose mode. Display column headers
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function aliases($params = array())
    {
        $name = $this->extractArgument($params, 'name');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cat\Aliases $endpoint */
        $endpoint = $endpointBuilder('Cat\Aliases');
        $endpoint->setName($name);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['local']          = (bool) Return local information, do not retrieve the state from master node (default: false)
     *        ['master_timeout'] = (time) Explicit operation timeout for connection to master node
     *        ['h']              = (list) Comma-separated list of column names to display
     *        ['help']           = (bool) Return help information
     *        ['v']              = (bool) Verbose mode. Display column headers
     *        ['bytes']          = (enum) The unit in which to display byte values
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function allocation($params = array())
    {
        $nodeID = $this->extractArgument($params, 'node_id');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cat\Allocation $endpoint */
        $endpoint = $endpointBuilder('Cat\Allocation');
        $endpoint->setNodeID($nodeID);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['local']          = (bool) Return local information, do not retrieve the state from master node (default: false)
     *        ['master_timeout'] = (time) Explicit operation timeout for connection to master node
     *        ['h']              = (list) Comma-separated list of column names to display
     *        ['help']           = (bool) Return help information
     *        ['v']              = (bool) Verbose mode. Display column headers
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function count($params = array())
    {
        $index = $this->extractArgument($params, 'index');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cat\Count $endpoint */
        $endpoint = $endpointBuilder('Cat\Count');
        $endpoint->setIndex($index);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['local']          = (bool) Return local information, do not retrieve the state from master node (default: false)
     *        ['master_timeout'] = (time) Explicit operation timeout for connection to master node
     *        ['h']              = (list) Comma-separated list of column names to display
     *        ['help']           = (bool) Return help information
     *        ['v']              = (bool) Verbose mode. Display column headers
     *        ['ts']             = (bool) Set to false to disable timestamping
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function health($params = array())
    {
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cat\Health $endpoint */
        $endpoint = $endpointBuilder('Cat\Health');
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['help'] = (bool) Return help information
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function help($params = array())
    {
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cat\Help $endpoint */
        $endpoint = $endpointBuilder('Cat\Help');
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['local']          = (bool) Return local information, do not retrieve the state from master node (default: false)
     *        ['master_timeout'] = (time) Explicit operation timeout for connection to master node
     *        ['h']              = (list) Comma-separated list of column names to display
     *        ['help']           = (bool) Return help information
     *        ['v']              = (bool) Verbose mode. Display column headers
     *        ['bytes']          = (enum) The unit in which to display byte values
     *        ['pri']            = (bool) Set to true to return stats only for primary shards
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function indices($params = array())
    {
        $index = $this->extractArgument($params, 'index');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cat\Indices $endpoint */
        $endpoint = $endpointBuilder('Cat\Indices');
        $endpoint->setIndex($index);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['local']          = (bool) Return local information, do not retrieve the state from master node (default: false)
     *        ['master_timeout'] = (time) Explicit operation timeout for connection to master node
     *        ['h']              = (list) Comma-separated list of column names to display
     *        ['help']           = (bool) Return help information
     *        ['v']              = (bool) Verbose mode. Display column headers
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function master($params = array())
    {
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cat\Master $endpoint */
        $endpoint = $endpointBuilder('Cat\Master');
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['local']          = (bool) Return local information, do not retrieve the state from master node (default: false)
     *        ['master_timeout'] = (time) Explicit operation timeout for connection to master node
     *        ['h']              = (list) Comma-separated list of column names to display
     *        ['help']           = (bool) Return help information
     *        ['v']              = (bool) Verbose mode. Display column headers
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function nodes($params = array())
    {
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cat\Nodes $endpoint */
        $endpoint = $endpointBuilder('Cat\Nodes');
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['local']          = (bool) Return local information, do not retrieve the state from master node (default: false)
     *        ['master_timeout'] = (time) Explicit operation timeout for connection to master node
     *        ['h']              = (list) Comma-separated list of column names to display
     *        ['help']           = (bool) Return help information
     *        ['v']              = (bool) Verbose mode. Display column headers
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function nodeAttrs($params = array())
    {
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cat\NodeAttrs $endpoint */
        $endpoint = $endpointBuilder('Cat\NodeAttrs');
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['local']          = (bool) Return local information, do not retrieve the state from master node (default: false)
     *        ['master_timeout'] = (time) Explicit operation timeout for connection to master node
     *        ['h']              = (list) Comma-separated list of column names to display
     *        ['help']           = (bool) Return help information
     *        ['v']              = (bool) Verbose mode. Display column headers
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function pendingTasks($params = array())
    {
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cat\PendingTasks $endpoint */
        $endpoint = $endpointBuilder('Cat\PendingTasks');
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['local']          = (bool) Return local information, do not retrieve the state from master node (default: false)
     *        ['master_timeout'] = (time) Explicit operation timeout for connection to master node
     *        ['h']              = (list) Comma-separated list of column names to display
     *        ['help']           = (bool) Return help information
     *        ['v']              = (bool) Verbose mode. Display column headers
     *        ['bytes']          = (enum) The unit in which to display byte values
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function recovery($params = array())
    {
        $index = $this->extractArgument($params, 'index');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cat\Recovery $endpoint */
        $endpoint = $endpointBuilder('Cat\Recovery');
        $endpoint->setIndex($index);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['local']          = (bool) Return local information, do not retrieve the state from master node (default: false)
     *        ['master_timeout'] = (time) Explicit operation timeout for connection to master node
     *        ['h']              = (list) Comma-separated list of column names to display
     *        ['help']           = (bool) Return help information
     *        ['v']              = (bool) Verbose mode. Display column headers
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function repositories($params = array())
    {
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cat\Repositories $endpoint */
        $endpoint = $endpointBuilder('Cat\Repositories');
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['local']          = (bool) Return local information, do not retrieve the state from master node (default: false)
     *        ['master_timeout'] = (time) Explicit operation timeout for connection to master node
     *        ['h']              = (list) Comma-separated list of column names to display
     *        ['help']           = (bool) Return help information
     *        ['v']              = (bool) Verbose mode. Display column headers
     *        ['bytes']          = (enum) The unit in which to display byte values
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function shards($params = array())
    {
        $index = $this->extractArgument($params, 'index');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cat\Shards $endpoint */
        $endpoint = $endpointBuilder('Cat\Shards');
        $endpoint->setIndex($index);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['local']          = (bool) Return local information, do not retrieve the state from master node (default: false)
     *        ['master_timeout'] = (time) Explicit operation timeout for connection to master node
     *        ['h']              = (list) Comma-separated list of column names to display
     *        ['help']           = (bool) Return help information
     *        ['v']              = (bool) Verbose mode. Display column headers
     *        ['bytes']          = (enum) The unit in which to display byte values
     *        ['repository']     = (string) Name of repository from which to fetch the snapshot information
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function snapshots($params = array())
    {
        $repository = $this->extractArgument($params, 'repository');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cat\Snapshots $endpoint */
        $endpoint = $endpointBuilder('Cat\Snapshots');
        $endpoint->setRepository($repository);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['local']          = (bool) Return local information, do not retrieve the state from master node (default: false)
     *        ['master_timeout'] = (time) Explicit operation timeout for connection to master node
     *        ['h']              = (list) Comma-separated list of column names to display
     *        ['help']           = (bool) Return help information
     *        ['v']              = (bool) Verbose mode. Display column headers
     *        ['full_id']        = (bool) Enables displaying the complete node ids
     *        ['size']           = (enum) The multiplier in which to display values ([ "", "k", "m", "g", "t", "p" ])
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function threadPool($params = array())
    {
        $index = $this->extractArgument($params, 'index');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cat\ThreadPool $endpoint */
        $endpoint = $endpointBuilder('Cat\ThreadPool');
        $endpoint->setIndex($index);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['local']          = (bool) Return local information, do not retrieve the state from master node (default: false)
     *        ['master_timeout'] = (time) Explicit operation timeout for connection to master node
     *        ['h']              = (list) Comma-separated list of column names to display
     *        ['help']           = (bool) Return help information
     *        ['v']              = (bool) Verbose mode. Display column headers
     *        ['bytes']          = (enum) The unit in which to display byte values
     *        ['fields']         = (list) A comma-separated list of fields to return the fielddata size
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function fielddata($params = array())
    {
        $fields = $this->extractArgument($params, 'fields');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cat\Fielddata $endpoint */
        $endpoint = $endpointBuilder('Cat\Fielddata');
        $endpoint->setFields($fields);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['local']          = (bool) Return local information, do not retrieve the state from master node (default: false)
     *        ['master_timeout'] = (time) Explicit operation timeout for connection to master node
     *        ['h']              = (list) Comma-separated list of column names to display
     *        ['help']           = (bool) Return help information
     *        ['v']              = (bool) Verbose mode. Display column headers
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function plugins($params = array())
    {
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cat\Plugins $endpoint */
        $endpoint = $endpointBuilder('Cat\Plugins');
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['h']              = (list) Comma-separated list of column names to display
     *        ['help']           = (bool) Return help information
     *        ['v']              = (bool) Verbose mode. Display column headers
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function segments($params = array())
    {
        $index = $this->extractArgument($params, 'index');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cat\Segments $endpoint */
        $endpoint = $endpointBuilder('Cat\Segments');
        $endpoint->setIndex($index);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['format']         = (string) a short version of the Accept header, e.g. json, yaml
     *        ['node_id']        = (list) A comma-separated list of node IDs or names to limit the returned information; use `_local` to return information from the node you're connecting to, leave empty to get information from all nodes
     *        ['format']         = (string) a short version of the Accept header, e.g. json, yaml
     *        ['actions']        = (list) A comma-separated list of actions that should be returned. Leave empty to return all.
     *        ['detailed']       = (boolean) Return detailed task information (default: false)
     *        ['parent_node']    = (string) Return tasks with specified parent node.
     *        ['parent_task']    = (number) Return tasks with specified parent task id. Set to -1 to return all.
     *        ['h']              = (list) Comma-separated list of column names to display
     *        ['help']           = (bool) Return help information
     *        ['v']              = (bool) Verbose mode. Display column headers
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function tasks($params = array())
    {
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cat\Tasks $endpoint */
        $endpoint = $endpointBuilder('Cat\Tasks');
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['local']          = (bool) Return local information, do not retrieve the state from master node (default: false)
     *        ['master_timeout'] = (time) Explicit operation timeout for connection to master node
     *        ['h']              = (list) Comma-separated list of column names to display
     *        ['help']           = (bool) Return help information
     *        ['v']              = (bool) Verbose mode. Display column headers
     *        ['bytes']          = (enum) The unit in which to display byte values
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function templates($params = array())
    {
        $name = $this->extractArgument($params, 'name');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cat\Templates $endpoint */
        $endpoint = $endpointBuilder('Cat\Templates');
        $endpoint->setName($name)
            ->setParams($params);

        return $this->performRequest($endpoint);
    }
}
<?php

namespace Elasticsearch\Namespaces;

use Elasticsearch\Endpoints\Tasks\Cancel;
use Elasticsearch\Endpoints\Tasks\Get;

/**
 * Class TasksNamespace
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Namespaces\TasksNamespace
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class TasksNamespace extends AbstractNamespace
{
    /**
     * $params['wait_for_completion'] = (bool) Wait for the matching tasks to complete (default: false)
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function get($params = array())
    {
        $id = $this->extractArgument($params, 'task_id');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var Get $endpoint */
        $endpoint = $endpointBuilder('Tasks\Get');
        $endpoint->setTaskId($id)
            ->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['node_id'] = (list) A comma-separated list of node IDs or names to limit the returned information; use `_local` to return information from the node you're connecting to, leave empty to get information from all nodes
     *        ['actions'] = (list) A comma-separated list of actions that should be cancelled. Leave empty to cancel all.
     *        ['parent_node'] = (string) Cancel tasks with specified parent node
     *        ['parent_task'] = (string) Cancel tasks with specified parent task id (node_id:task_number). Set to -1 to cancel all.
     *        ['detailed'] = (bool) Return detailed task information (default: false)
     *        ['wait_for_completion'] = (bool) Wait for the matching tasks to complete (default: false)
     *        ['group_by'] = (enum) Group tasks by nodes or parent/child relationships
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function tasksList($params = array())
    {

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var Get $endpoint */
        $endpoint = $endpointBuilder('Tasks\TasksList');
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['node_id'] = (list) A comma-separated list of node IDs or names to limit the returned information; use `_local` to return information from the node you're connecting to, leave empty to get information from all nodes
     *        ['actions'] = (list) A comma-separated list of actions that should be cancelled. Leave empty to cancel all.
     *        ['parent_node'] = (string) Cancel tasks with specified parent node
     *        ['parent_task'] = (string) Cancel tasks with specified parent task id (node_id:task_number). Set to -1 to cancel all.
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function cancel($params = array())
    {
        $id = $this->extractArgument($params, 'id');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var Cancel $endpoint */
        $endpoint = $endpointBuilder('Tasks\Cancel');
        $endpoint->setTaskId($id)
            ->setParams($params);

        return $this->performRequest($endpoint);
    }
}
<?php

namespace Elasticsearch\Namespaces;

/**
 * Class NodesNamespace
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Namespaces\NodesNamespace
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class NodesNamespace extends AbstractNamespace
{
    /**
     * $params['fields']        = (list) A comma-separated list of fields for `fielddata` metric (supports wildcards)
     *        ['metric_family'] = (enum) Limit the information returned to a certain metric family
     *        ['metric']        = (enum) Limit the information returned for `indices` family to a specific metric
     *        ['node_id']       = (list) A comma-separated list of node IDs or names to limit the returned information; use `_local` to return information from the node you're connecting to, leave empty to get information from all nodes
     *        ['all']           = (boolean) Return all available information
     *        ['clear']         = (boolean) Reset the default level of detail
     *        ['fs']            = (boolean) Return information about the filesystem
     *        ['http']          = (boolean) Return information about HTTP
     *        ['indices']       = (boolean) Return information about indices
     *        ['jvm']           = (boolean) Return information about the JVM
     *        ['network']       = (boolean) Return information about network
     *        ['os']            = (boolean) Return information about the operating system
     *        ['process']       = (boolean) Return information about the Elasticsearch process
     *        ['thread_pool']   = (boolean) Return information about the thread pool
     *        ['transport']     = (boolean) Return information about transport
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function stats($params = array())
    {
        $nodeID = $this->extractArgument($params, 'node_id');

        $metric = $this->extractArgument($params, 'metric');

        $index_metric = $this->extractArgument($params, 'index_metric');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cluster\Nodes\Stats $endpoint */
        $endpoint = $endpointBuilder('Cluster\Nodes\Stats');
        $endpoint->setNodeID($nodeID)
                 ->setMetric($metric)
                 ->setIndexMetric($index_metric)
                 ->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['node_id']       = (list) A comma-separated list of node IDs or names to limit the returned information; use `_local` to return information from the node you're connecting to, leave empty to get information from all nodes
     *        ['metric']        = (list) A comma-separated list of metrics you wish returned. Leave empty to return all.
     *        ['flat_settings'] = (boolean) Return settings in flat format (default: false)
     *        ['human']         = (boolean) Whether to return time and byte values in human-readable format.

     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function info($params = array())
    {
        $nodeID = $this->extractArgument($params, 'node_id');
        $metric = $this->extractArgument($params, 'metric');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cluster\Nodes\Info $endpoint */
        $endpoint = $endpointBuilder('Cluster\Nodes\Info');
        $endpoint->setNodeID($nodeID)->setMetric($metric);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['node_id']   = (list) A comma-separated list of node IDs or names to limit the returned information; use `_local` to return information from the node you're connecting to, leave empty to get information from all nodes
     *        ['interval']  = (time) The interval for the second sampling of threads
     *        ['snapshots'] = (number) Number of samples of thread stacktrace (default: 10)
     *        ['threads']   = (number) Specify the number of threads to provide information for (default: 3)
     *        ['type']      = (enum) The type to sample (default: cpu)
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function hotThreads($params = array())
    {
        $nodeID = $this->extractArgument($params, 'node_id');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cluster\Nodes\HotThreads $endpoint */
        $endpoint = $endpointBuilder('Cluster\Nodes\HotThreads');
        $endpoint->setNodeID($nodeID);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['node_id'] = (list) A comma-separated list of node IDs or names to perform the operation on; use `_local` to perform the operation on the node you're connected to, leave empty to perform the operation on all nodes
     *        ['delay']   = (time) Set the delay for the operation (default: 1s)
     *        ['exit']    = (boolean) Exit the JVM as well (default: true)
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function shutdown($params = array())
    {
        $nodeID = $this->extractArgument($params, 'node_id');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cluster\Nodes\Shutdown $endpoint */
        $endpoint = $endpointBuilder('Cluster\Nodes\Shutdown');
        $endpoint->setNodeID($nodeID);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }
}
<?php

namespace Elasticsearch\Namespaces;

use Elasticsearch\Endpoints\Remote\Info;

/**
 * Class RemoteNamespace
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Namespaces\TasksNamespace
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class RemoteNamespace extends AbstractNamespace
{
    /**
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function info($params = array())
    {
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var Info $endpoint */
        $endpoint = $endpointBuilder('Remote\Info');
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }
}
<?php

namespace Elasticsearch\Namespaces;

use Elasticsearch\Endpoints\Ingest\Pipeline\Delete;
use Elasticsearch\Endpoints\Ingest\Pipeline\Get;
use Elasticsearch\Endpoints\Ingest\Pipeline\Put;
use Elasticsearch\Endpoints\Ingest\Simulate;

/**
 * Class IngestNamespace
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Namespaces\IngestNamespace
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class IngestNamespace extends AbstractNamespace
{
    /**
     * $params['master_timeout']             = (time) Explicit operation timeout for connection to master node
     *        ['timeout']                    = (time) Explicit operation timeout
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function deletePipeline($params = array())
    {
        $id = $this->extractArgument($params, 'id');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var Delete $endpoint */
        $endpoint = $endpointBuilder('Ingest\Pipeline\Delete');
        $endpoint->setID($id);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['master_timeout']             = (time) Explicit operation timeout for connection to master node
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function getPipeline($params = array())
    {
        $id = $this->extractArgument($params, 'id');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var Get $endpoint */
        $endpoint = $endpointBuilder('Ingest\Pipeline\Get');
        $endpoint->setID($id);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['master_timeout']             = (time) Explicit operation timeout for connection to master node
     *        ['timeout']                    = (time) Explicit operation timeout
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function putPipeline($params = array())
    {
        $body = $this->extractArgument($params, 'body');
        $id = $this->extractArgument($params, 'id');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var Put $endpoint */
        $endpoint = $endpointBuilder('Ingest\Pipeline\Put');
        $endpoint->setID($id)
            ->setBody($body)
            ->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['verbose'] = (bool) Verbose mode. Display data output for each processor in executed pipeline
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function simulate($params = array())
    {
        $body = $this->extractArgument($params, 'body');
        $id = $this->extractArgument($params, 'id');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var Simulate $endpoint */
        $endpoint = $endpointBuilder('Ingest\Simulate');
        $endpoint->setID($id)
            ->setBody($body)
            ->setParams($params);

        return $this->performRequest($endpoint);
    }
}
<?php

namespace Elasticsearch\Namespaces;

/**
 * Class ClusterNamespace
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Namespaces\ClusterNamespace
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class ClusterNamespace extends AbstractNamespace
{
    /**
     * $params['index']                      = (string) Limit the information returned to a specific index
     *        ['level']                      = (enum) Specify the level of detail for returned information
     *        ['local']                      = (boolean) Return local information, do not retrieve the state from master node (default: false)
     *        ['master_timeout']             = (time) Explicit operation timeout for connection to master node
     *        ['timeout']                    = (time) Explicit operation timeout
     *        ['wait_for_active_shards']     = (number) Wait until the specified number of shards is active
     *        ['wait_for_nodes']             = (number) Wait until the specified number of nodes is available
     *        ['wait_for_relocating_shards'] = (number) Wait until the specified number of relocating shards is finished
     *        ['wait_for_status']            = (enum) Wait until cluster is in a specific state
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function health($params = array())
    {
        $index = $this->extractArgument($params, 'index');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cluster\Health $endpoint */
        $endpoint = $endpointBuilder('Cluster\Health');
        $endpoint->setIndex($index);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['dry_run']         = (boolean) Simulate the operation only and return the resulting state
     *        ['filter_metadata'] = (boolean) Don't return cluster state metadata (default: false)
     *        ['body']            = (boolean) Don't return cluster state metadata (default: false)
     *        ['explain']         = (boolean) Return an explanation of why the commands can or cannot be executed
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function reroute($params = array())
    {
        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cluster\Reroute $endpoint */
        $endpoint = $endpointBuilder('Cluster\Reroute');
        $endpoint->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['filter_blocks']          = (boolean) Do not return information about blocks
     *        ['filter_index_templates'] = (boolean) Do not return information about index templates
     *        ['filter_indices']         = (list) Limit returned metadata information to specific indices
     *        ['filter_metadata']        = (boolean) Do not return information about indices metadata
     *        ['filter_nodes']           = (boolean) Do not return information about nodes
     *        ['filter_routing_table']   = (boolean) Do not return information about shard allocation (`routing_table` and `routing_nodes`)
     *        ['local']                  = (boolean) Return local information, do not retrieve the state from master node (default: false)
     *        ['master_timeout']         = (time) Specify timeout for connection to master
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function state($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $metric = $this->extractArgument($params, 'metric');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cluster\State $endpoint */
        $endpoint = $endpointBuilder('Cluster\State');
        $endpoint->setParams($params)
                 ->setIndex($index)
                 ->setMetric($metric);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['flat_settings']          = (boolean) Return settings in flat format (default: false)
     *        ['human'] = (boolean) Whether to return time and byte values in human-readable format.
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function stats($params = array())
    {
        $nodeID = $this->extractArgument($params, 'node_id');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cluster\Stats $endpoint */
        $endpoint = $endpointBuilder('Cluster\Stats');
        $endpoint->setNodeID($nodeID)
                 ->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['body'] = ()
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function putSettings($params = array())
    {
        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cluster\Settings\Put $endpoint */
        $endpoint = $endpointBuilder('Cluster\Settings\Put');
        $endpoint->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * @param array $params
     *
     * @return array
     */
    public function getSettings($params = array())
    {
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cluster\Settings\Put $endpoint */
        $endpoint = $endpointBuilder('Cluster\Settings\Get');
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['local']   = (bool) Return local information, do not retrieve the state from master node (default: false)
     *        ['master_timeout']  = (time) Specify timeout for connection to master
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function pendingTasks($params = array())
    {
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cluster\PendingTasks $endpoint */
        $endpoint = $endpointBuilder('Cluster\PendingTasks');
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['include_yes_decisions'] = (bool) Return 'YES' decisions in explanation (default: false)
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function allocationExplain($params = array())
    {
        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Cluster\AllocationExplain $endpoint */
        $endpoint = $endpointBuilder('Cluster\AllocationExplain');
        $endpoint->setBody($body)
                 ->setParams($params);

        return $this->performRequest($endpoint);
    }
}
<?php
/**
 * Class RegisteredNamespaceInterface
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Namespaces
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */

namespace Elasticsearch\Namespaces;


use Elasticsearch\Serializers\SerializerInterface;
use Elasticsearch\Transport;

interface NamespaceBuilderInterface
{
    /**
     * Returns the name of the namespace.  This is what users will call, e.g. the name
     * "foo" will be invoked by the user as `$client->foo()`
     * @return string
     */
    public function getName();

    /**
     * Returns the actual namespace object which contains your custom methods. The transport
     * and serializer objects are provided so that your namespace may do whatever custom
     * logic is required.
     *
     * @param Transport $transport
     * @param SerializerInterface $serializer
     * @return Object
     */
    public function getObject(Transport $transport, SerializerInterface $serializer);
}<?php

namespace Elasticsearch\Namespaces;

/**
 * Class IndicesNamespace
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Namespaces\IndicesNamespace
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class IndicesNamespace extends AbstractNamespace
{
    /**
     * $params['index'] = (list) A comma-separated list of indices to check (Required)
     *
     * @param $params array Associative array of parameters
     *
     * @return boolean
     */
    public function exists($params)
    {
        $index = $this->extractArgument($params, 'index');

        //manually make this verbose so we can check status code
        $params['client']['verbose'] = true;

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Exists $endpoint */
        $endpoint = $endpointBuilder('Indices\Exists');
        $endpoint->setIndex($index);
        $endpoint->setParams($params);

        return BooleanRequestWrapper::performRequest($endpoint, $this->transport);
    }

    /**
     * $params['index'] = (list) A comma-separated list of indices to check (Required)
     *        ['feature'] = (list) A comma-separated list of features to return
     *        ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['allow_no_indices']   = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['expand_wildcards']   = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *        ['local']   = (bool) Return local information, do not retrieve the state from master node (default: false)
     *
     * @param $params array Associative array of parameters
     *
     * @return bool
     */
    public function get($params)
    {
        $index = $this->extractArgument($params, 'index');
        $feature = $this->extractArgument($params, 'feature');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Get $endpoint */
        $endpoint = $endpointBuilder('Indices\Get');
        $endpoint->setIndex($index)
                 ->setFeature($feature)
                 ->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']               = (list) A comma-separated list of index names; use `_all` or empty string to perform the operation on all indices
     *        ['operation_threading'] = () TODO: ?
     *        ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['allow_no_indices']   = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['expand_wildcards']   = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function segments($params = array())
    {
        $index = $this->extractArgument($params, 'index');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Segments $endpoint */
        $endpoint = $endpointBuilder('Indices\Segments');
        $endpoint->setIndex($index);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['name']    = (string) The name of the template (Required)
     *        ['timeout'] = (time) Explicit operation timeout
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function deleteTemplate($params)
    {
        $name = $this->extractArgument($params, 'name');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Template\Delete $endpoint */
        $endpoint = $endpointBuilder('Indices\Template\Delete');
        $endpoint->setName($name);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']   = (list) A comma-separated list of indices to delete; use `_all` or empty string to delete all indices
     *        ['timeout'] = (time) Explicit operation timeout
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function delete($params = array())
    {
        $index = $this->extractArgument($params, 'index');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Delete $endpoint */
        $endpoint = $endpointBuilder('Indices\Delete');
        $endpoint->setIndex($index);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['fields']         = (boolean) A comma-separated list of fields for `fielddata` metric (supports wildcards)
     *        ['index']          = (list) A comma-separated list of index names; use `_all` or empty string to perform the operation on all indices
     *        ['indexing_types'] = (list) A comma-separated list of document types to include in the `indexing` statistics
     *        ['metric_family']  = (enum) Limit the information returned to a specific metric
     *        ['search_groups']  = (list) A comma-separated list of search groups to include in the `search` statistics
     *        ['all']            = (boolean) Return all available information
     *        ['clear']          = (boolean) Reset the default level of detail
     *        ['docs']           = (boolean) Return information about indexed and deleted documents
     *        ['fielddata']      = (boolean) Return information about field data
     *        ['filter_cache']   = (boolean) Return information about filter cache
     *        ['flush']          = (boolean) Return information about flush operations
     *        ['get']            = (boolean) Return information about get operations
     *        ['groups']         = (boolean) A comma-separated list of search groups for `search` statistics
     *        ['id_cache']       = (boolean) Return information about ID cache
     *        ['ignore_indices'] = (enum) When performed on multiple indices, allows to ignore `missing` ones
     *        ['indexing']       = (boolean) Return information about indexing operations
     *        ['merge']          = (boolean) Return information about merge operations
     *        ['refresh']        = (boolean) Return information about refresh operations
     *        ['search']         = (boolean) Return information about search operations; use the `groups` parameter to include information for specific search groups
     *        ['store']          = (boolean) Return information about the size of the index
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function stats($params = array())
    {
        $metric = $this->extractArgument($params, 'metric');

        $index = $this->extractArgument($params, 'index');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Stats $endpoint */
        $endpoint = $endpointBuilder('Indices\Stats');
        $endpoint->setIndex($index)
                 ->setMetric($metric);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index'] = (list) A comma-separated list of index names; use `_all` or empty string to perform the operation on all indices
     *        ['body']  = (list) A comma-separated list of index names; use `_all` or empty string to perform the operation on all indices
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function putSettings($params = array())
    {
        $index = $this->extractArgument($params, 'index');

        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Settings\Put $endpoint */
        $endpoint = $endpointBuilder('Indices\Settings\Put');
        $endpoint->setIndex($index)
                 ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']              = (list) A comma-separated list of index names; use `_all` or empty string for all indices
     *        ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['allow_no_indices']   = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['expand_wildcards']   = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function snapshotIndex($params = array())
    {
        $index = $this->extractArgument($params, 'index');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Gateway\Snapshot $endpoint */
        $endpoint = $endpointBuilder('Indices\Gateway\Snapshot');
        $endpoint->setIndex($index);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']          = (string) The name of the source index to shrink
     *        ['target']         = (string) The name of the target index to shrink into
     *        ['timeout']        = (time) Explicit operation timeout
     *        ['master_timeout'] = (time) Specify timeout for connection to master
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function shrink($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $target = $this->extractArgument($params, 'target');
        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Shrink $endpoint */
        $endpoint = $endpointBuilder('Indices\Shrink');
        $endpoint->setIndex($index)
                 ->setTarget($target)
                 ->setBody($body);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index'] = (list) A comma-separated list of index names; use `_all` or empty string for all indices
     *        ['type']  = (list) A comma-separated list of document types
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function getMapping($params = array())
    {
        $index = $this->extractArgument($params, 'index');

        $type = $this->extractArgument($params, 'type');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Mapping\Get $endpoint */
        $endpoint = $endpointBuilder('Indices\Mapping\Get');
        $endpoint->setIndex($index)
                 ->setType($type);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']            = (list) A comma-separated list of index names; use `_all` or empty string for all indices
     *        ['type']             = (list) A comma-separated list of document types
     *        ['field']            = (list) A comma-separated list of document fields
     *        ['include_defaults'] = (bool) specifies default mapping values should be returned
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function getFieldMapping($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $fields = $this->extractArgument($params, 'fields');

        if (!isset($fields)) {
            $fields = $this->extractArgument($params, 'field');
        }


        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Mapping\GetField $endpoint */
        $endpoint = $endpointBuilder('Indices\Mapping\GetField');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setFields($fields);

        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']              = (list) A comma-separated list of index names; use `_all` or empty string for all indices
     *        ['force']              = (boolean) TODO: ?
     *        ['full']               = (boolean) TODO: ?
     *        ['refresh']            = (boolean) Refresh the index after performing the operation
     *        ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['allow_no_indices']   = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['expand_wildcards']   = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function flush($params = array())
    {
        $index = $this->extractArgument($params, 'index');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Flush $endpoint */
        $endpoint = $endpointBuilder('Indices\Flush');
        $endpoint->setIndex($index);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']              = (list) A comma-separated list of index names; use `_all` or empty string for all indices
     *        ['force']              = (boolean) TODO: ?
     *        ['full']               = (boolean) TODO: ?
     *        ['refresh']            = (boolean) Refresh the index after performing the operation
     *        ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['allow_no_indices']   = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['expand_wildcards']   = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function flushSynced($params = array())
    {
        $index = $this->extractArgument($params, 'index');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Flush $endpoint */
        $endpoint = $endpointBuilder('Indices\Flush');
        $endpoint->setIndex($index);
        $endpoint->setParams($params);
        $endpoint->setSynced(true);

        return $this->performRequest($endpoint);
    }


    /**
     * $params['index']               = (list) A comma-separated list of index names; use `_all` or empty string to perform the operation on all indices
     *        ['operation_threading'] = () TODO: ?
     *        ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['allow_no_indices']   = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['expand_wildcards']   = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function refresh($params = array())
    {
        $index = $this->extractArgument($params, 'index');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Refresh $endpoint */
        $endpoint = $endpointBuilder('Indices\Refresh');
        $endpoint->setIndex($index);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']       = (list) A comma-separated list of index names; use `_all` or empty string for all indices
     *        ['detailed']    = (bool) Whether to display detailed information about shard recovery
     *        ['active_only'] = (bool) Display only those recoveries that are currently on-going
     *        ['human']       = (bool) Whether to return time and byte values in human-readable format.
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function recovery($params = array())
    {
        $index = $this->extractArgument($params, 'index');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Flush $endpoint */
        $endpoint = $endpointBuilder('Indices\Recovery');
        $endpoint->setIndex($index);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']              = (list) A comma-separated list of index names; use `_all` to check the types across all indices (Required)
     *        ['type']               = (list) A comma-separated list of document types to check (Required)
     *        ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['allow_no_indices']   = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['expand_wildcards']   = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *
     * @param $params array Associative array of parameters
     *
     * @return boolean
     */
    public function existsType($params)
    {
        $index = $this->extractArgument($params, 'index');

        $type = $this->extractArgument($params, 'type');

        //manually make this verbose so we can check status code
        $params['client']['verbose'] = true;

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Type\Exists $endpoint */
        $endpoint = $endpointBuilder('Indices\Type\Exists');
        $endpoint->setIndex($index)
                 ->setType($type);
        $endpoint->setParams($params);

        return BooleanRequestWrapper::performRequest($endpoint, $this->transport);
    }

    /**
     * $params['index']   = (string) The name of the index with an alias
     *        ['name']    = (string) The name of the alias to be created or updated
     *        ['timeout'] = (time) Explicit timestamp for the document
     *        ['body']    = (time) Explicit timestamp for the document
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function putAlias($params = array())
    {
        $index = $this->extractArgument($params, 'index');

        $name = $this->extractArgument($params, 'name');

        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Alias\Put $endpoint */
        $endpoint = $endpointBuilder('Indices\Alias\Put');
        $endpoint->setIndex($index)
                 ->setName($name)
                 ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['name']    = (string) The name of the template (Required)
     *        ['order']   = (number) The order for this template when merging multiple matching ones (higher numbers are merged later, overriding the lower numbers)
     *        ['timeout'] = (time) Explicit operation timeout
     *        ['body']    = (time) Explicit operation timeout
     *        ['create']  = (bool) Whether the index template should only be added if new or can also replace an existing one
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function putTemplate($params)
    {
        $name = $this->extractArgument($params, 'name');

        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Template\Put $endpoint */
        $endpoint = $endpointBuilder('Indices\Template\Put');
        $endpoint->setName($name)
                 ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']               = (list) A comma-separated list of index names to restrict the operation; use `_all` or empty string to perform the operation on all indices
     *        ['type']                = (list) A comma-separated list of document types to restrict the operation; leave empty to perform the operation on all types
     *        ['explain']             = (boolean) Return detailed information about the error
     *        ['ignore_indices']      = (enum) When performed on multiple indices, allows to ignore `missing` ones
     *        ['operation_threading'] = () TODO: ?
     *        ['source']              = (string) The URL-encoded query definition (instead of using the request body)
     *        ['body']                = (string) The URL-encoded query definition (instead of using the request body)
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function validateQuery($params = array())
    {
        $index = $this->extractArgument($params, 'index');

        $type = $this->extractArgument($params, 'type');

        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Validate\Query $endpoint */
        $endpoint = $endpointBuilder('Indices\Validate\Query');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['name']           = (list) A comma-separated list of alias names to return (Required)
     *        ['index']          = (list) A comma-separated list of index names to filter aliases
     *        ['ignore_indices'] = (enum) When performed on multiple indices, allows to ignore `missing` ones
     *        ['name']           = (list) A comma-separated list of alias names to return
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function getAlias($params)
    {
        $index = $this->extractArgument($params, 'index');

        $name = $this->extractArgument($params, 'name');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Alias\Get $endpoint */
        $endpoint = $endpointBuilder('Indices\Alias\Get');
        $endpoint->setIndex($index)
                 ->setName($name);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']            = (list) A comma-separated list of index names; use `_all` to perform the operation on all indices (Required)
     *        ['type']             = (string) The name of the document type
     *        ['ignore_conflicts'] = (boolean) Specify whether to ignore conflicts while updating the mapping (default: false)
     *        ['timeout']          = (time) Explicit operation timeout
     *        ['body']             = (time) Explicit operation timeout
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function putMapping($params)
    {
        $index = $this->extractArgument($params, 'index');

        $type = $this->extractArgument($params, 'type');

        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Mapping\Put $endpoint */
        $endpoint = $endpointBuilder('Indices\Mapping\Put');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index'] = (list) A comma-separated list of index names; use `_all` for all indices (Required)
     *        ['type']  = (string) The name of the document type to delete (Required)
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function deleteMapping($params)
    {
        $index = $this->extractArgument($params, 'index');

        $type = $this->extractArgument($params, 'type');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Mapping\Delete $endpoint */
        $endpoint = $endpointBuilder('Indices\Mapping\Delete');
        $endpoint->setIndex($index)
                 ->setType($type);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['name'] = (string) The name of the template (Required)
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function getTemplate($params)
    {
        $name = $this->extractArgument($params, 'name');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Template\Get $endpoint */
        $endpoint = $endpointBuilder('Indices\Template\Get');
        $endpoint->setName($name);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['name'] = (string) The name of the template (Required)
     *
     * @param $params array Associative array of parameters
     *
     * @return boolean
     */
    public function existsTemplate($params)
    {
        $name = $this->extractArgument($params, 'name');

        //manually make this verbose so we can check status code
        $params['client']['verbose'] = true;

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Template\Exists $endpoint */
        $endpoint = $endpointBuilder('Indices\Template\Exists');
        $endpoint->setName($name);
        $endpoint->setParams($params);

        return BooleanRequestWrapper::performRequest($endpoint, $this->transport);
    }

    /**
     * $params['index']   = (string) The name of the index (Required)
     *        ['timeout'] = (time) Explicit operation timeout
     *        ['body']    = (time) Explicit operation timeout
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function create($params)
    {
        $index = $this->extractArgument($params, 'index');

        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Create $endpoint */
        $endpoint = $endpointBuilder('Indices\Create');
        $endpoint->setIndex($index)
                 ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']                = (list) A comma-separated list of index names; use `_all` or empty string to perform the operation on all indices
     *        ['flush']                = (boolean) Specify whether the index should be flushed after performing the operation (default: true)
     *        ['max_num_segments']     = (number) The number of segments the index should be merged into (default: dynamic)
     *        ['only_expunge_deletes'] = (boolean) Specify whether the operation should only expunge deleted documents
     *        ['operation_threading']  = () TODO: ?
     *        ['refresh']              = (boolean) Specify whether the index should be refreshed after performing the operation (default: true)
     *        ['wait_for_merge']       = (boolean) Specify whether the request should block until the merge process is finished (default: true)
     *        ['ignore_unavailable']   = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['allow_no_indices']     = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['expand_wildcards']     = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function forceMerge($params = array())
    {
        $index = $this->extractArgument($params, 'index');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\ForceMerge $endpoint */
        $endpoint = $endpointBuilder('Indices\ForceMerge');
        $endpoint->setIndex($index);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']   = (string) The name of the index with an alias (Required)
     *        ['name']    = (string) The name of the alias to be deleted (Required)
     *        ['timeout'] = (time) Explicit timestamp for the document
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function deleteAlias($params)
    {
        $index = $this->extractArgument($params, 'index');

        $name = $this->extractArgument($params, 'name');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Alias\Delete $endpoint */
        $endpoint = $endpointBuilder('Indices\Alias\Delete');
        $endpoint->setIndex($index)
                 ->setName($name);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']   = (string) The name of the index (Required)
     *        ['timeout'] = (time) Explicit operation timeout
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function open($params)
    {
        $index = $this->extractArgument($params, 'index');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Open $endpoint */
        $endpoint = $endpointBuilder('Indices\Open');
        $endpoint->setIndex($index);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']        = (string) The name of the index to scope the operation
     *        ['analyzer']     = (string) The name of the analyzer to use
     *        ['field']        = (string) Use the analyzer configured for this field (instead of passing the analyzer name)
     *        ['filter']       = (list) A comma-separated list of filters to use for the analysis
     *        ['prefer_local'] = (boolean) With `true`, specify that a local shard should be used if available, with `false`, use a random shard (default: true)
     *        ['text']         = (string) The text on which the analysis should be performed (when request body is not used)
     *        ['tokenizer']    = (string) The name of the tokenizer to use for the analysis
     *        ['format']       = (enum) Format of the output
     *        ['body']         = (enum) Format of the output
     *        ['char_filter']  = (list) A comma-separated list of character filters to use for the analysis
     *        ['explain']      = (bool) With `true`, outputs more advanced details. (default: false)
     *        ['attributes']   = (list) A comma-separated list of token attributes to output, this parameter works only with `explain=true`
     *        ['format']       = (enum) Format of the output (["detailed", "text"])
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function analyze($params = array())
    {
        $index = $this->extractArgument($params, 'index');

        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Analyze $endpoint */
        $endpoint = $endpointBuilder('Indices\Analyze');
        $endpoint->setIndex($index)
                 ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']              = (list) A comma-separated list of index name to limit the operation
     *        ['field_data']         = (boolean) Clear field data
     *        ['fielddata']          = (boolean) Clear field data
     *        ['fields']             = (list) A comma-separated list of fields to clear when using the `field_data` parameter (default: all)
     *        ['filter']             = (boolean) Clear filter caches
     *        ['filter_cache']       = (boolean) Clear filter caches
     *        ['filter_keys']        = (boolean) A comma-separated list of keys to clear when using the `filter_cache` parameter (default: all)
     *        ['id']                 = (boolean) Clear ID caches for parent/child
     *        ['id_cache']           = (boolean) Clear ID caches for parent/child
     *        ['recycler']           = (boolean) Clear the recycler cache
     *        ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['allow_no_indices']   = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['expand_wildcards']   = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function clearCache($params = array())
    {
        $index = $this->extractArgument($params, 'index');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Cache\Clear $endpoint */
        $endpoint = $endpointBuilder('Indices\Cache\Clear');
        $endpoint->setIndex($index);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']   = (list) A comma-separated list of index names to filter aliases
     *        ['timeout'] = (time) Explicit timestamp for the document
     *        ['body']    = (time) Explicit timestamp for the document
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function updateAliases($params = array())
    {
        $index = $this->extractArgument($params, 'index');

        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Aliases\Update $endpoint */
        $endpoint = $endpointBuilder('Indices\Aliases\Update');
        $endpoint->setIndex($index)
                 ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['local']   = (bool) Return local information, do not retrieve the state from master node (default: false)
     *        ['timeout'] = (time) Explicit timestamp for the document
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function getAliases($params = array())
    {
        $index = $this->extractArgument($params, 'index');

        $name = $this->extractArgument($params, 'name');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Aliases\Get $endpoint */
        $endpoint = $endpointBuilder('Indices\Aliases\Get');
        $endpoint->setIndex($index)
                 ->setName($name);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['name']               = (list) A comma-separated list of alias names to return (Required)
     *        ['index']              = (list) A comma-separated list of index names to filter aliases
     *        ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['allow_no_indices']   = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['expand_wildcards']   = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *
     * @param $params array Associative array of parameters
     *
     * @return boolean
     */
    public function existsAlias($params)
    {
        $index = $this->extractArgument($params, 'index');

        $name = $this->extractArgument($params, 'name');

        //manually make this verbose so we can check status code
        $params['client']['verbose'] = true;

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Alias\Exists $endpoint */
        $endpoint = $endpointBuilder('Indices\Alias\Exists');
        $endpoint->setIndex($index)
                 ->setName($name);
        $endpoint->setParams($params);

        return BooleanRequestWrapper::performRequest($endpoint, $this->transport);
    }

    /**
     * $params['index']               = (list) A comma-separated list of index names; use `_all` or empty string to perform the operation on all indices
     *        ['ignore_indices']      = (enum) When performed on multiple indices, allows to ignore `missing` ones
     *        ['operation_threading'] = () TODO: ?
     *        ['recovery']            = (boolean) Return information about shard recovery
     *        ['snapshot']            = (boolean) TODO: ?
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function status($params = array())
    {
        $index = $this->extractArgument($params, 'index');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Status $endpoint */
        $endpoint = $endpointBuilder('Indices\Status');
        $endpoint->setIndex($index);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index'] = (list) A comma-separated list of index names; use `_all` or empty string to perform the operation on all indices
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function getSettings($params = array())
    {
        $index = $this->extractArgument($params, 'index');

        $name = $this->extractArgument($params, 'name');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Settings\Get $endpoint */
        $endpoint = $endpointBuilder('Indices\Settings\Get');
        $endpoint->setIndex($index)
                 ->setName($name);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']   = (string) The name of the index (Required)
     *        ['timeout'] = (time) Explicit operation timeout
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function close($params)
    {
        $index = $this->extractArgument($params, 'index');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Close $endpoint */
        $endpoint = $endpointBuilder('Indices\Close');
        $endpoint->setIndex($index);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']   = (string) The name of the index
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function seal($params)
    {
        $index = $this->extractArgument($params, 'index');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Seal $endpoint */
        $endpoint = $endpointBuilder('Indices\Seal');
        $endpoint->setIndex($index);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']              = (list) A comma-separated list of index names; use `_all` or empty string for all indices
     *        ['wait_for_completion']= (boolean) Specify whether the request should block until the all segments are upgraded (default: false)
     *        ['only_ancient_segments'] = (boolean) If true, only ancient (an older Lucene major release) segments will be upgraded
     *        ['refresh']            = (boolean) Refresh the index after performing the operation
     *        ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['allow_no_indices']   = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['expand_wildcards']   = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function upgrade($params = array())
    {
        $index = $this->extractArgument($params, 'index');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Upgrade\Post $endpoint */
        $endpoint = $endpointBuilder('Indices\Upgrade\Post');
        $endpoint->setIndex($index);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']              = (list) A comma-separated list of index names; use `_all` or empty string for all indices
     *        ['wait_for_completion']= (boolean) Specify whether the request should block until the all segments are upgraded (default: false)
     *        ['only_ancient_segments'] = (boolean) If true, only ancient (an older Lucene major release) segments will be upgraded
     *        ['refresh']            = (boolean) Refresh the index after performing the operation
     *        ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['allow_no_indices']   = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['expand_wildcards']   = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function getUpgrade($params = array())
    {
        $index = $this->extractArgument($params, 'index');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Upgrade\Get $endpoint */
        $endpoint = $endpointBuilder('Indices\Upgrade\Get');
        $endpoint->setIndex($index);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']   = (string) A comma-separated list of index names; use `_all` or empty string to perform the operation on all indices
     *        ['status']   = (list) A comma-separated list of statuses used to filter on shards to get store information for
     *        ['ignore_unavailable'] = (boolean) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['allow_no_indices'] = (boolean) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['expand_wildcards'] = (boolean) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *        ['operation_threading']
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function shardStores($params)
    {
        $index = $this->extractArgument($params, 'index');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\ShardStores $endpoint */
        $endpoint = $endpointBuilder('Indices\ShardStores');
        $endpoint->setIndex($index);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['newIndex']       = (string) The name of the rollover index
     *        ['alias']          = (string) The name of the alias to rollover
     *        ['timeout']        = (time) Explicit operation timeout
     *        ['master_timeout'] = (time) Specify timeout for connection to master
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function rollover($params)
    {
        $newIndex = $this->extractArgument($params, 'newIndex');
        $alias = $this->extractArgument($params, 'alias');
        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Indices\Rollover $endpoint */
        $endpoint = $endpointBuilder('Indices\Rollover');
        $endpoint->setNewIndex($newIndex)
            ->setAlias($alias)
            ->setParams($params)
            ->setBody($body);

        return $this->performRequest($endpoint);
    }
}
<?php

namespace Elasticsearch\Namespaces;

use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Transport;

/**
 * Class AbstractNamespace
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Namespaces
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
abstract class AbstractNamespace
{
    /** @var \Elasticsearch\Transport  */
    protected $transport;

    /** @var  callback */
    protected $endpoints;

    /**
     * Abstract constructor
     *
     * @param Transport $transport Transport object
     * @param $endpoints
     */
    public function __construct($transport, $endpoints)
    {
        $this->transport = $transport;
        $this->endpoints = $endpoints;
    }

    /**
     * @param array $params
     * @param string $arg
     *
     * @return null|mixed
     */
    public function extractArgument(&$params, $arg)
    {
        if (is_object($params) === true) {
            $params = (array) $params;
        }

        if (array_key_exists($arg, $params) === true) {
            $val = $params[$arg];
            unset($params[$arg]);

            return $val;
        } else {
            return null;
        }
    }

    /**
     * @param $endpoint AbstractEndpoint
     *
     * @throws \Exception
     * @return array
     */
    protected function performRequest(AbstractEndpoint $endpoint)
    {
        $response = $this->transport->performRequest(
            $endpoint->getMethod(),
            $endpoint->getURI(),
            $endpoint->getParams(),
            $endpoint->getBody(),
            $endpoint->getOptions()
        );

        return $this->transport->resultOrFuture($response, $endpoint->getOptions());
    }
}
<?php

namespace Elasticsearch\Connections;

use Elasticsearch\Common\Exceptions\AlreadyExpiredException;
use Elasticsearch\Common\Exceptions\BadRequest400Exception;
use Elasticsearch\Common\Exceptions\Conflict409Exception;
use Elasticsearch\Common\Exceptions\Curl\CouldNotConnectToHost;
use Elasticsearch\Common\Exceptions\Curl\CouldNotResolveHostException;
use Elasticsearch\Common\Exceptions\Curl\OperationTimeoutException;
use Elasticsearch\Common\Exceptions\Forbidden403Exception;
use Elasticsearch\Common\Exceptions\MaxRetriesException;
use Elasticsearch\Common\Exceptions\Missing404Exception;
use Elasticsearch\Common\Exceptions\NoDocumentsToGetException;
use Elasticsearch\Common\Exceptions\NoShardAvailableException;
use Elasticsearch\Common\Exceptions\RequestTimeout408Exception;
use Elasticsearch\Common\Exceptions\RoutingMissingException;
use Elasticsearch\Common\Exceptions\ScriptLangNotSupportedException;
use Elasticsearch\Common\Exceptions\ServerErrorResponseException;
use Elasticsearch\Common\Exceptions\TransportException;
use Elasticsearch\Serializers\SerializerInterface;
use Elasticsearch\Transport;
use GuzzleHttp\Ring\Core;
use GuzzleHttp\Ring\Exception\ConnectException;
use GuzzleHttp\Ring\Exception\RingException;
use Psr\Log\LoggerInterface;

/**
 * Class AbstractConnection
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Connections
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Connection implements ConnectionInterface
{
    /** @var  callable */
    protected $handler;

    /** @var SerializerInterface */
    protected $serializer;

    /**
     * @var string
     */
    protected $transportSchema = 'http';    // TODO depreciate this default

    /**
     * @var string
     */
    protected $host;

    /**
     * @var string || null
     */
    protected $path;

    /**
     * @var LoggerInterface
     */
    protected $log;

    /**
     * @var LoggerInterface
     */
    protected $trace;

    /**
     * @var array
     */
    protected $connectionParams;

    /** @var  array */
    protected $headers = [];

    /** @var bool  */
    protected $isAlive = false;

    /** @var float  */
    private $pingTimeout = 1;    //TODO expose this

    /** @var int  */
    private $lastPing = 0;

    /** @var int  */
    private $failedPings = 0;

    private $lastRequest = array();

    /**
     * Constructor
     *
     * @param $handler
     * @param array $hostDetails
     * @param array $connectionParams Array of connection-specific parameters
     * @param \Elasticsearch\Serializers\SerializerInterface $serializer
     * @param \Psr\Log\LoggerInterface $log              Logger object
     * @param \Psr\Log\LoggerInterface $trace
     */
    public function __construct($handler, $hostDetails, $connectionParams,
                                SerializerInterface $serializer, LoggerInterface $log, LoggerInterface $trace)
    {
        if (isset($hostDetails['port']) !== true) {
            $hostDetails['port'] = 9200;
        }

        if (isset($hostDetails['scheme'])) {
            $this->transportSchema = $hostDetails['scheme'];
        }

        if (isset($hostDetails['user']) && isset($hostDetails['pass'])) {
            $connectionParams['client']['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_BASIC;
            $connectionParams['client']['curl'][CURLOPT_USERPWD] = $hostDetails['user'].':'.$hostDetails['pass'];
        }

        if (isset($connectionParams['client']['headers']) === true) {
            $this->headers = $connectionParams['client']['headers'];
            unset($connectionParams['client']['headers']);
        }

        $host = $hostDetails['host'].':'.$hostDetails['port'];
        $path = null;
        if (isset($hostDetails['path']) === true) {
            $path = $hostDetails['path'];
        }
        $this->host             = $host;
        $this->path             = $path;
        $this->log              = $log;
        $this->trace            = $trace;
        $this->connectionParams = $connectionParams;
        $this->serializer       = $serializer;

        $this->handler = $this->wrapHandler($handler, $log, $trace);
    }

    /**
     * @param $method
     * @param $uri
     * @param null $params
     * @param null $body
     * @param array $options
     * @param \Elasticsearch\Transport $transport
     * @return mixed
     */
    public function performRequest($method, $uri, $params = null, $body = null, $options = [], Transport $transport = null)
    {
        if (isset($body) === true) {
            $body = $this->serializer->serialize($body);
        }

        $request = [
            'http_method' => $method,
            'scheme'      => $this->transportSchema,
            'uri'         => $this->getURI($uri, $params),
            'body'        => $body,
            'headers'     => array_merge([
                'Host'  => [$this->host]
            ], $this->headers)
        ];

        $request = array_replace_recursive($request, $this->connectionParams, $options);

        // RingPHP does not like if client is empty
        if (empty($request['client'])) {
            unset($request['client']);
        }

        $handler = $this->handler;
        $future = $handler($request, $this, $transport, $options);

        return $future;
    }

    /** @return string */
    public function getTransportSchema()
    {
        return $this->transportSchema;
    }

    /** @return array */
    public function getLastRequestInfo()
    {
        return $this->lastRequest;
    }

    private function wrapHandler(callable $handler, LoggerInterface $logger, LoggerInterface $tracer)
    {
        return function (array $request, Connection $connection, Transport $transport = null, $options) use ($handler, $logger, $tracer) {

            $this->lastRequest = [];
            $this->lastRequest['request'] = $request;

            // Send the request using the wrapped handler.
            $response =  Core::proxy($handler($request), function ($response) use ($connection, $transport, $logger, $tracer, $request, $options) {

                $this->lastRequest['response'] = $response;

                if (isset($response['error']) === true) {
                    if ($response['error'] instanceof ConnectException || $response['error'] instanceof RingException) {
                        $this->log->warning("Curl exception encountered.");

                        $exception = $this->getCurlRetryException($request, $response);

                        $this->logRequestFail(
                            $request['http_method'],
                            $response['effective_url'],
                            $request['body'],
                            $request['headers'],
                            $response['status'],
                            $response['body'],
                            $response['transfer_stats']['total_time'],
                            $exception
                        );

                        $node = $connection->getHost();
                        $this->log->warning("Marking node $node dead.");
                        $connection->markDead();

                        // If the transport has not been set, we are inside a Ping or Sniff,
                        // so we don't want to retrigger retries anyway.
                        //
                        // TODO this could be handled better, but we are limited because connectionpools do not
                        // have access to Transport.  Architecturally, all of this needs to be refactored
                        if (isset($transport) === true) {
                            $transport->connectionPool->scheduleCheck();

                            $neverRetry = isset($request['client']['never_retry']) ? $request['client']['never_retry'] : false;
                            $shouldRetry = $transport->shouldRetry($request);
                            $shouldRetryText = ($shouldRetry) ? 'true' : 'false';

                            $this->log->warning("Retries left? $shouldRetryText");
                            if ($shouldRetry && !$neverRetry) {
                                return $transport->performRequest(
                                    $request['http_method'],
                                    $request['uri'],
                                    [],
                                    $request['body'],
                                    $options
                                );
                            }
                        }

                        $this->log->warning("Out of retries, throwing exception from $node");
                        // Only throw if we run out of retries
                        throw $exception;
                    } else {
                        // Something went seriously wrong, bail
                        $exception = new TransportException($response['error']->getMessage());
                        $this->logRequestFail(
                            $request['http_method'],
                            $response['effective_url'],
                            $request['body'],
                            $request['headers'],
                            $response['status'],
                            $response['body'],
                            $response['transfer_stats']['total_time'],
                            $exception
                        );
                        throw $exception;
                    }
                } else {
                    $connection->markAlive();

                    if (isset($response['body']) === true) {
                        $response['body'] = stream_get_contents($response['body']);
                        $this->lastRequest['response']['body'] = $response['body'];
                    }

                    if ($response['status'] >= 400 && $response['status'] < 500) {
                        $ignore = isset($request['client']['ignore']) ? $request['client']['ignore'] : [];
                        $this->process4xxError($request, $response, $ignore);
                    } elseif ($response['status'] >= 500) {
                        $ignore = isset($request['client']['ignore']) ? $request['client']['ignore'] : [];
                        $this->process5xxError($request, $response, $ignore);
                    }

                    // No error, deserialize
                    $response['body'] = $this->serializer->deserialize($response['body'], $response['transfer_stats']);
                }
                $this->logRequestSuccess(
                    $request['http_method'],
                    $response['effective_url'],
                    $request['body'],
                    $request['headers'],
                    $response['status'],
                    $response['body'],
                    $response['transfer_stats']['total_time']
                );

                return isset($request['client']['verbose']) && $request['client']['verbose'] === true ? $response : $response['body'];

            });

            return $response;
        };
    }

    /**
     * @param string $uri
     * @param array $params
     *
     * @return string
     */
    private function getURI($uri, $params)
    {
        if (isset($params) === true && !empty($params)) {
            array_walk($params, function (&$value, &$key) {
                if ($value === true) {
                    $value = 'true';
                } else if ($value === false) {
                    $value = 'false';
                }
            });

            $uri .= '?' . http_build_query($params);
        }

        if ($this->path !== null) {
            $uri = $this->path . $uri;
        }

        return $uri;
    }

    /**
     * Log a successful request
     *
     * @param string $method
     * @param string $fullURI
     * @param string $body
     * @param array  $headers
     * @param string $statusCode
     * @param string $response
     * @param string $duration
     *
     * @return void
     */
    public function logRequestSuccess($method, $fullURI, $body, $headers, $statusCode, $response, $duration)
    {
        $this->log->debug('Request Body', array($body));
        $this->log->info(
            'Request Success:',
            array(
                'method'    => $method,
                'uri'       => $fullURI,
                'headers'   => $headers,
                'HTTP code' => $statusCode,
                'duration'  => $duration,
            )
        );
        $this->log->debug('Response', array($response));

        // Build the curl command for Trace.
        $curlCommand = $this->buildCurlCommand($method, $fullURI, $body);
        $this->trace->info($curlCommand);
        $this->trace->debug(
            'Response:',
            array(
                'response'  => $response,
                'method'    => $method,
                'uri'       => $fullURI,
                'HTTP code' => $statusCode,
                'duration'  => $duration,
            )
        );
    }

    /**
     * Log a a failed request
     *
     * @param string $method
     * @param string $fullURI
     * @param string $body
     * @param array $headers
     * @param null|string $statusCode
     * @param null|string $response
     * @param string $duration
     * @param \Exception|null $exception
     *
     * @return void
     */
    public function logRequestFail($method, $fullURI, $body, $headers, $statusCode, $response, $duration, \Exception $exception)
    {
        $this->log->debug('Request Body', array($body));
        $this->log->warning(
            'Request Failure:',
            array(
                'method'    => $method,
                'uri'       => $fullURI,
                'headers'   => $headers,
                'HTTP code' => $statusCode,
                'duration'  => $duration,
                'error'     => $exception->getMessage(),
            )
        );
        $this->log->warning('Response', array($response));

        // Build the curl command for Trace.
        $curlCommand = $this->buildCurlCommand($method, $fullURI, $body);
        $this->trace->info($curlCommand);
        $this->trace->debug(
            'Response:',
            array(
                'response'  => $response,
                'method'    => $method,
                'uri'       => $fullURI,
                'HTTP code' => $statusCode,
                'duration'  => $duration,
            )
        );
    }

    /**
     * @return bool
     */
    public function ping()
    {
        $options = [
            'client' => [
                'timeout' => $this->pingTimeout,
                'never_retry' => true,
                'verbose' => true
            ]
        ];
        try {
            $response = $this->performRequest('HEAD', '/', null, null, $options);
            $response = $response->wait();
        } catch (TransportException $exception) {
            $this->markDead();

            return false;
        }

        if ($response['status'] === 200) {
            $this->markAlive();

            return true;
        } else {
            $this->markDead();

            return false;
        }
    }

    /**
     * @return array
     */
    public function sniff()
    {
        $options = [
            'client' => [
                'timeout' => $this->pingTimeout,
                'never_retry' => true
            ]
        ];

        return $this->performRequest('GET', '/_nodes/', null, null, $options);
    }

    /**
     * @return bool
     */
    public function isAlive()
    {
        return $this->isAlive;
    }

    public function markAlive()
    {
        $this->failedPings = 0;
        $this->isAlive = true;
        $this->lastPing = time();
    }

    public function markDead()
    {
        $this->isAlive = false;
        $this->failedPings += 1;
        $this->lastPing = time();
    }

    /**
     * @return int
     */
    public function getLastPing()
    {
        return $this->lastPing;
    }

    /**
     * @return int
     */
    public function getPingFailures()
    {
        return $this->failedPings;
    }

    /**
     * @return string
     */
    public function getHost()
    {
        return $this->host;
    }

    /**
     * @return null|string
     */
    public function getUserPass()
    {
        if (isset($this->connectionParams['client']['curl'][CURLOPT_USERPWD]) === true) {
            return $this->connectionParams['client']['curl'][CURLOPT_USERPWD];
        }
        return null;
    }

    /**
     * @return null|string
     */
    public function getPath()
    {
        return $this->path;
    }

    /**
     * @param $request
     * @param $response
     * @return \Elasticsearch\Common\Exceptions\Curl\CouldNotConnectToHost|\Elasticsearch\Common\Exceptions\Curl\CouldNotResolveHostException|\Elasticsearch\Common\Exceptions\Curl\OperationTimeoutException|\Elasticsearch\Common\Exceptions\MaxRetriesException
     */
    protected function getCurlRetryException($request, $response)
    {
        $exception = null;
        $message = $response['error']->getMessage();
        $exception = new MaxRetriesException($message);
        switch ($response['curl']['errno']) {
            case 6:
                $exception = new CouldNotResolveHostException($message, null, $exception);
                break;
            case 7:
                $exception = new CouldNotConnectToHost($message, null, $exception);
                break;
            case 28:
                $exception = new OperationTimeoutException($message, null, $exception);
                break;
        }

        return $exception;
    }

    /**
     * Construct a string cURL command
     *
     * @param string $method HTTP method
     * @param string $uri    Full URI of request
     * @param string $body   Request body
     *
     * @return string
     */
    private function buildCurlCommand($method, $uri, $body)
    {
        if (strpos($uri, '?') === false) {
            $uri .= '?pretty=true';
        } else {
            str_replace('?', '?pretty=true', $uri);
        }

        $curlCommand = 'curl -X' . strtoupper($method);
        $curlCommand .= " '" . $uri . "'";

        if (isset($body) === true && $body !== '') {
            $curlCommand .= " -d '" . $body . "'";
        }

        return $curlCommand;
    }

    /**
     * @param $request
     * @param $response
     * @param $ignore
     * @throws \Elasticsearch\Common\Exceptions\AlreadyExpiredException|\Elasticsearch\Common\Exceptions\BadRequest400Exception|\Elasticsearch\Common\Exceptions\Conflict409Exception|\Elasticsearch\Common\Exceptions\Forbidden403Exception|\Elasticsearch\Common\Exceptions\Missing404Exception|\Elasticsearch\Common\Exceptions\ScriptLangNotSupportedException|null
     */
    private function process4xxError($request, $response, $ignore)
    {
        $statusCode = $response['status'];
        $responseBody = $response['body'];

        /** @var \Exception $exception */
        $exception = $this->tryDeserialize400Error($response);

        if (array_search($response['status'], $ignore) !== false) {
            return;
        }

        if ($statusCode === 400 && strpos($responseBody, "AlreadyExpiredException") !== false) {
            $exception = new AlreadyExpiredException($responseBody, $statusCode);
        } elseif ($statusCode === 403) {
            $exception = new Forbidden403Exception($responseBody, $statusCode);
        } elseif ($statusCode === 404) {
            $exception = new Missing404Exception($responseBody, $statusCode);
        } elseif ($statusCode === 409) {
            $exception = new Conflict409Exception($responseBody, $statusCode);
        } elseif ($statusCode === 400 && strpos($responseBody, 'script_lang not supported') !== false) {
            $exception = new ScriptLangNotSupportedException($responseBody. $statusCode);
        } elseif ($statusCode === 408) {
            $exception = new RequestTimeout408Exception($responseBody, $statusCode);
        } else {
            $exception = new BadRequest400Exception($responseBody, $statusCode);
        }

        $this->logRequestFail(
            $request['http_method'],
            $response['effective_url'],
            $request['body'],
            $request['headers'],
            $response['status'],
            $response['body'],
            $response['transfer_stats']['total_time'],
            $exception
        );

        throw $exception;
    }

    /**
     * @param $request
     * @param $response
     * @param $ignore
     * @throws \Elasticsearch\Common\Exceptions\NoDocumentsToGetException|\Elasticsearch\Common\Exceptions\NoShardAvailableException|\Elasticsearch\Common\Exceptions\RoutingMissingException|\Elasticsearch\Common\Exceptions\ServerErrorResponseException
     */
    private function process5xxError($request, $response, $ignore)
    {
        $statusCode = $response['status'];
        $responseBody = $response['body'];

        /** @var \Exception $exception */
        $exception = $this->tryDeserialize500Error($response);

        $exceptionText = "[$statusCode Server Exception] ".$exception->getMessage();
        $this->log->error($exceptionText);
        $this->log->error($exception->getTraceAsString());

        if (array_search($statusCode, $ignore) !== false) {
            return;
        }

        if ($statusCode === 500 && strpos($responseBody, "RoutingMissingException") !== false) {
            $exception = new RoutingMissingException($exception->getMessage(), $statusCode, $exception);
        } elseif ($statusCode === 500 && preg_match('/ActionRequestValidationException.+ no documents to get/', $responseBody) === 1) {
            $exception = new NoDocumentsToGetException($exception->getMessage(), $statusCode, $exception);
        } elseif ($statusCode === 500 && strpos($responseBody, 'NoShardAvailableActionException') !== false) {
            $exception = new NoShardAvailableException($exception->getMessage(), $statusCode, $exception);
        } else {
            $exception = new ServerErrorResponseException($responseBody, $statusCode);
        }

        $this->logRequestFail(
            $request['http_method'],
            $response['effective_url'],
            $request['body'],
            $request['headers'],
            $response['status'],
            $response['body'],
            $response['transfer_stats']['total_time'],
            $exception
        );

        throw $exception;
    }

    private function tryDeserialize400Error($response)
    {
        return $this->tryDeserializeError($response, 'Elasticsearch\Common\Exceptions\BadRequest400Exception');
    }

    private function tryDeserialize500Error($response)
    {
        return $this->tryDeserializeError($response, 'Elasticsearch\Common\Exceptions\ServerErrorResponseException');
    }

    private function tryDeserializeError($response, $errorClass)
    {
        $error = $this->serializer->deserialize($response['body'], $response['transfer_stats']);
        if (is_array($error) === true) {
            // 2.0 structured exceptions
            if (isset($error['error']['reason']) === true) {

                // Try to use root cause first (only grabs the first root cause)
                $root = $error['error']['root_cause'];
                if (isset($root) && isset($root[0])) {
                    $cause = $root[0]['reason'];
                    $type = $root[0]['type'];
                } else {
                    $cause = $error['error']['reason'];
                    $type = $error['error']['type'];
                }

                $original = new $errorClass($response['body'], $response['status']);

                return new $errorClass("$type: $cause", $response['status'], $original);
            } elseif (isset($error['error']) === true) {
                // <2.0 semi-structured exceptions
                $original = new $errorClass($response['body'], $response['status']);

                return new $errorClass($error['error'], $response['status'], $original);
            }

            // <2.0 "i just blew up" nonstructured exception
            // $error is an array but we don't know the format, reuse the response body instead
            return new $errorClass($response['body'], $response['status']);
        }

        // <2.0 "i just blew up" nonstructured exception
        return new $errorClass($response['body']);
    }
}
<?php

namespace Elasticsearch\Connections;

use Elasticsearch\Serializers\SerializerInterface;
use Psr\Log\LoggerInterface;

/**
 * Class AbstractConnection
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Connections
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
interface ConnectionFactoryInterface
{
    /**
     * @param $handler
     * @param array $connectionParams
     * @param SerializerInterface $serializer
     * @param LoggerInterface $logger
     * @param LoggerInterface $tracer
     */
    public function __construct(callable $handler, array $connectionParams,
                                SerializerInterface $serializer, LoggerInterface $logger, LoggerInterface $tracer);

    /**
     * @param $hostDetails
     *
     * @return ConnectionInterface
     */
    public function create($hostDetails);
}
<?php

namespace Elasticsearch\Connections;

use Elasticsearch\Serializers\SerializerInterface;
use Elasticsearch\Transport;
use Psr\Log\LoggerInterface;

/**
 * Interface ConnectionInterface
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Connections
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
interface ConnectionInterface
{
    /**
     * Constructor
     *
     * @param $handler
     * @param array $hostDetails
     * @param array $connectionParams connection-specific parameters
     * @param \Elasticsearch\Serializers\SerializerInterface $serializer
     * @param \Psr\Log\LoggerInterface $log          Logger object
     * @param \Psr\Log\LoggerInterface $trace        Logger object
     */
    public function __construct($handler, $hostDetails, $connectionParams,
                                SerializerInterface $serializer, LoggerInterface $log, LoggerInterface $trace);

    /**
     * Get the transport schema for this connection
     *
     * @return string
     */
    public function getTransportSchema();

    /**
     * Get the hostname for this connection
     *
     * @return string
     */
    public function getHost();

    /**
     * Get the username:password string for this connection, null if not set
     *
     * @return null|string
     */
    public function getUserPass();

    /**
     * Get the URL path suffix, null if not set
     *
     * @return null|string;
     */
    public function getPath();

    /**
     * Check to see if this instance is marked as 'alive'
     *
     * @return bool
     */
    public function isAlive();

    /**
     * Mark this instance as 'alive'
     *
     * @return void
     */
    public function markAlive();

    /**
     * Mark this instance as 'dead'
     *
     * @return void
     */
    public function markDead();

    /**
     * Return an associative array of information about the last request
     *
     * @return array
     */
    public function getLastRequestInfo();

    /**
     * @param $method
     * @param $uri
     * @param null $params
     * @param null $body
     * @param array $options
     * @param \Elasticsearch\Transport $transport
     * @return mixed
     */
    public function performRequest($method, $uri, $params = null, $body = null, $options = [], Transport $transport);
}
<?php

namespace Elasticsearch\Connections;

use Elasticsearch\Serializers\SerializerInterface;
use Psr\Log\LoggerInterface;

/**
 * Class AbstractConnection
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Connections
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class ConnectionFactory implements ConnectionFactoryInterface
{
    /** @var  array */
    private $connectionParams;

    /** @var  SerializerInterface */
    private $serializer;

    /** @var  LoggerInterface */
    private $logger;

    /** @var  LoggerInterface */
    private $tracer;

    /** @var callable */
    private $handler;

    /**
     * Constructor
     *
     * @param callable            $handler
     * @param array               $connectionParams
     * @param SerializerInterface $serializer
     * @param LoggerInterface     $logger
     * @param LoggerInterface     $tracer
     */
    public function __construct(callable $handler, array $connectionParams, SerializerInterface $serializer, LoggerInterface $logger, LoggerInterface $tracer)
    {
        $this->handler          = $handler;
        $this->connectionParams = $connectionParams;
        $this->logger           = $logger;
        $this->tracer           = $tracer;
        $this->serializer       = $serializer;
    }
    /**
     * @param $hostDetails
     *
     * @return ConnectionInterface
     */
    public function create($hostDetails)
    {
        return new Connection(
            $this->handler,
            $hostDetails,
            $this->connectionParams,
            $this->serializer,
            $this->logger,
            $this->tracer
        );
    }
}
<?php

namespace Elasticsearch\Common;

use Psr\Log\AbstractLogger;
use Psr\Log\LoggerInterface;

/**
 * Class EmptyLogger
 *
 * Logger that doesn't do anything.  Similar to Monolog's NullHandler,
 * but avoids the overhead of partially loading Monolog
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Common
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class EmptyLogger extends AbstractLogger implements LoggerInterface
{
    /**
     * Logs with an arbitrary level.
     *
     * @param mixed $level
     * @param string $message
     * @param array $context
     *
     * @return null
     */
    public function log($level, $message, array $context = array())
    {
        return;
    }
}
<?php

namespace Elasticsearch\Common\Exceptions;

/**
 * Class ClientErrorResponseException
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Common\Exceptions
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class ClientErrorResponseException extends TransportException implements ElasticsearchException
{
}
<?php

namespace Elasticsearch\Common\Exceptions\Serializer;

use Elasticsearch\Common\Exceptions\ElasticsearchException;

/**
 * Class JsonErrorException
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Common\Exceptions\Curl
 * @author   Bez Hermoso <bezalelhermoso@gmail.com>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class JsonErrorException extends \Exception implements ElasticsearchException
{
    /**
     * @var mixed
     */
    private $input;

    /**
     * @var mixed
     */
    private $result;

    private static $messages = array(
        JSON_ERROR_DEPTH => 'The maximum stack depth has been exceeded',
        JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON',
        JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded',
        JSON_ERROR_SYNTAX => 'Syntax error',
        JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded',
        JSON_ERROR_RECURSION => 'One or more recursive references in the value to be encoded',
        JSON_ERROR_INF_OR_NAN => 'One or more NAN or INF values in the value to be encoded',
        JSON_ERROR_UNSUPPORTED_TYPE => 'A value of a type that cannot be encoded was given',

        // JSON_ERROR_* constant values that are available on PHP >= 7.0
        9 => 'Decoding of value would result in invalid PHP property name', //JSON_ERROR_INVALID_PROPERTY_NAME
        10 => 'Attempted to decode nonexistent UTF-16 code-point' //JSON_ERROR_UTF16
    );

    public function __construct($code, $input, $result, $previous = null)
    {
        if (isset(self::$messages[$code]) !== true) {
            throw new \InvalidArgumentException(sprintf('Encountered unknown JSON error code: [%d]', $code));
        }

        parent::__construct(self::$messages[$code], $code, $previous);
        $this->input = $input;
        $this->result = $result;
    }

    /**
     * @return mixed
     */
    public function getInput()
    {
        return $this->input;
    }

    /**
     * @return mixed
     */
    public function getResult()
    {
        return $this->result;
    }
}
<?php

namespace Elasticsearch\Common\Exceptions;

/**
 * Forbidden403Exception
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Common\Exceptions
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Forbidden403Exception extends \Exception implements ElasticsearchException
{
}
<?php

namespace Elasticsearch\Common\Exceptions;

/**
 * Missing404Exception
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Common\Exceptions
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Missing404Exception extends \Exception implements ElasticsearchException
{
}
<?php

namespace Elasticsearch\Common\Exceptions;

/**
 * UnexpectedValueException
 *
 * Denote a value that is outside the normally accepted values
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Common\Exceptions
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class UnexpectedValueException extends \UnexpectedValueException implements ElasticsearchException
{
}
<?php

namespace Elasticsearch\Common\Exceptions;

/**
 * RuntimeException
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Common\Exceptions
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class RuntimeException extends \RuntimeException implements ElasticsearchException
{
}
<?php

namespace Elasticsearch\Common\Exceptions;

/**
 * ServerErrorResponseException
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Common\Exceptions
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class ServerErrorResponseException extends TransportException implements ElasticsearchException
{
}
<?php

namespace Elasticsearch\Common\Exceptions;

/**
 * AlreadyExpiredException, thrown when a document has already expired
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Common\Exceptions
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class AlreadyExpiredException extends \Exception implements ElasticsearchException
{
}
<?php

namespace Elasticsearch\Common\Exceptions;

/**
 * InvalidArgumentException
 *
 * Denote invalid or incorrect argument values
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Common\Exceptions
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class InvalidArgumentException extends \InvalidArgumentException implements ElasticsearchException
{
}
<?php

namespace Elasticsearch\Common\Exceptions;

/**
 * BadMethodCallException
 *
 * Denote problems with a method call (e.g. incorrect number of arguments)
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Common\Exceptions
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class BadMethodCallException extends \BadMethodCallException implements ElasticsearchException
{
}
<?php

namespace Elasticsearch\Common\Exceptions;

/**
 * NoDocumentsToGetException
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Common\Exceptions
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class NoDocumentsToGetException extends ServerErrorResponseException implements ElasticsearchException
{
}
<?php

namespace Elasticsearch\Common\Exceptions\Curl;

use Elasticsearch\Common\Exceptions\ElasticsearchException;
use Elasticsearch\Common\Exceptions\TransportException;

/**
 * Class OperationTimeoutException
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Common\Exceptions\Curl
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
  */
class OperationTimeoutException extends TransportException implements ElasticsearchException
{
}
<?php

namespace Elasticsearch\Common\Exceptions\Curl;

use Elasticsearch\Common\Exceptions\ElasticsearchException;
use Elasticsearch\Common\Exceptions\TransportException;

/**
 * Class CouldNotConnectToHost
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Common\Exceptions\Curl
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class CouldNotConnectToHost extends TransportException implements ElasticsearchException
{
}
<?php

namespace Elasticsearch\Common\Exceptions\Curl;

use Elasticsearch\Common\Exceptions\ElasticsearchException;
use Elasticsearch\Common\Exceptions\TransportException;

/**
 * Class CouldNotResolveHostException
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Common\Exceptions\Curl
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
  */
class CouldNotResolveHostException extends TransportException implements ElasticsearchException
{
}
<?php

namespace Elasticsearch\Common\Exceptions;

/**
 * RequestTimeout408Exception
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Common\Exceptions
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class RequestTimeout408Exception extends BadRequest400Exception implements ElasticsearchException
{
}
<?php

namespace Elasticsearch\Common\Exceptions;

/**
 * RoutingMissingException, thrown on when a routing value is required but
 * not provided
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Common\Exceptions
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class RoutingMissingException extends ServerErrorResponseException implements ElasticsearchException
{
}
<?php

namespace Elasticsearch\Common\Exceptions;

/**
 * TransportException
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Common\Exceptions
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class TransportException extends \Exception implements ElasticsearchException
{
}
<?php

namespace Elasticsearch\Common\Exceptions;

/**
 * MaxRetriesException
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Common\Exceptions
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class MaxRetriesException extends TransportException implements ElasticsearchException
{
}
<?php

namespace Elasticsearch\Common\Exceptions;

/**
 * BadRequest400Exception, thrown on 400 conflict http error
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Common\Exceptions
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class BadRequest400Exception extends \Exception implements ElasticsearchException
{
}
<?php

namespace Elasticsearch\Common\Exceptions;

/**
 * ScriptLangNotSupportedException
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Common\Exceptions
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class ScriptLangNotSupportedException extends BadRequest400Exception implements ElasticsearchException
{
}
<?php

namespace Elasticsearch\Common\Exceptions;

/**
 * NoNodesAvailableException
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Common\Exceptions
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class NoNodesAvailableException extends \Exception implements ElasticsearchException
{
}
<?php

namespace Elasticsearch\Common\Exceptions;

/**
 * NoShardAvailableException
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Common\Exceptions
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class NoShardAvailableException extends ServerErrorResponseException implements ElasticsearchException
{
}
<?php

namespace Elasticsearch\Common\Exceptions;

/**
 * Conflict409Exception, thrown on 409 conflict http error
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Common\Exceptions
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Conflict409Exception extends \Exception implements ElasticsearchException
{
}
<?php

namespace Elasticsearch\Common\Exceptions;

/**
 * Generic Exception interface
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Common\Exceptions
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
interface ElasticsearchException
{
}
<?php

namespace Elasticsearch\Serializers;

/**
 * Interface SerializerInterface
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Serializers
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
interface SerializerInterface
{
    /**
     * Serialize a complex data-structure into a json encoded string
     *
     * @param mixed   The data to encode
     *
     * @return string
     */
    public function serialize($data);

    /**
     * Deserialize json encoded string into an associative array
     *
     * @param string $data    JSON encoded string
     * @param array  $headers Response Headers
     *
     * @return array
     */
    public function deserialize($data, $headers);
}
<?php

namespace Elasticsearch\Serializers;

/**
 * Class EverythingToJSONSerializer
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Serializers
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class EverythingToJSONSerializer implements SerializerInterface
{
    /**
     * Serialize assoc array into JSON string
     *
     * @param string|array $data Assoc array to encode into JSON
     *
     * @return string
     */
    public function serialize($data)
    {
        $data = json_encode($data, JSON_PRESERVE_ZERO_FRACTION);
        if ($data === '[]') {
            return '{}';
        } else {
            return $data;
        }
    }

    /**
     * Deserialize JSON into an assoc array
     *
     * @param string $data JSON encoded string
     * @param array  $headers Response headers
     *
     * @return array
     */
    public function deserialize($data, $headers)
    {
        return json_decode($data, true);
    }
}
<?php

namespace Elasticsearch\Serializers;

/**
 * Class JSONSerializer
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Serializers\JSONSerializer
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class ArrayToJSONSerializer implements SerializerInterface
{
    /**
     * Serialize assoc array into JSON string
     *
     * @param string|array $data Assoc array to encode into JSON
     *
     * @return string
     */
    public function serialize($data)
    {
        if (is_string($data) === true) {
            return $data;
        } else {
            $data = json_encode($data, JSON_PRESERVE_ZERO_FRACTION);
            if ($data === '[]') {
                return '{}';
            } else {
                return $data;
            }
        }
    }

    /**
     * Deserialize JSON into an assoc array
     *
     * @param string $data JSON encoded string
     * @param array  $headers Response Headers
     *
     * @return array
     */
    public function deserialize($data, $headers)
    {
        return json_decode($data, true);
    }
}
<?php

namespace Elasticsearch\Serializers;

use Elasticsearch\Common\Exceptions\Serializer\JsonErrorException;

/**
 * Class SmartSerializer
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Serializers\JSONSerializer
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class SmartSerializer implements SerializerInterface
{
    private $PHP_VERSION;

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

    /**
     * Serialize assoc array into JSON string
     *
     * @param string|array $data Assoc array to encode into JSON
     *
     * @return string
     */
    public function serialize($data)
    {
        if (is_string($data) === true) {
            return $data;
        } else {
            if (version_compare($this->PHP_VERSION, '5.6.6', '<') || ! defined('JSON_PRESERVE_ZERO_FRACTION')) {
                $data = json_encode($data);
            } else {
                $data = json_encode($data, JSON_PRESERVE_ZERO_FRACTION);
            }
            if ($data === '[]') {
                return '{}';
            } else {
                return $data;
            }
        }
    }

    /**
     * Deserialize by introspecting content_type. Tries to deserialize JSON,
     * otherwise returns string
     *
     * @param string $data JSON encoded string
     * @param array  $headers Response Headers
     *
     * @throws JsonErrorException
     * @return array
     */
    public function deserialize($data, $headers)
    {
        if (isset($headers['content_type']) === true) {
            if (strpos($headers['content_type'], 'json') !== false) {
                return $this->decode($data);
            } else {
                //Not json, return as string
                return $data;
            }
        } else {
            //No content headers, assume json
            return $this->decode($data);
        }
    }

    /**
     * @todo For 2.0, remove the E_NOTICE check before raising the exception.
     *
     * @param $data
     *
     * @return array
     * @throws JsonErrorException
     */
    private function decode($data)
    {
        if ($data === null || strlen($data) === 0) {
            return "";
        }

        $result = @json_decode($data, true);

        // Throw exception only if E_NOTICE is on to maintain backwards-compatibility on systems that silently ignore E_NOTICEs.
        if (json_last_error() !== JSON_ERROR_NONE && (error_reporting() & E_NOTICE) === E_NOTICE) {
            $e = new JsonErrorException(json_last_error(), $data, $result);
            throw $e;
        }

        return $result;
    }
}
<?php

namespace Elasticsearch;

use Elasticsearch\Common\Exceptions;
use Elasticsearch\ConnectionPool\AbstractConnectionPool;
use Elasticsearch\Connections\Connection;
use Elasticsearch\Connections\ConnectionInterface;
use GuzzleHttp\Ring\Future\FutureArrayInterface;
use Psr\Log\LoggerInterface;

/**
 * Class Transport
 *
 * @category Elasticsearch
 * @package  Elasticsearch
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Transport
{
    /**
     * @var AbstractConnectionPool
     */
    public $connectionPool;

    /**
     * @var LoggerInterface
     */
    private $log;

    /** @var  int */
    public $retryAttempts = 0;

    /** @var  Connection */
    public $lastConnection;

    /** @var int  */
    public $retries;

    /**
     * Transport class is responsible for dispatching requests to the
     * underlying cluster connections
     *
     * @param $retries
     * @param bool $sniffOnStart
     * @param ConnectionPool\AbstractConnectionPool $connectionPool
     * @param \Psr\Log\LoggerInterface $log    Monolog logger object
     */
    public function __construct($retries, $sniffOnStart = false, AbstractConnectionPool $connectionPool, LoggerInterface $log)
    {
        $this->log            = $log;
        $this->connectionPool = $connectionPool;
        $this->retries        = $retries;

        if ($sniffOnStart === true) {
            $this->log->notice('Sniff on Start.');
            $this->connectionPool->scheduleCheck();
        }
    }

    /**
     * Returns a single connection from the connection pool
     * Potentially performs a sniffing step before returning
     *
     * @return ConnectionInterface Connection
     */

    public function getConnection()
    {
        return $this->connectionPool->nextConnection();
    }

    /**
     * Perform a request to the Cluster
     *
     * @param string $method     HTTP method to use
     * @param string $uri        HTTP URI to send request to
     * @param null $params     Optional query parameters
     * @param null $body       Optional query body
     * @param array $options
     *
     * @throws Common\Exceptions\NoNodesAvailableException|\Exception
     * @return FutureArrayInterface
     */
    public function performRequest($method, $uri, $params = null, $body = null, $options = [])
    {
        try {
            $connection  = $this->getConnection();
        } catch (Exceptions\NoNodesAvailableException $exception) {
            $this->log->critical('No alive nodes found in cluster');
            throw $exception;
        }

        $response             = array();
        $caughtException      = null;
        $this->lastConnection = $connection;

        $future = $connection->performRequest(
            $method,
            $uri,
            $params,
            $body,
            $options,
            $this
        );

        $future->promise()->then(
            //onSuccess
            function ($response) {
                $this->retryAttempts = 0;
                // Note, this could be a 4xx or 5xx error
            },
            //onFailure
            function (\Exception $response) {
                // Ignore 400 level errors, as that means the server responded just fine
                $code = $response->getCode();
                if (!(isset($code) && $code >=400 && $code < 500)) {
                    // Otherwise schedule a check
                    $this->connectionPool->scheduleCheck();
                }
            });

        return $future;
    }

    /**
     * @param FutureArrayInterface $result  Response of a request (promise)
     * @param array                $options Options for transport
     *
     * @return callable|array
     */
    public function resultOrFuture($result, $options = [])
    {
        $response = null;
        $async = isset($options['client']['future']) ? $options['client']['future'] : null;
        if (is_null($async) || $async === false) {
            do {
                $result = $result->wait();
            } while ($result instanceof FutureArrayInterface);

            return $result;
        } elseif ($async === true || $async === 'lazy') {
            return $result;
        }
    }

    /**
     * @param $request
     *
     * @return bool
     */
    public function shouldRetry($request)
    {
        if ($this->retryAttempts < $this->retries) {
            $this->retryAttempts += 1;

            return true;
        }

        return false;
    }

    /**
     * Returns the last used connection so that it may be inspected.  Mainly
     * for debugging/testing purposes.
     *
     * @return Connection
     */
    public function getLastConnection()
    {
        return $this->lastConnection;
    }
}
<?php

namespace Elasticsearch\ConnectionPool;

use Elasticsearch\Common\Exceptions\NoNodesAvailableException;
use Elasticsearch\ConnectionPool\Selectors\SelectorInterface;
use Elasticsearch\Connections\Connection;
use Elasticsearch\Connections\ConnectionFactoryInterface;

class StaticNoPingConnectionPool extends AbstractConnectionPool implements ConnectionPoolInterface
{
    /**
     * @var int
     */
    private $pingTimeout    = 60;

    /**
     * @var int
     */
    private $maxPingTimeout = 3600;

    /**
     * {@inheritdoc}
     */
    public function __construct($connections, SelectorInterface $selector, ConnectionFactoryInterface $factory, $connectionPoolParams)
    {
        parent::__construct($connections, $selector, $factory, $connectionPoolParams);
    }

    /**
     * @param bool $force
     *
     * @return Connection
     * @throws \Elasticsearch\Common\Exceptions\NoNodesAvailableException
     */
    public function nextConnection($force = false)
    {
        $total = count($this->connections);
        while ($total--) {
            /** @var Connection $connection */
            $connection = $this->selector->select($this->connections);
            if ($connection->isAlive() === true) {
                return $connection;
            }

            if ($this->readyToRevive($connection) === true) {
                return $connection;
            }
        }

        throw new NoNodesAvailableException("No alive nodes found in your cluster");
    }

    public function scheduleCheck()
    {
    }

    /**
     * @param \Elasticsearch\Connections\Connection $connection
     *
     * @return bool
     */
    private function readyToRevive(Connection $connection)
    {
        $timeout = min(
            $this->pingTimeout * pow(2, $connection->getPingFailures()),
            $this->maxPingTimeout
        );

        if ($connection->getLastPing() + $timeout < time()) {
            return true;
        } else {
            return false;
        }
    }
}
<?php

namespace Elasticsearch\ConnectionPool;

use Elasticsearch\Common\Exceptions\InvalidArgumentException;
use Elasticsearch\ConnectionPool\Selectors\SelectorInterface;
use Elasticsearch\Connections\Connection;
use Elasticsearch\Connections\ConnectionFactoryInterface;
use Elasticsearch\Connections\ConnectionInterface;

/**
 * Class AbstractConnectionPool
 *
 * @category Elasticsearch
 * @package  Elasticsearch\ConnectionPool
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
abstract class AbstractConnectionPool implements ConnectionPoolInterface
{
    /**
     * Array of connections
     *
     * @var ConnectionInterface[]
     */
    protected $connections;

    /**
     * Array of initial seed connections
     *
     * @var ConnectionInterface[]
     */
    protected $seedConnections;

    /**
     * Selector object, used to select a connection on each request
     *
     * @var SelectorInterface
     */
    protected $selector;

    /** @var array */
    protected $connectionPoolParams;

    /** @var \Elasticsearch\Connections\ConnectionFactory  */
    protected $connectionFactory;

    /**
     * Constructor
     *
     * @param ConnectionInterface[]          $connections          The Connections to choose from
     * @param SelectorInterface              $selector             A Selector instance to perform the selection logic for the available connections
     * @param ConnectionFactoryInterface     $factory              ConnectionFactory instance
     * @param array                          $connectionPoolParams
     */
    public function __construct($connections, SelectorInterface $selector, ConnectionFactoryInterface $factory, $connectionPoolParams)
    {
        $paramList = array('connections', 'selector', 'connectionPoolParams');
        foreach ($paramList as $param) {
            if (isset($$param) === false) {
                throw new InvalidArgumentException('`' . $param . '` parameter must not be null');
            }
        }

        if (isset($connectionPoolParams['randomizeHosts']) === true
            && $connectionPoolParams['randomizeHosts'] === true) {
            shuffle($connections);
        }

        $this->connections          = $connections;
        $this->seedConnections      = $connections;
        $this->selector             = $selector;
        $this->connectionPoolParams = $connectionPoolParams;
        $this->connectionFactory    = $factory;
    }

    /**
     * @param bool $force
     *
     * @return Connection
     */
    abstract public function nextConnection($force = false);

    abstract public function scheduleCheck();
}
<?php

namespace Elasticsearch\ConnectionPool;

use Elasticsearch\Common\Exceptions\Curl\OperationTimeoutException;
use Elasticsearch\Common\Exceptions\NoNodesAvailableException;
use Elasticsearch\ConnectionPool\Selectors\SelectorInterface;
use Elasticsearch\Connections\Connection;
use Elasticsearch\Connections\ConnectionFactoryInterface;

class SniffingConnectionPool extends AbstractConnectionPool implements ConnectionPoolInterface
{
    /** @var int  */
    private $sniffingInterval = 300;

    /** @var  int */
    private $nextSniff = -1;

    /**
     * {@inheritdoc}
     */
    public function __construct($connections, SelectorInterface $selector, ConnectionFactoryInterface $factory, $connectionPoolParams)
    {
        parent::__construct($connections, $selector, $factory, $connectionPoolParams);

        $this->setConnectionPoolParams($connectionPoolParams);
        $this->nextSniff = time() + $this->sniffingInterval;
    }

    /**
     * @param bool $force
     *
     * @return Connection
     * @throws \Elasticsearch\Common\Exceptions\NoNodesAvailableException
     */
    public function nextConnection($force = false)
    {
        $this->sniff($force);

        $size = count($this->connections);
        while ($size--) {
            /** @var Connection $connection */
            $connection = $this->selector->select($this->connections);
            if ($connection->isAlive() === true || $connection->ping() === true) {
                return $connection;
            }
        }

        if ($force === true) {
            throw new NoNodesAvailableException("No alive nodes found in your cluster");
        }

        return $this->nextConnection(true);
    }

    public function scheduleCheck()
    {
        $this->nextSniff = -1;
    }

    /**
     * @param bool $force
     */
    private function sniff($force = false)
    {
        if ($force === false && $this->nextSniff >= time()) {
            return;
        }

        $total = count($this->connections);

        while ($total--) {
            /** @var Connection $connection */
            $connection = $this->selector->select($this->connections);

            if ($connection->isAlive() xor $force) {
                continue;
            }

            if ($this->sniffConnection($connection) === true) {
                return;
            }
        }

        if ($force === true) {
            return;
        }

        foreach ($this->seedConnections as $connection) {
            if ($this->sniffConnection($connection) === true) {
                return;
            }
        }
    }

    /**
     * @param Connection $connection
     * @return bool
     */
    private function sniffConnection(Connection $connection)
    {
        try {
            $response = $connection->sniff();
        } catch (OperationTimeoutException $exception) {
            return false;
        }

        $nodes = $this->parseClusterState($connection->getTransportSchema(), $response);

        if (count($nodes) === 0) {
            return false;
        }

        $this->connections = array();

        foreach ($nodes as $node) {
            $nodeDetails = array(
                'host' => $node['host'],
                'port' => $node['port']
            );
            $this->connections[] = $this->connectionFactory->create($nodeDetails);
        }

        $this->nextSniff = time() + $this->sniffingInterval;

        return true;
    }

    private function parseClusterState($transportSchema, $nodeInfo)
    {
        $pattern       = '/([^:]*):([0-9]+)/';
        $schemaAddress = $transportSchema . '_address';
        $hosts         = array();

        foreach ($nodeInfo['nodes'] as $node) {
            if (isset($node['http']) === true && isset($node['http']['publish_address']) === true) {
                if (preg_match($pattern, $node['http']['publish_address'], $match) === 1) {
                    $hosts[] = array(
                        'host' => $match[1],
                        'port' => (int) $match[2],
                    );
                }
            }
        }

        return $hosts;
    }

    private function setConnectionPoolParams($connectionPoolParams)
    {
        if (isset($connectionPoolParams['sniffingInterval']) === true) {
            $this->sniffingInterval = $connectionPoolParams['sniffingInterval'];
        }
    }
}
<?php

namespace Elasticsearch\ConnectionPool;

use Elasticsearch\Common\Exceptions\NoNodesAvailableException;
use Elasticsearch\ConnectionPool\Selectors\SelectorInterface;
use Elasticsearch\Connections\Connection;
use Elasticsearch\Connections\ConnectionFactoryInterface;

class StaticConnectionPool extends AbstractConnectionPool implements ConnectionPoolInterface
{
    /**
     * @var int
     */
    private $pingTimeout    = 60;

    /**
     * @var int
     */
    private $maxPingTimeout = 3600;

    /**
     * {@inheritdoc}
     */
    public function __construct($connections, SelectorInterface $selector, ConnectionFactoryInterface $factory, $connectionPoolParams)
    {
        parent::__construct($connections, $selector, $factory, $connectionPoolParams);
        $this->scheduleCheck();
    }

    /**
     * @param bool $force
     *
     * @return Connection
     * @throws \Elasticsearch\Common\Exceptions\NoNodesAvailableException
     */
    public function nextConnection($force = false)
    {
        $skipped = array();

        $total = count($this->connections);
        while ($total--) {
            /** @var Connection $connection */
            $connection = $this->selector->select($this->connections);
            if ($connection->isAlive() === true) {
                return $connection;
            }

            if ($this->readyToRevive($connection) === true) {
                if ($connection->ping() === true) {
                    return $connection;
                }
            } else {
                $skipped[] = $connection;
            }
        }

        // All "alive" nodes failed, force pings on "dead" nodes
        foreach ($skipped as $connection) {
            if ($connection->ping() === true) {
                return $connection;
            }
        }

        throw new NoNodesAvailableException("No alive nodes found in your cluster");
    }

    public function scheduleCheck()
    {
        foreach ($this->connections as $connection) {
            $connection->markDead();
        }
    }

    /**
     * @param Connection $connection
     *
     * @return bool
     */
    private function readyToRevive(Connection $connection)
    {
        $timeout = min(
            $this->pingTimeout * pow(2, $connection->getPingFailures()),
            $this->maxPingTimeout
        );

        if ($connection->getLastPing() + $timeout < time()) {
            return true;
        } else {
            return false;
        }
    }
}
<?php

namespace Elasticsearch\ConnectionPool;

use Elasticsearch\Connections\ConnectionInterface;

/**
 * ConnectionPoolInterface
 *
 * @category Elasticsearch
 * @package  Elasticsearch\ConnectionPool
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
interface ConnectionPoolInterface
{
    /**
     * @param bool $force
     *
     * @return ConnectionInterface
     */
    public function nextConnection($force = false);

    /**
     * @return void
     */
    public function scheduleCheck();
}
<?php

namespace Elasticsearch\ConnectionPool;

use Elasticsearch\ConnectionPool\Selectors\SelectorInterface;
use Elasticsearch\Connections\Connection;
use Elasticsearch\Connections\ConnectionFactoryInterface;

class SimpleConnectionPool extends AbstractConnectionPool implements ConnectionPoolInterface
{

    /**
     * {@inheritdoc}
     */
    public function __construct($connections, SelectorInterface $selector, ConnectionFactoryInterface $factory, $connectionPoolParams)
    {
        parent::__construct($connections, $selector, $factory, $connectionPoolParams);
    }

    /**
     * @param bool $force
     *
     * @return Connection
     * @throws \Elasticsearch\Common\Exceptions\NoNodesAvailableException
     */
    public function nextConnection($force = false)
    {
        return $this->selector->select($this->connections);
    }

    public function scheduleCheck()
    {
    }
}
<?php

namespace Elasticsearch\ConnectionPool\Selectors;

/**
 * Class RandomSelector
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Connections\Selectors
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
interface SelectorInterface
{
    /**
     * Perform logic to select a single ConnectionInterface instance from the array provided
     *
     * @param  ConnectionInterface[] $connections an array of ConnectionInterface instances to choose from
     *
     * @return \Elasticsearch\Connections\ConnectionInterface
     */
    public function select($connections);
}
<?php

namespace Elasticsearch\ConnectionPool\Selectors;

use Elasticsearch\Connections\ConnectionInterface;

/**
 * Class RandomSelector
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Connections\Selectors\RandomSelector
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class RandomSelector implements SelectorInterface
{
    /**
     * Select a random connection from the provided array
     *
     * @param  ConnectionInterface[] $connections an array of ConnectionInterface instances to choose from
     *
     * @return \Elasticsearch\Connections\ConnectionInterface
     */
    public function select($connections)
    {
        return $connections[array_rand($connections)];
    }
}
<?php

namespace Elasticsearch\ConnectionPool\Selectors;

use Elasticsearch\Connections\ConnectionInterface;

/**
 * Class RoundRobinSelector
 *
 * @category Elasticsearch
 * @package  Elasticsearch\ConnectionPool\Selectors\ConnectionPool
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class RoundRobinSelector implements SelectorInterface
{
    /**
     * @var int
     */
    private $current = 0;

    /**
     * Select the next connection in the sequence
     *
     * @param  ConnectionInterface[] $connections an array of ConnectionInterface instances to choose from
     *
     * @return \Elasticsearch\Connections\ConnectionInterface
     */
    public function select($connections)
    {
        $this->current += 1;

        return $connections[$this->current % count($connections)];
    }
}
<?php

namespace Elasticsearch\ConnectionPool\Selectors;

use Elasticsearch\Connections\ConnectionInterface;

/**
 * Class StickyRoundRobinSelector
 *
 * @category Elasticsearch
 * @package  Elasticsearch\ConnectionPool\Selectors\ConnectionPool
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class StickyRoundRobinSelector implements SelectorInterface
{
    /**
     * @var int
     */
    private $current = 0;

    /**
     * @var int
     */
    private $currentCounter = 0;

    /**
     * Use current connection unless it is dead, otherwise round-robin
     *
     * @param ConnectionInterface[] $connections Array of connections to choose from
     *
     * @return ConnectionInterface
     */
    public function select($connections)
    {
        /** @var ConnectionInterface[] $connections */
        if ($connections[$this->current]->isAlive()) {
            return $connections[$this->current];
        }

        $this->currentCounter += 1;
        $this->current = $this->currentCounter % count($connections);

        return $connections[$this->current];
    }
}
<?php

namespace Elasticsearch;

use Elasticsearch\Common\Exceptions\InvalidArgumentException;
use Elasticsearch\Common\Exceptions\RuntimeException;
use Elasticsearch\ConnectionPool\AbstractConnectionPool;
use Elasticsearch\ConnectionPool\Selectors\SelectorInterface;
use Elasticsearch\ConnectionPool\StaticNoPingConnectionPool;
use Elasticsearch\Connections\Connection;
use Elasticsearch\Connections\ConnectionFactory;
use Elasticsearch\Connections\ConnectionFactoryInterface;
use Elasticsearch\Namespaces\NamespaceBuilderInterface;
use Elasticsearch\Serializers\SerializerInterface;
use Elasticsearch\ConnectionPool\Selectors;
use Elasticsearch\Serializers\SmartSerializer;
use GuzzleHttp\Ring\Client\CurlHandler;
use GuzzleHttp\Ring\Client\CurlMultiHandler;
use GuzzleHttp\Ring\Client\Middleware;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Processor\IntrospectionProcessor;

/**
 * Class ClientBuilder
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Common\Exceptions
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
  */
class ClientBuilder
{
    /** @var Transport */
    private $transport;

    /** @var callback */
    private $endpoint;

    /** @var NamespaceBuilderInterface[] */
    private $registeredNamespacesBuilders = [];

    /** @var  ConnectionFactoryInterface */
    private $connectionFactory;

    private $handler;

    /** @var  LoggerInterface */
    private $logger;

    /** @var  LoggerInterface */
    private $tracer;

    /** @var string */
    private $connectionPool = '\Elasticsearch\ConnectionPool\StaticNoPingConnectionPool';

    /** @var  string */
    private $serializer = '\Elasticsearch\Serializers\SmartSerializer';

    /** @var  string */
    private $selector = '\Elasticsearch\ConnectionPool\Selectors\RoundRobinSelector';

    /** @var  array */
    private $connectionPoolArgs = [
        'randomizeHosts' => true
    ];

    /** @var array */
    private $hosts;

    /** @var array */
    private $connectionParams;

    /** @var  int */
    private $retries;

    /** @var bool */
    private $sniffOnStart = false;

    /** @var null|array  */
    private $sslCert = null;

    /** @var null|array  */
    private $sslKey = null;

    /** @var null|bool|string */
    private $sslVerification = null;

    /** @var bool  */
    private $allowBadJSON = false;

    /**
     * @return ClientBuilder
     */
    public static function create()
    {
        return new static();
    }

    /**
     * Can supply first parm to Client::__construct() when invoking manually or with dependency injection
     * @return this->ransport
     *
     */
    public function getTransport()
    {
        return $this->transport;
    }

    /**
     * Can supply second parm to Client::__construct() when invoking manually or with dependency injection
     * @return this->endpoint
     *
     */
    public function getEndpoint()
    {
        return $this->endpoint;
    }

    /**
     * Can supply third parm to Client::__construct() when invoking manually or with dependency injection
     * @return this->registeredNamespacesBuilders
     *
     */
    public function getRegisteredNamespacesBuilders()
    {
        return $this->registeredNamespacesBuilders;
    }

    /**
     * Build a new client from the provided config.  Hash keys
     * should correspond to the method name e.g. ['connectionPool']
     * corresponds to setConnectionPool().
     *
     * Missing keys will use the default for that setting if applicable
     *
     * Unknown keys will throw an exception by default, but this can be silenced
     * by setting `quiet` to true
     *
     * @param array $config hash of settings
     * @param bool $quiet False if unknown settings throw exception, true to silently
     *                    ignore unknown settings
     * @throws Common\Exceptions\RuntimeException
     * @return \Elasticsearch\Client
     */
    public static function fromConfig($config, $quiet = false)
    {
        $builder = new self;
        foreach ($config as $key => $value) {
            $method = "set$key";
            if (method_exists($builder, $method)) {
                $builder->$method($value);
                unset($config[$key]);
            }
        }

        if ($quiet === false && count($config) > 0) {
            $unknown = implode(array_keys($config));
            throw new RuntimeException("Unknown parameters provided: $unknown");
        }
        return $builder->build();
    }

    /**
     * @param array $singleParams
     * @param array $multiParams
     * @throws \RuntimeException
     * @return callable
     */
    public static function defaultHandler($multiParams = [], $singleParams = [])
    {
        $future = null;
        if (extension_loaded('curl')) {
            $config = array_merge([ 'mh' => curl_multi_init() ], $multiParams);
            if (function_exists('curl_reset')) {
                $default = new CurlHandler($singleParams);
                $future = new CurlMultiHandler($config);
            } else {
                $default = new CurlMultiHandler($config);
            }
        } else {
            throw new \RuntimeException('Elasticsearch-PHP requires cURL, or a custom HTTP handler.');
        }

        return $future ? Middleware::wrapFuture($default, $future) : $default;
    }

    /**
     * @param array $params
     * @throws \RuntimeException
     * @return CurlMultiHandler
     */
    public static function multiHandler($params = [])
    {
        if (function_exists('curl_multi_init')) {
            return new CurlMultiHandler(array_merge([ 'mh' => curl_multi_init() ], $params));
        } else {
            throw new \RuntimeException('CurlMulti handler requires cURL.');
        }
    }

    /**
     * @return CurlHandler
     * @throws \RuntimeException
     */
    public static function singleHandler()
    {
        if (function_exists('curl_reset')) {
            return new CurlHandler();
        } else {
            throw new \RuntimeException('CurlSingle handler requires cURL.');
        }
    }

    /**
     * @param $path string
     * @return \Monolog\Logger\Logger
     */
    public static function defaultLogger($path, $level = Logger::WARNING)
    {
        $log       = new Logger('log');
        $handler   = new StreamHandler($path, $level);
        $log->pushHandler($handler);

        return $log;
    }

    /**
     * @param \Elasticsearch\Connections\ConnectionFactoryInterface $connectionFactory
     * @return $this
     */
    public function setConnectionFactory(ConnectionFactoryInterface $connectionFactory)
    {
        $this->connectionFactory = $connectionFactory;

        return $this;
    }

    /**
     * @param \Elasticsearch\ConnectionPool\AbstractConnectionPool|string $connectionPool
     * @param array $args
     * @throws \InvalidArgumentException
     * @return $this
     */
    public function setConnectionPool($connectionPool, array $args = [])
    {
        if (is_string($connectionPool)) {
            $this->connectionPool = $connectionPool;
            $this->connectionPoolArgs = $args;
        } elseif (is_object($connectionPool)) {
            $this->connectionPool = $connectionPool;
        } else {
            throw new InvalidArgumentException("Serializer must be a class path or instantiated object extending AbstractConnectionPool");
        }

        return $this;
    }

    /**
     * @param callable $endpoint
     * @return $this
     */
    public function setEndpoint($endpoint)
    {
        $this->endpoint = $endpoint;

        return $this;
    }

    /**
     * @param NamespaceBuilderInterface $namespaceBuilder
     * @return $this
     */
    public function registerNamespace(NamespaceBuilderInterface $namespaceBuilder)
    {
        $this->registeredNamespacesBuilders[] = $namespaceBuilder;

        return $this;
    }

    /**
     * @param \Elasticsearch\Transport $transport
     * @return $this
     */
    public function setTransport($transport)
    {
        $this->transport = $transport;

        return $this;
    }

    /**
     * @param mixed $handler
     * @return $this
     */
    public function setHandler($handler)
    {
        $this->handler = $handler;

        return $this;
    }

    /**
     * @param \Psr\Log\LoggerInterface $logger
     * @return $this
     */
    public function setLogger($logger)
    {
        $this->logger = $logger;

        return $this;
    }

    /**
     * @param \Psr\Log\LoggerInterface $tracer
     * @return $this
     */
    public function setTracer($tracer)
    {
        $this->tracer = $tracer;

        return $this;
    }

    /**
     * @param \Elasticsearch\Serializers\SerializerInterface|string $serializer
     * @throws \InvalidArgumentException
     * @return $this
     */
    public function setSerializer($serializer)
    {
        $this->parseStringOrObject($serializer, $this->serializer, 'SerializerInterface');

        return $this;
    }

    /**
     * @param array $hosts
     * @return $this
     */
    public function setHosts($hosts)
    {
        $this->hosts = $hosts;

        return $this;
    }

    /**
     * @param array $params
     * @return $this
     */
    public function setConnectionParams(array $params)
    {
        $this->connectionParams = $params;

        return $this;
    }

    /**
     * @param int $retries
     * @return $this
     */
    public function setRetries($retries)
    {
        $this->retries = $retries;

        return $this;
    }

    /**
     * @param \Elasticsearch\ConnectionPool\Selectors\SelectorInterface|string $selector
     * @throws \InvalidArgumentException
     * @return $this
     */
    public function setSelector($selector)
    {
        $this->parseStringOrObject($selector, $this->selector, 'SelectorInterface');

        return $this;
    }

    /**
     * @param boolean $sniffOnStart
     * @return $this
     */
    public function setSniffOnStart($sniffOnStart)
    {
        $this->sniffOnStart = $sniffOnStart;

        return $this;
    }

    /**
     * @param $cert
     * @param null|string $password
     * @return $this
     */
    public function setSSLCert($cert, $password = null)
    {
        $this->sslCert = [$cert, $password];

        return $this;
    }

    /**
     * @param $key
     * @param null|string $password
     * @return $this
     */
    public function setSSLKey($key, $password = null)
    {
        $this->sslKey = [$key, $password];

        return $this;
    }

    /**
     * @param bool|string $value
     * @return $this
     */
    public function setSSLVerification($value = true)
    {
        $this->sslVerification = $value;

        return $this;
    }

    public function allowBadJSONSerialization()
    {
        $this->allowBadJSON = true;
        return $this;
    }

    /**
     * @return Client
     */
    public function build()
    {
        if(!defined('JSON_PRESERVE_ZERO_FRACTION') && $this->allowBadJSON === false) {
            throw new RuntimeException("Your version of PHP / json-ext does not support the constant 'JSON_PRESERVE_ZERO_FRACTION',".
            " which is important for proper type mapping in Elasticsearch. Please upgrade your PHP or json-ext.\n".
            "If you are unable to upgrade, and are willing to accept the consequences, you may use the allowBadJSONSerialization()".
            " method on the ClientBuilder to bypass this limitation.");
        }

        $this->buildLoggers();

        if (is_null($this->handler)) {
            $this->handler = ClientBuilder::defaultHandler();
        }

        $sslOptions = null;
        if (isset($this->sslKey)) {
            $sslOptions['ssl_key'] = $this->sslKey;
        }
        if (isset($this->sslCert)) {
            $sslOptions['cert'] = $this->sslCert;
        }
        if (isset($this->sslVerification)) {
            $sslOptions['verify'] = $this->sslVerification;
        }

        if (!is_null($sslOptions)) {
            $sslHandler = function (callable $handler, array $sslOptions) {
                return function (array $request) use ($handler, $sslOptions) {
                    // Add our custom headers
                    foreach ($sslOptions as $key => $value) {
                        $request['client'][$key] = $value;
                    }

                    // Send the request using the handler and return the response.
                    return $handler($request);
                };
            };
            $this->handler = $sslHandler($this->handler, $sslOptions);
        }

        if (is_null($this->serializer)) {
            $this->serializer = new SmartSerializer();
        } elseif (is_string($this->serializer)) {
            $this->serializer = new $this->serializer;
        }

        if (is_null($this->connectionFactory)) {
            if (is_null($this->connectionParams)) {
                $this->connectionParams = [];
            }

            // Make sure we are setting Content-Type and Accept (unless the user has explicitly
            // overridden it
            if (isset($this->connectionParams['client']['headers']) === false) {
                $this->connectionParams['client']['headers'] = [
                    'Content-Type' => ['application/json'],
                    'Accept' => ['application/json']
                ];
            } else {
                if (isset($this->connectionParams['client']['headers']['Content-Type']) === false) {
                    $this->connectionParams['client']['headers']['Content-Type'] = ['application/json'];
                }
                if (isset($this->connectionParams['client']['headers']['Accept']) === false) {
                    $this->connectionParams['client']['headers']['Accept'] = ['application/json'];
                }
            }

            $this->connectionFactory = new ConnectionFactory($this->handler, $this->connectionParams, $this->serializer, $this->logger, $this->tracer);
        }

        if (is_null($this->hosts)) {
            $this->hosts = $this->getDefaultHost();
        }

        if (is_null($this->selector)) {
            $this->selector = new Selectors\RoundRobinSelector();
        } elseif (is_string($this->selector)) {
            $this->selector = new $this->selector;
        }

        $this->buildTransport();

        if (is_null($this->endpoint)) {
            $serializer = $this->serializer;

            $this->endpoint = function ($class) use ($serializer) {
                $fullPath = '\\Elasticsearch\\Endpoints\\' . $class;
                if ($class === 'Bulk' || $class === 'Msearch' || $class === 'MsearchTemplate' || $class === 'MPercolate') {
                    return new $fullPath($serializer);
                } else {
                    return new $fullPath();
                }
            };
        }

        $registeredNamespaces = [];
        foreach ($this->registeredNamespacesBuilders as $builder) {
            /** @var $builder NamespaceBuilderInterface */
            $registeredNamespaces[$builder->getName()] = $builder->getObject($this->transport, $this->serializer);
        }

        return $this->instantiate($this->transport, $this->endpoint, $registeredNamespaces);
    }

    /**
     * @param Transport $transport
     * @param callable $endpoint
     * @param Object[] $registeredNamespaces
     * @return Client
     */
    protected function instantiate(Transport $transport, callable $endpoint, array $registeredNamespaces)
    {
        return new Client($transport, $endpoint, $registeredNamespaces);
    }

    private function buildLoggers()
    {
        if (is_null($this->logger)) {
            $this->logger = new NullLogger();
        }

        if (is_null($this->tracer)) {
            $this->tracer = new NullLogger();
        }
    }

    private function buildTransport()
    {
        $connections = $this->buildConnectionsFromHosts($this->hosts);

        if (is_string($this->connectionPool)) {
            $this->connectionPool = new $this->connectionPool(
                $connections,
                $this->selector,
                $this->connectionFactory,
                $this->connectionPoolArgs);
        } elseif (is_null($this->connectionPool)) {
            $this->connectionPool = new StaticNoPingConnectionPool(
                $connections,
                $this->selector,
                $this->connectionFactory,
                $this->connectionPoolArgs);
        }

        if (is_null($this->retries)) {
            $this->retries = count($connections);
        }

        if (is_null($this->transport)) {
            $this->transport = new Transport($this->retries, $this->sniffOnStart, $this->connectionPool, $this->logger);
        }
    }

    private function parseStringOrObject($arg, &$destination, $interface)
    {
        if (is_string($arg)) {
            $destination = new $arg;
        } elseif (is_object($arg)) {
            $destination = $arg;
        } else {
            throw new InvalidArgumentException("Serializer must be a class path or instantiated object implementing $interface");
        }
    }

    /**
     * @return array
     */
    private function getDefaultHost()
    {
        return ['localhost:9200'];
    }

    /**
     * @param array $hosts
     *
     * @throws \InvalidArgumentException
     * @return \Elasticsearch\Connections\Connection[]
     */
    private function buildConnectionsFromHosts($hosts)
    {
        if (is_array($hosts) === false) {
            $this->logger->error("Hosts parameter must be an array of strings, or an array of Connection hashes.");
            throw new InvalidArgumentException('Hosts parameter must be an array of strings, or an array of Connection hashes.');
        }

        $connections = [];
        foreach ($hosts as $host) {
            if (is_string($host)) {
                $host = $this->prependMissingScheme($host);
                $host = $this->extractURIParts($host);
            } else if (is_array($host)) {
                $host = $this->normalizeExtendedHost($host);
            } else {
                $this->logger->error("Could not parse host: ".print_r($host, true));
                throw new RuntimeException("Could not parse host: ".print_r($host, true));
            }
            $connections[] = $this->connectionFactory->create($host);
        }

        return $connections;
    }

    /**
     * @param $host
     * @return array
     */
    private function normalizeExtendedHost($host) {
        if (isset($host['host']) === false) {
            $this->logger->error("Required 'host' was not defined in extended format: ".print_r($host, true));
            throw new RuntimeException("Required 'host' was not defined in extended format: ".print_r($host, true));
        }

        if (isset($host['scheme']) === false) {
            $host['scheme'] = 'http';
        }
        if (isset($host['port']) === false) {
            $host['port'] = '9200';
        }
        return $host;
    }

    /**
     * @param array $host
     *
     * @throws \InvalidArgumentException
     * @return array
     */
    private function extractURIParts($host)
    {
        $parts = parse_url($host);

        if ($parts === false) {
            throw new InvalidArgumentException("Could not parse URI");
        }

        if (isset($parts['port']) !== true) {
            $parts['port'] = 9200;
        }

        return $parts;
    }

    /**
     * @param string $host
     *
     * @return string
     */
    private function prependMissingScheme($host)
    {
        if (!filter_var($host, FILTER_VALIDATE_URL)) {
            $host = 'http://' . $host;
        }

        return $host;
    }
}
<?php

namespace Elasticsearch\Helper\Iterators;

use ElasticSearch\Client;
use Iterator;

/**
 * Class SearchResponseIterator
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Helper\Iterators
 * @author   Arturo Mejia <arturo.mejia@kreatetechnology.com>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 * @see      Iterator
 */
class SearchResponseIterator implements Iterator
{

    /**
     * @var Client
     */
    private $client;

    /**
     * @var array
     */
    private $params;

    /**
     * @var int
     */
    private $current_key;

    /**
     * @var array
     */
    private $current_scrolled_response;

    /**
     * @var string
     */
    private $scroll_id;

    /**
     * @var duration
     */
    private $scroll_ttl;

    /**
     * Constructor
     *
     * @param Client $client
     * @param array  $params  Associative array of parameters
     * @see   Client::search()
     */
    public function __construct(Client $client, array $search_params)
    {
        $this->client = $client;
        $this->params = $search_params;

        if (isset($search_params['scroll'])) {
            $this->scroll_ttl = $search_params['scroll'];
        }
    }

    /**
     * Destructor
     */
    public function __destruct()
    {
        $this->clearScroll();
    }

    /**
     * Sets the time to live duration of a scroll window
     *
     * @param  string $time_to_live
     * @return $this
     */
    public function setScrollTimeout($time_to_live)
    {
        $this->scroll_ttl = $time_to_live;
        return $this;
    }

    /**
     * Clears the current scroll window if there is a scroll_id stored
     *
     * @return void
     */
    private function clearScroll()
    {
        if (!empty($this->scroll_id)) {
            $this->client->clearScroll(
                array(
                    'scroll_id' => $this->scroll_id,
                    'client' => array(
                        'ignore' => 404
                    )
                )
            );
            $this->scroll_id = null;
        }
    }

    /**
     * Rewinds the iterator by performing the initial search.
     *
     *
     * @return void
     * @see    Iterator::rewind()
     */
    public function rewind()
    {
        $this->clearScroll();
        $this->current_key = 0;
        $this->current_scrolled_response = $this->client->search($this->params);
        $this->scroll_id = $this->current_scrolled_response['_scroll_id'];
    }

    /**
     * Fetches every "page" after the first one using the lastest "scroll_id"
     *
     * @return void
     * @see    Iterator::next()
     */
    public function next()
    {
        if ($this->current_key !== 0) {
            $this->current_scrolled_response = $this->client->scroll(
                array(
                    'scroll_id' => $this->scroll_id,
                    'scroll'    => $this->scroll_ttl
                )
            );
            $this->scroll_id = $this->current_scrolled_response['_scroll_id'];
        }
        $this->current_key++;
    }

    /**
     * Returns a boolean value indicating if the current page is valid or not
     *
     * @return bool
     * @see    Iterator::valid()
     */
    public function valid()
    {
        return isset($this->current_scrolled_response['hits']['hits'][0]);
    }

    /**
     * Returns the current "page"
     *
     * @return array
     * @see    Iterator::current()
     */
    public function current()
    {
        return $this->current_scrolled_response;
    }

    /**
     * Returns the current "page number" of the current "page"
     *
     * @return int
     * @see    Iterator::key()
     */
    public function key()
    {
        return $this->current_key;
    }
}
<?php

namespace Elasticsearch\Helper\Iterators;

use Iterator;

/**
 * Class SearchHitIterator
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Helper\Iterators
 * @author   Arturo Mejia <arturo.mejia@kreatetechnology.com>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 * @see      Iterator
 */
class SearchHitIterator implements Iterator, \Countable
{

    /**
     * @var SearchResponseIterator
     */
    private $search_responses;

    /**
     * @var int
     */
    protected $current_key;

    /**
     * @var int
     */
    protected $current_hit_index;

    /**
     * @var array|null
     */
    protected $current_hit_data;

    /**
     * @var int
     */
    protected $count;

    /**
     * Constructor
     *
     * @param SearchResponseIterator $search_responses
     */
    public function __construct(SearchResponseIterator $search_responses)
    {
        $this->search_responses = $search_responses;
    }

    /**
     * Rewinds the internal SearchResponseIterator and itself
     *
     * @return void
     * @see    Iterator::rewind()
     */
    public function rewind()
    {
        $this->current_key = 0;
        $this->search_responses->rewind();

        // The first page may be empty. In that case, the next page is fetched.
        $current_page = $this->search_responses->current();
        if ($this->search_responses->valid() && empty($current_page['hits']['hits'])) {
            $this->search_responses->next();
        }

        $this->count = 0;
        if (isset($current_page['hits']) && isset($current_page['hits']['total'])) {
            $this->count = $current_page['hits']['total'];
        }

        $this->readPageData();
    }

    /**
     * Advances pointer of the current hit to the next one in the current page. If there
     * isn't a next hit in the current page, then it advances the current page and moves the
     * pointer to the first hit in the page.
     *
     * @return void
     * @see    Iterator::next()
     */
    public function next()
    {
        $this->current_key++;
        $this->current_hit_index++;
        $current_page = $this->search_responses->current();
        if (isset($current_page['hits']['hits'][$this->current_hit_index])) {
            $this->current_hit_data = $current_page['hits']['hits'][$this->current_hit_index];
        } else {
            $this->search_responses->next();
            $this->readPageData();
        }
    }

    /**
     * Returns a boolean indicating whether or not the current pointer has valid data
     *
     * @return bool
     * @see    Iterator::valid()
     */
    public function valid()
    {
        return is_array($this->current_hit_data);
    }

    /**
     * Returns the current hit
     *
     * @return array
     * @see    Iterator::current()
     */
    public function current()
    {
        return $this->current_hit_data;
    }

    /**
     * Returns the current hit index. The hit index spans all pages.
     *
     * @return int
     * @see    Iterator::key()
     */
    public function key()
    {
        return $this->current_hit_index;
    }

    /**
     * Advances the internal SearchResponseIterator and resets the current_hit_index to 0
     *
     * @internal
     */
    private function readPageData()
    {
        if ($this->search_responses->valid()) {
            $current_page = $this->search_responses->current();
            $this->current_hit_index = 0;
            $this->current_hit_data = $current_page['hits']['hits'][$this->current_hit_index];
        } else {
            $this->current_hit_data = null;
        }
    }

    /**
     * {@inheritDoc}
     */
    public function count()
    {
        if ($this->count === null) {
            $this->rewind();
        }

        return $this->count;
    }
}
<?php

namespace Elasticsearch;

use Elasticsearch\Common\Exceptions\BadMethodCallException;
use Elasticsearch\Common\Exceptions\InvalidArgumentException;
use Elasticsearch\Common\Exceptions\NoNodesAvailableException;
use Elasticsearch\Common\Exceptions\BadRequest400Exception;
use Elasticsearch\Common\Exceptions\Missing404Exception;
use Elasticsearch\Common\Exceptions\TransportException;
use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Namespaces\AbstractNamespace;
use Elasticsearch\Namespaces\CatNamespace;
use Elasticsearch\Namespaces\ClusterNamespace;
use Elasticsearch\Namespaces\IndicesNamespace;
use Elasticsearch\Namespaces\IngestNamespace;
use Elasticsearch\Namespaces\NamespaceBuilderInterface;
use Elasticsearch\Namespaces\NodesNamespace;
use Elasticsearch\Namespaces\RemoteNamespace;
use Elasticsearch\Namespaces\SnapshotNamespace;
use Elasticsearch\Namespaces\BooleanRequestWrapper;
use Elasticsearch\Namespaces\TasksNamespace;

/**
 * Class Client
 *
 * @category Elasticsearch
 * @package  Elasticsearch
 * @author   Zachary Tong <zach@elastic.co>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Client
{
    /**
     * @var Transport
     */
    public $transport;

    /**
     * @var array
     */
    protected $params;

    /**
     * @var IndicesNamespace
     */
    protected $indices;

    /**
     * @var ClusterNamespace
     */
    protected $cluster;

    /**
     * @var NodesNamespace
     */
    protected $nodes;

    /**
     * @var SnapshotNamespace
     */
    protected $snapshot;

    /**
     * @var CatNamespace
     */
    protected $cat;

    /**
     * @var IngestNamespace
     */
    protected $ingest;

    /**
     * @var TasksNamespace
     */
    protected $tasks;

    /**
     * @var RemoteNamespace
     */
    protected $remote;

    /** @var  callback */
    protected $endpoints;

    /** @var  NamespaceBuilderInterface[] */
    protected $registeredNamespaces = [];

    /**
     * Client constructor
     *
     * @param Transport $transport
     * @param callable $endpoint
     * @param AbstractNamespace[] $registeredNamespaces
     */
    public function __construct(Transport $transport, callable $endpoint, array $registeredNamespaces)
    {
        $this->transport = $transport;
        $this->endpoints = $endpoint;
        $this->indices   = new IndicesNamespace($transport, $endpoint);
        $this->cluster   = new ClusterNamespace($transport, $endpoint);
        $this->nodes     = new NodesNamespace($transport, $endpoint);
        $this->snapshot  = new SnapshotNamespace($transport, $endpoint);
        $this->cat       = new CatNamespace($transport, $endpoint);
        $this->ingest    = new IngestNamespace($transport, $endpoint);
        $this->tasks     = new TasksNamespace($transport, $endpoint);
        $this->remote    = new RemoteNamespace($transport, $endpoint);
        $this->registeredNamespaces = $registeredNamespaces;
    }

    /**
     * @param $params
     * @return array
     */
    public function info($params = [])
    {
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Info $endpoint */
        $endpoint = $endpointBuilder('Info');
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * @param $params array Associative array of parameters
     *
     * @return bool
     */
    public function ping($params = [])
    {
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Ping $endpoint */
        $endpoint = $endpointBuilder('Ping');
        $endpoint->setParams($params);

        try {
            $this->performRequest($endpoint);
        } catch (Missing404Exception $exception) {
            return false;
        } catch (TransportException $exception) {
            return false;
        } catch (NoNodesAvailableException $exception) {
            return false;
        }

        return true;
    }

    /**
     * $params['id']              = (string) The document ID (Required)
     *        ['index']           = (string) The name of the index (Required)
     *        ['type']            = (string) The type of the document (use `_all` to fetch the first document matching the ID across all types) (Required)
     *        ['ignore_missing']  = ??
     *        ['fields']          = (list) A comma-separated list of fields to return in the response
     *        ['parent']          = (string) The ID of the parent document
     *        ['preference']      = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['realtime']        = (boolean) Specify whether to perform the operation in realtime or search mode
     *        ['refresh']         = (boolean) Refresh the shard containing the document before performing the operation
     *        ['routing']         = (string) Specific routing value
     *        ['_source']         = (list) True or false to return the _source field or not, or a list of fields to return
     *        ['_source_exclude'] = (list) A list of fields to exclude from the returned _source field
     *        ['_source_include'] = (list) A list of fields to extract and return from the _source field
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function get($params)
    {
        $id = $this->extractArgument($params, 'id');
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Get $endpoint */
        $endpoint = $endpointBuilder('Get');
        $endpoint->setID($id)
                 ->setIndex($index)
                 ->setType($type);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['id']             = (string) The document ID (Required)
     *        ['index']          = (string) The name of the index (Required)
     *        ['type']           = (string) The type of the document (use `_all` to fetch the first document matching the ID across all types) (Required)
     *        ['ignore_missing'] = ??
     *        ['parent']         = (string) The ID of the parent document
     *        ['preference']     = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['realtime']       = (boolean) Specify whether to perform the operation in realtime or search mode
     *        ['refresh']        = (boolean) Refresh the shard containing the document before performing the operation
     *        ['routing']        = (string) Specific routing value
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function getSource($params)
    {
        $id = $this->extractArgument($params, 'id');
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Get $endpoint */
        $endpoint = $endpointBuilder('Get');
        $endpoint->setID($id)
                 ->setIndex($index)
                 ->setType($type)
                 ->returnOnlySource();
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['id']           = (string) The document ID (Required)
     *        ['index']        = (string) The name of the index (Required)
     *        ['type']         = (string) The type of the document (Required)
     *        ['consistency']  = (enum) Specific write consistency setting for the operation
     *        ['parent']       = (string) ID of parent document
     *        ['refresh']      = (boolean) Refresh the index after performing the operation
     *        ['replication']  = (enum) Specific replication type
     *        ['routing']      = (string) Specific routing value
     *        ['timeout']      = (time) Explicit operation timeout
     *        ['version_type'] = (enum) Specific version type
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function delete($params)
    {
        $id = $this->extractArgument($params, 'id');
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');

        $this->verifyNotNullOrEmpty("id", $id);
        $this->verifyNotNullOrEmpty("type", $type);
        $this->verifyNotNullOrEmpty("index", $index);

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Delete $endpoint */
        $endpoint = $endpointBuilder('Delete');
        $endpoint->setID($id)
                 ->setIndex($index)
                 ->setType($type);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     *
     * $params['_source'] = (list) True or false to return the _source field or not, or a list of fields to return
     *        ['_source_exclude'] = (array) A list of fields to exclude from the returned _source field
     *        ['_source_include'] = (array) A list of fields to extract and return from the _source field
     *        ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['analyze_wildcard'] = (bool) Specify whether wildcard and prefix queries should be analyzed (default: false)
     *        ['analyzer'] = (string) The analyzer to use for the query string
     *        ['conflicts'] = (enum) What to do when the delete-by-query hits version conflicts?
     *        ['default_operator'] = (enum) The default operator for query string query (AND or OR)
     *        ['df'] = (string) The field to use as default where no field prefix is given in the query string
     *        ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *        ['from'] = (number) Starting offset (default: 0)
     *        ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['lenient'] = (bool) Specify whether format-based query failures (such as providing text to a numeric field) should be ignored
     *        ['preference'] = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['q'] = (string) Query in the Lucene query string syntax
     *        ['refresh'] = (bool) Should the effected indexes be refreshed?
     *        ['request_cache'] = (bool) Specify if request cache should be used for this request or not, defaults to index level setting
     *        ['requests_per_second'] = (number) The throttle for this request in sub-requests per second. -1 means no throttle.
     *        ['routing'] = (array) A comma-separated list of specific routing values
     *        ['scroll'] = (number) Specify how long a consistent view of the index should be maintained for scrolled search
     *        ['scroll_size'] = (number) Size on the scroll request powering the update_by_query
     *        ['search_timeout'] = (number) Explicit timeout for each search request. Defaults to no timeout.
     *        ['search_type'] = (enum) Search operation type
     *        ['size'] = (number) Number of hits to return (default: 10)
     *        ['slices'] = (integer) The number of slices this task should be divided into. Defaults to 1 meaning the task isn't sliced into subtasks.
     *        ['sort'] = (array) A comma-separated list of <field>:<direction> pairs
     *        ['stats'] = (array) Specific 'tag' of the request for logging and statistical purposes
     *        ['terminate_after'] = (number) The maximum number of documents to collect for each shard, upon reaching which the query execution will terminate early.
     *        ['timeout'] = (number) Time each individual bulk request should wait for shards that are unavailable.
     *        ['version'] = (bool) Specify whether to return document version as part of a hit
     *        ['wait_for_active_shards'] = (string) Sets the number of shard copies that must be active before proceeding with the delete by query operation. Defaults to 1, meaning the primary shard only. Set to `all` for all shard copies, otherwise set to any non-negative value less than or equal to the total number of copies for the shard (number of replicas + 1)
     *        ['wait_for_completion'] = (bool) Should the request should block until the delete-by-query is complete.
     *
     * @param array $params
     *
     * @return array
     */
    public function deleteByQuery($params = array())
    {
        $index = $this->extractArgument($params, 'index');

        $type = $this->extractArgument($params, 'type');

        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\DeleteByQuery $endpoint */
        $endpoint = $endpointBuilder('DeleteByQuery');
        $endpoint->setIndex($index)
                ->setType($type)
                ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']              = (list) A comma-separated list of indices to restrict the results
     *        ['type']               = (list) A comma-separated list of types to restrict the results
     *        ['min_score']          = (number) Include only documents with a specific `_score` value in the result
     *        ['preference']         = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['routing']            = (string) Specific routing value
     *        ['source']             = (string) The URL-encoded query definition (instead of using the request body)
     *        ['body']               = (array) A query to restrict the results (optional)
     *        ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['allow_no_indices']   = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['expand_wildcards']   = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function count($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Count $endpoint */
        $endpoint = $endpointBuilder('Count');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']              = (list) A comma-separated list of indices to restrict the results
     *        ['type']               = (list) A comma-separated list of types to restrict the results
     *        ['id']                 = (string) ID of document
     *        ['ignore_unavailable'] = (boolean) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['preference']         = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['routing']            = (string) Specific routing value
     *        ['allow_no_indices']   = (boolean) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['body']               = (array) A query to restrict the results (optional)
     *        ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['percolate_index']    = (string) The index to count percolate the document into. Defaults to index.
     *        ['expand_wildcards']   = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *        ['version']            = (number) Explicit version number for concurrency control
     *        ['version_type']       = (enum) Specific version type
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     *
     * @deprecated
     */
    public function countPercolate($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type  = $this->extractArgument($params, 'type');
        $id    = $this->extractArgument($params, 'id');
        $body  = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\CountPercolate $endpoint */
        $endpoint = $endpointBuilder('CountPercolate');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setID($id)
                 ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']        = (string) The name of the index with a registered percolator query (Required)
     *        ['type']         = (string) The document type (Required)
     *        ['prefer_local'] = (boolean) With `true`, specify that a local shard should be used if available, with `false`, use a random shard (default: true)
     *        ['body']         = (array) The document (`doc`) to percolate against registered queries; optionally also a `query` to limit the percolation to specific registered queries
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     *
     * @deprecated
     */
    public function percolate($params)
    {
        $index = $this->extractArgument($params, 'index');
        $type  = $this->extractArgument($params, 'type');
        $id    = $this->extractArgument($params, 'id');
        $body  = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Percolate $endpoint */
        $endpoint = $endpointBuilder('Percolate');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setID($id)
                 ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']              = (string) Default index for items which don't provide one
     *        ['type']               = (string) Default document type for items which don't provide one
     *        ['ignore_unavailable'] = (boolean) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['allow_no_indices']   = (boolean) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['expand_wildcards']   = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     *
     * @deprecated
     */
    public function mpercolate($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\MPercolate $endpoint */
        $endpoint = $endpointBuilder('MPercolate');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']            = (string) Default index for items which don't provide one
     *        ['type']             = (string) Default document type for items which don't provide one
     *        ['term_statistics']  = (boolean) Specifies if total term frequency and document frequency should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['field_statistics'] = (boolean) Specifies if document count, sum of document frequencies and sum of total term frequencies should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['fields']           = (list) A comma-separated list of fields to return. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['offsets']          = (boolean) Specifies if term offsets should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['positions']        = (boolean) Specifies if term positions should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['payloads']         = (boolean) Specifies if term payloads should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\".
     *        ['preference']       = (string) Specify the node or shard the operation should be performed on (default: random) .Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\".
     *        ['routing']          = (string) Specific routing value. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\".
     *        ['parent']           = (string) Parent id of documents. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\".
     *        ['realtime']         = (boolean) Specifies if request is real-time as opposed to near-real-time (default: true).
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function termvectors($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type  = $this->extractArgument($params, 'type');
        $id    = $this->extractArgument($params, 'id');
        $body  = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\TermVectors $endpoint */
        $endpoint = $endpointBuilder('TermVectors');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setID($id)
                 ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']            = (string) Default index for items which don't provide one
     *        ['type']             = (string) Default document type for items which don't provide one
     *        ['ids']              = (list) A comma-separated list of documents ids. You must define ids as parameter or set \"ids\" or \"docs\" in the request body
     *        ['term_statistics']  = (boolean) Specifies if total term frequency and document frequency should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['field_statistics'] = (boolean) Specifies if document count, sum of document frequencies and sum of total term frequencies should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['fields']           = (list) A comma-separated list of fields to return. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['offsets']          = (boolean) Specifies if term offsets should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['positions']        = (boolean) Specifies if term positions should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['payloads']         = (boolean) Specifies if term payloads should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\".
     *        ['preference']       = (string) Specify the node or shard the operation should be performed on (default: random) .Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\".
     *        ['routing']          = (string) Specific routing value. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\".
     *        ['parent']           = (string) Parent id of documents. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\".
     *        ['realtime']         = (boolean) Specifies if request is real-time as opposed to near-real-time (default: true).
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function mtermvectors($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type  = $this->extractArgument($params, 'type');
        $body  = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\MTermVectors $endpoint */
        $endpoint = $endpointBuilder('MTermVectors');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['id']         = (string) The document ID (Required)
     *        ['index']      = (string) The name of the index (Required)
     *        ['type']       = (string) The type of the document (use `_all` to fetch the first document matching the ID across all types) (Required)
     *        ['parent']     = (string) The ID of the parent document
     *        ['preference'] = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['realtime']   = (boolean) Specify whether to perform the operation in realtime or search mode
     *        ['refresh']    = (boolean) Refresh the shard containing the document before performing the operation
     *        ['routing']    = (string) Specific routing value
     *
     * @param $params array Associative array of parameters
     *
     * @return array | boolean
     */
    public function exists($params)
    {
        $id = $this->extractArgument($params, 'id');
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');

        //manually make this verbose so we can check status code
        $params['client']['verbose'] = true;

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Exists $endpoint */
        $endpoint = $endpointBuilder('Exists');
        $endpoint->setID($id)
                 ->setIndex($index)
                 ->setType($type);
        $endpoint->setParams($params);

        return BooleanRequestWrapper::performRequest($endpoint, $this->transport);
    }

    /**
     * $params['index']           = (string) The name of the index
     *        ['type']            = (string) The type of the document
     *        ['fields']          = (list) A comma-separated list of fields to return in the response
     *        ['parent']          = (string) The ID of the parent document
     *        ['preference']      = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['realtime']        = (boolean) Specify whether to perform the operation in realtime or search mode
     *        ['refresh']         = (boolean) Refresh the shard containing the document before performing the operation
     *        ['routing']         = (string) Specific routing value
     *        ['body']            = (array) Document identifiers; can be either `docs` (containing full document information) or `ids` (when index and type is provided in the URL.
     *        ['_source']         = (list) True or false to return the _source field or not, or a list of fields to return
     *        ['_source_exclude'] = (list) A list of fields to exclude from the returned _source field
     *        ['_source_include'] = (list) A list of fields to extract and return from the _source field
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function mget($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Mget $endpoint */
        $endpoint = $endpointBuilder('Mget');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']       = (list) A comma-separated list of index names to use as default
     *        ['type']        = (list) A comma-separated list of document types to use as default
     *        ['search_type'] = (enum) Search operation type
     *        ['body']        = (array|string) The request definitions (metadata-search request definition pairs), separated by newlines
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function msearch($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Msearch $endpoint */
        $endpoint = $endpointBuilder('Msearch');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']       = (list) A comma-separated list of index names to use as default
     *        ['type']        = (list) A comma-separated list of document types to use as default
     *        ['search_type'] = (enum) Search operation type
     *        ['body']        = (array|string) The request definitions (metadata-search request definition pairs), separated by newlines
     *        ['max_concurrent_searches'] = (number) Controls the maximum number of concurrent searches the multi search api will execute
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function msearchTemplate($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\MsearchTemplate $endpoint */
        $endpoint = $endpointBuilder('MsearchTemplate');
        $endpoint->setIndex($index)
            ->setType($type)
            ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']        = (string) The name of the index (Required)
     *        ['type']         = (string) The type of the document (Required)
     *        ['id']           = (string) Specific document ID (when the POST method is used)
     *        ['consistency']  = (enum) Explicit write consistency setting for the operation
     *        ['parent']       = (string) ID of the parent document
     *        ['refresh']      = (boolean) Refresh the index after performing the operation
     *        ['replication']  = (enum) Specific replication type
     *        ['routing']      = (string) Specific routing value
     *        ['timeout']      = (time) Explicit operation timeout
     *        ['timestamp']    = (time) Explicit timestamp for the document
     *        ['ttl']          = (duration) Expiration time for the document
     *        ['version']      = (number) Explicit version number for concurrency control
     *        ['version_type'] = (enum) Specific version type
     *        ['body']         = (array) The document
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function create($params)
    {
        $id = $this->extractArgument($params, 'id');
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Create $endpoint */
        $endpoint = $endpointBuilder('Create');
        $endpoint->setID($id)
                 ->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']       = (string) Default index for items which don't provide one
     *        ['type']        = (string) Default document type for items which don't provide one
     *        ['consistency'] = (enum) Explicit write consistency setting for the operation
     *        ['refresh']     = (boolean) Refresh the index after performing the operation
     *        ['replication'] = (enum) Explicitly set the replication type
     *        ['fields']      = (list) Default comma-separated list of fields to return in the response for updates
     *        ['body']        = (array) The document
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function bulk($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Bulk $endpoint */
        $endpoint = $endpointBuilder('Bulk');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']        = (string) The name of the index (Required)
     *        ['type']         = (string) The type of the document (Required)
     *        ['id']           = (string) Specific document ID (when the POST method is used)
     *        ['consistency']  = (enum) Explicit write consistency setting for the operation
     *        ['op_type']      = (enum) Explicit operation type
     *        ['parent']       = (string) ID of the parent document
     *        ['refresh']      = (boolean) Refresh the index after performing the operation
     *        ['replication']  = (enum) Specific replication type
     *        ['routing']      = (string) Specific routing value
     *        ['timeout']      = (time) Explicit operation timeout
     *        ['timestamp']    = (time) Explicit timestamp for the document
     *        ['ttl']          = (duration) Expiration time for the document
     *        ['version']      = (number) Explicit version number for concurrency control
     *        ['version_type'] = (enum) Specific version type
     *        ['body']         = (array) The document
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function index($params)
    {
        $id = $this->extractArgument($params, 'id');
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Index $endpoint */
        $endpoint = $endpointBuilder('Index');
        $endpoint->setID($id)
                 ->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['refresh']             = (boolean) Should the effected indexes be refreshed?
     *        ['timeout']             = (time) Time each individual bulk request should wait for shards that are unavailable
     *        ['consistency']         = (enum) Explicit write consistency setting for the operation
     *        ['wait_for_completion'] = (boolean) Should the request should block until the reindex is complete
     *        ['requests_per_second'] = (float) The throttle for this request in sub-requests per second. 0 means set no throttle
     *        ['body']                = (array) The search definition using the Query DSL and the prototype for the index request (Required)
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function reindex($params)
    {
        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
        /** @var \Elasticsearch\Endpoints\Reindex $endpoint */
        $endpoint = $endpointBuilder('Reindex');
        $endpoint->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']          = (list) A comma-separated list of index names to restrict the operation; use `_all` or empty string to perform the operation on all indices
     *        ['ignore_indices'] = (enum) When performed on multiple indices, allows to ignore `missing` ones
     *        ['preference']     = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['routing']        = (string) Specific routing value
     *        ['source']         = (string) The URL-encoded request definition (instead of using request body)
     *        ['body']           = (array) The request definition
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function suggest($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Suggest $endpoint */
        $endpoint = $endpointBuilder('Suggest');
        $endpoint->setIndex($index)
                 ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['id']                       = (string) The document ID (Required)
     *        ['index']                    = (string) The name of the index (Required)
     *        ['type']                     = (string) The type of the document (Required)
     *        ['analyze_wildcard']         = (boolean) Specify whether wildcards and prefix queries in the query string query should be analyzed (default: false)
     *        ['analyzer']                 = (string) The analyzer for the query string query
     *        ['default_operator']         = (enum) The default operator for query string query (AND or OR)
     *        ['df']                       = (string) The default field for query string query (default: _all)
     *        ['fields']                   = (list) A comma-separated list of fields to return in the response
     *        ['lenient']                  = (boolean) Specify whether format-based query failures (such as providing text to a numeric field) should be ignored
     *        ['lowercase_expanded_terms'] = (boolean) Specify whether query terms should be lowercased
     *        ['parent']                   = (string) The ID of the parent document
     *        ['preference']               = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['q']                        = (string) Query in the Lucene query string syntax
     *        ['routing']                  = (string) Specific routing value
     *        ['source']                   = (string) The URL-encoded query definition (instead of using the request body)
     *        ['_source']                  = (list) True or false to return the _source field or not, or a list of fields to return
     *        ['_source_exclude']          = (list) A list of fields to exclude from the returned _source field
     *        ['_source_include']          = (list) A list of fields to extract and return from the _source field
     *        ['body']                     = (string) The URL-encoded query definition (instead of using the request body)
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function explain($params)
    {
        $id = $this->extractArgument($params, 'id');
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Explain $endpoint */
        $endpoint = $endpointBuilder('Explain');
        $endpoint->setID($id)
                 ->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']                    = (list) A comma-separated list of index names to search; use `_all` or empty string to perform the operation on all indices
     *        ['type']                     = (list) A comma-separated list of document types to search; leave empty to perform the operation on all types
     *        ['analyzer']                 = (string) The analyzer to use for the query string
     *        ['analyze_wildcard']         = (boolean) Specify whether wildcard and prefix queries should be analyzed (default: false)
     *        ['default_operator']         = (enum) The default operator for query string query (AND or OR)
     *        ['df']                       = (string) The field to use as default where no field prefix is given in the query string
     *        ['explain']                  = (boolean) Specify whether to return detailed information about score computation as part of a hit
     *        ['fields']                   = (list) A comma-separated list of fields to return as part of a hit
     *        ['from']                     = (number) Starting offset (default: 0)
     *        ['ignore_indices']           = (enum) When performed on multiple indices, allows to ignore `missing` ones
     *        ['indices_boost']            = (list) Comma-separated list of index boosts
     *        ['lenient']                  = (boolean) Specify whether format-based query failures (such as providing text to a numeric field) should be ignored
     *        ['lowercase_expanded_terms'] = (boolean) Specify whether query terms should be lowercased
     *        ['preference']               = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['q']                        = (string) Query in the Lucene query string syntax
     *        ['query_cache']              = (boolean) Enable query cache for this request
     *        ['request_cache']            = (boolean) Enable request cache for this request
     *        ['routing']                  = (list) A comma-separated list of specific routing values
     *        ['scroll']                   = (duration) Specify how long a consistent view of the index should be maintained for scrolled search
     *        ['search_type']              = (enum) Search operation type
     *        ['size']                     = (number) Number of hits to return (default: 10)
     *        ['sort']                     = (list) A comma-separated list of <field>:<direction> pairs
     *        ['source']                   = (string) The URL-encoded request definition using the Query DSL (instead of using request body)
     *        ['_source']                  = (list) True or false to return the _source field or not, or a list of fields to return
     *        ['_source_exclude']          = (list) A list of fields to exclude from the returned _source field
     *        ['_source_include']          = (list) A list of fields to extract and return from the _source field
     *        ['stats']                    = (list) Specific 'tag' of the request for logging and statistical purposes
     *        ['suggest_field']            = (string) Specify which field to use for suggestions
     *        ['suggest_mode']             = (enum) Specify suggest mode
     *        ['suggest_size']             = (number) How many suggestions to return in response
     *        ['suggest_text']             = (text) The source text for which the suggestions should be returned
     *        ['timeout']                  = (time) Explicit operation timeout
     *        ['terminate_after']          = (number) The maximum number of documents to collect for each shard, upon reaching which the query execution will terminate early.
     *        ['version']                  = (boolean) Specify whether to return document version as part of a hit
     *        ['body']                     = (array|string) The search definition using the Query DSL
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function search($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Search $endpoint */
        $endpoint = $endpointBuilder('Search');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']              = (list) A comma-separated list of index names to search; use `_all` or empty string to perform the operation on all indices
     *        ['type']               = (list) A comma-separated list of document types to search; leave empty to perform the operation on all types
     *        ['preference']         = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['routing']            = (string) Specific routing value
     *        ['local']              = (bool) Return local information, do not retrieve the state from master node (default: false)
     *        ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['allow_no_indices']   = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['expand_wildcards']   = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function searchShards($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\SearchShards $endpoint */
        $endpoint = $endpointBuilder('SearchShards');
        $endpoint->setIndex($index)
                 ->setType($type);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']                    = (list) A comma-separated list of index names to search; use `_all` or empty string to perform the operation on all indices
     *        ['type']                     = (list) A comma-separated list of document types to search; leave empty to perform the operation on all types
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function searchTemplate($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Search $endpoint */
        $endpoint = $endpointBuilder('SearchTemplate');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['scroll_id'] = (string) The scroll ID for scrolled search
     *        ['scroll']    = (duration) Specify how long a consistent view of the index should be maintained for scrolled search
     *        ['body']      = (string) The scroll ID for scrolled search
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function scroll($params = array())
    {
        $scrollID = $this->extractArgument($params, 'scroll_id');
        $body = $this->extractArgument($params, 'body');
        $scroll = $this->extractArgument($params, 'scroll');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Scroll $endpoint */
        $endpoint = $endpointBuilder('Scroll');
        $endpoint->setScrollID($scrollID)
                 ->setScroll($scroll)
                 ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['scroll_id'] = (string) The scroll ID for scrolled search
     *        ['scroll']    = (duration) Specify how long a consistent view of the index should be maintained for scrolled search
     *        ['body']      = (string) The scroll ID for scrolled search
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function clearScroll($params = array())
    {
        $scrollID = $this->extractArgument($params, 'scroll_id');
        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\ClearScroll $endpoint */
        $endpoint = $endpointBuilder('ClearScroll');
        $endpoint->setScrollID($scrollID)
                 ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['id']                = (string) Document ID (Required)
     *        ['index']             = (string) The name of the index (Required)
     *        ['type']              = (string) The type of the document (Required)
     *        ['consistency']       = (enum) Explicit write consistency setting for the operation
     *        ['fields']            = (list) A comma-separated list of fields to return in the response
     *        ['lang']              = (string) The script language (default: mvel)
     *        ['parent']            = (string) ID of the parent document
     *        ['refresh']           = (boolean) Refresh the index after performing the operation
     *        ['replication']       = (enum) Specific replication type
     *        ['retry_on_conflict'] = (number) Specify how many times should the operation be retried when a conflict occurs (default: 0)
     *        ['routing']           = (string) Specific routing value
     *        ['script']            = () The URL-encoded script definition (instead of using request body)
     *        ['timeout']           = (time) Explicit operation timeout
     *        ['timestamp']         = (time) Explicit timestamp for the document
     *        ['ttl']               = (duration) Expiration time for the document
     *        ['version_type']      = (number) Explicit version number for concurrency control
     *        ['body']              = (array) The request definition using either `script` or partial `doc`
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function update($params)
    {
        $id = $this->extractArgument($params, 'id');
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Update $endpoint */
        $endpoint = $endpointBuilder('Update');
        $endpoint->setID($id)
                 ->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']                    = (list) A comma-separated list of index names to search; use `_all` or
     * empty string to perform the operation on all indices (Required)
     *        ['type']                     = (list) A comma-separated list of document types to search; leave empty to
     * perform the operation on all types
     *        ['analyzer']                 = (string) The analyzer to use for the query string
     *        ['analyze_wildcard']         = (boolean) Specify whether wildcard and prefix queries should be analyzed
     * (default: false)
     *        ['default_operator']         = (enum) The default operator for query string query (AND or OR) (AND,OR)
     * (default: OR)
     *        ['df']                       = (string) The field to use as default where no field prefix is given in the
     * query string
     *        ['explain']                  = (boolean) Specify whether to return detailed information about score
     * computation as part of a hit
     *        ['fields']                   = (list) A comma-separated list of fields to return as part of a hit
     *        ['fielddata_fields']         = (list) A comma-separated list of fields to return as the field data
     * representation of a field for each hit
     *        ['from']                     = (number) Starting offset (default: 0)
     *        ['ignore_unavailable']       = (boolean) Whether specified concrete indices should be ignored when
     * unavailable (missing or closed)
     *        ['allow_no_indices']         = (boolean) Whether to ignore if a wildcard indices expression resolves into
     * no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['conflicts']                = (enum) What to do when the reindex hits version conflicts? (abort,proceed)
     * (default: abort)
     *        ['expand_wildcards']         = (enum) Whether to expand wildcard expression to concrete indices that are
     * open, closed or both. (open,closed,none,all) (default: open)
     *        ['lenient']                  = (boolean) Specify whether format-based query failures (such as providing
     * text to a numeric field) should be ignored
     *        ['lowercase_expanded_terms'] = (boolean) Specify whether query terms should be lowercased
     *        ['preference']               = (string) Specify the node or shard the operation should be performed on
     * (default: random)
     *        ['q']                        = (string) Query in the Lucene query string syntax
     *        ['routing']                  = (list) A comma-separated list of specific routing values
     *        ['scroll']                   = (duration) Specify how long a consistent view of the index should be
     * maintained for scrolled search
     *        ['search_type']              = (enum) Search operation type (query_then_fetch,dfs_query_then_fetch)
     *        ['search_timeout']           = (time) Explicit timeout for each search request. Defaults to no timeout.
     *        ['size']                     = (number) Number of hits to return (default: 10)
     *        ['sort']                     = (list) A comma-separated list of <field>:<direction> pairs
     *        ['_source']                  = (list) True or false to return the _source field or not, or a list of
     * fields to return
     *        ['_source_exclude']          = (list) A list of fields to exclude from the returned _source field
     *        ['_source_include']          = (list) A list of fields to extract and return from the _source field
     *        ['terminate_after']          = (number) The maximum number of documents to collect for each shard, upon
     * reaching which the query execution will terminate early.
     *        ['stats']                    = (list) Specific 'tag' of the request for logging and statistical purposes
     *        ['suggest_field']            = (string) Specify which field to use for suggestions
     *        ['suggest_mode']             = (enum) Specify suggest mode (missing,popular,always) (default: missing)
     *        ['suggest_size']             = (number) How many suggestions to return in response
     *        ['suggest_text']             = (text) The source text for which the suggestions should be returned
     *        ['timeout']                  = (time) Time each individual bulk request should wait for shards that are
     * unavailable. (default: 1m)
     *        ['track_scores']             = (boolean) Whether to calculate and return scores even if they are not used
     * for sorting
     *        ['version']                  = (boolean) Specify whether to return document version as part of a hit
     *        ['version_type']             = (boolean) Should the document increment the version number (internal) on
     * hit or not (reindex)
     *        ['request_cache']            = (boolean) Specify if request cache should be used for this request or not,
     * defaults to index level setting
     *        ['refresh']                  = (boolean) Should the effected indexes be refreshed?
     *        ['consistency']              = (enum) Explicit write consistency setting for the operation
     * (one,quorum,all)
     *        ['scroll_size']              = (integer) Size on the scroll request powering the update_by_query
     *        ['wait_for_completion']      = (boolean) Should the request should block until the reindex is complete.
     * (default: false)
     *        ['body']                     = The search definition using the Query DSL
     *
     * @param array $params
     *
     * @return array
     */
    public function updateByQuery($params = array())
    {
        $index = $this->extractArgument($params, 'index');

        $body = $this->extractArgument($params, 'body');

        $type = $this->extractArgument($params, 'type');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\UpdateByQuery $endpoint */
        $endpoint = $endpointBuilder('UpdateByQuery');
        $endpoint->setIndex($index)
            ->setType($type)
            ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['id']   = (string) The script ID (Required)
     *        ['lang'] = (string) The script language (Required)
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function getScript($params)
    {
        $id = $this->extractArgument($params, 'id');
        $lang = $this->extractArgument($params, 'lang');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Script\Get $endpoint */
        $endpoint = $endpointBuilder('Script\Get');
        $endpoint->setID($id)
                 ->setLang($lang);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['id']   = (string) The script ID (Required)
     *        ['lang'] = (string) The script language (Required)
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function deleteScript($params)
    {
        $id = $this->extractArgument($params, 'id');
        $lang = $this->extractArgument($params, 'lang');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Script\Delete $endpoint */
        $endpoint = $endpointBuilder('Script\Delete');
        $endpoint->setID($id)
                 ->setLang($lang);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['id']   = (string) The script ID (Required)
     *        ['lang'] = (string) The script language (Required)
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function putScript($params)
    {
        $id   = $this->extractArgument($params, 'id');
        $lang = $this->extractArgument($params, 'lang');
        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Script\Put $endpoint */
        $endpoint = $endpointBuilder('Script\Put');
        $endpoint->setID($id)
                 ->setLang($lang)
                 ->setBody($body);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['id']   = (string) The search template ID (Required)
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function getTemplate($params)
    {
        $id = $this->extractArgument($params, 'id');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Template\Get $endpoint */
        $endpoint = $endpointBuilder('Template\Get');
        $endpoint->setID($id);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['id']   = (string) The search template ID (Required)
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function deleteTemplate($params)
    {
        $id = $this->extractArgument($params, 'id');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Template\Delete $endpoint */
        $endpoint = $endpointBuilder('Template\Delete');
        $endpoint->setID($id);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['id']   = (string) The search template ID (Required)
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function putTemplate($params)
    {
        $id   = $this->extractArgument($params, 'id');
        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\Template\Put $endpoint */
        $endpoint = $endpointBuilder('Template\Put');
        $endpoint->setID($id)
            ->setBody($body)
            ->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']              = (list) A comma-separated list of indices to restrict the results
     *        ['fields']             = (list) A comma-separated list of fields for to get field statistics for (min value, max value, and more)
     *        ['level']              = (enum) Defines if field stats should be returned on a per index level or on a cluster wide level
     *        ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['allow_no_indices']   = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['expand_wildcards']   = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function fieldStats($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\FieldStats $endpoint */
        $endpoint = $endpointBuilder('FieldStats');
        $endpoint->setIndex($index)
            ->setBody($body)
            ->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['index']              = (list) A comma-separated list of indices to restrict the results
     *        ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['allow_no_indices']   = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['expand_wildcards']   = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function fieldCaps($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $body = $this->extractArgument($params, 'body');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\FieldCaps $endpoint */
        $endpoint = $endpointBuilder('FieldCaps');
        $endpoint->setIndex($index)
            ->setBody($body)
            ->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * $params['id']                 = (string) ID of the template to render
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function renderSearchTemplate($params = array())
    {
        $body = $this->extractArgument($params, 'body');
        $id   = $this->extractArgument($params, 'id');

        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;

        /** @var \Elasticsearch\Endpoints\RenderSearchTemplate $endpoint */
        $endpoint = $endpointBuilder('RenderSearchTemplate');
        $endpoint->setBody($body)
            ->setID($id);
        $endpoint->setParams($params);

        return $this->performRequest($endpoint);
    }

    /**
     * Operate on the Indices Namespace of commands
     *
     * @return IndicesNamespace
     */
    public function indices()
    {
        return $this->indices;
    }

    /**
     * Operate on the Cluster namespace of commands
     *
     * @return ClusterNamespace
     */
    public function cluster()
    {
        return $this->cluster;
    }

    /**
     * Operate on the Nodes namespace of commands
     *
     * @return NodesNamespace
     */
    public function nodes()
    {
        return $this->nodes;
    }

    /**
     * Operate on the Snapshot namespace of commands
     *
     * @return SnapshotNamespace
     */
    public function snapshot()
    {
        return $this->snapshot;
    }

    /**
     * Operate on the Cat namespace of commands
     *
     * @return CatNamespace
     */
    public function cat()
    {
        return $this->cat;
    }

    /**
     * Operate on the Ingest namespace of commands
     *
     * @return IngestNamespace
     */
    public function ingest()
    {
        return $this->ingest;
    }

    /**
     * Operate on the Tasks namespace of commands
     *
     * @return TasksNamespace
     */
    public function tasks()
    {
        return $this->tasks;
    }

    /**
     * Operate on the Remote namespace of commands
     *
     * @return RemoteNamespace
     */
    public function remote()
    {
        return $this->remote;
    }

    /**
     * Catchall for registered namespaces
     *
     * @param $name
     * @param $arguments
     * @return Object
     * @throws BadMethodCallException if the namespace cannot be found
     */
    public function __call($name, $arguments)
    {
        if (isset($this->registeredNamespaces[$name])) {
            return $this->registeredNamespaces[$name];
        }
        throw new BadMethodCallException("Namespace [$name] not found");
    }

    /**
     * @param array $params
     * @param string $arg
     *
     * @return null|mixed
     */
    public function extractArgument(&$params, $arg)
    {
        if (is_object($params) === true) {
            $params = (array) $params;
        }

        if (array_key_exists($arg, $params) === true) {
            $val = $params[$arg];
            unset($params[$arg]);

            return $val;
        } else {
            return null;
        }
    }

    private function verifyNotNullOrEmpty($name, $var)
    {
        if ($var === null) {
            throw new InvalidArgumentException("$name cannot be null.");
        }

        if (is_string($var)) {
            if (strlen($var) === 0) {
                throw new InvalidArgumentException("$name cannot be an empty string");
            }
        }

        if (is_array($var)) {
            if (strlen(implode("", $var)) === 0) {
                throw new InvalidArgumentException("$name cannot be an array of empty strings");
            }
        }
    }

    /**
     * @param $endpoint AbstractEndpoint
     * 
     * @throws \Exception
     * @return array
     */
    private function performRequest(AbstractEndpoint $endpoint)
    {
        $promise =  $this->transport->performRequest(
            $endpoint->getMethod(),
            $endpoint->getURI(),
            $endpoint->getParams(),
            $endpoint->getBody(),
            $endpoint->getOptions()
        );

        return $this->transport->resultOrFuture($promise, $endpoint->getOptions());
    }
}
<?php
/**
 * Created by PhpStorm.
 * User: rohithb
 * Date: 16/10/18
 * Time: 3:14 PM
 */

namespace linways\cli\utils;


class FileNameUtils
{
    /**
     * @var string
     */
    const MIGRATION_FILE_NAME_PATTERN = '/^\d+_([\w_]+).php$/i';

    public static function isCamelCase($string, $classFormat=true,  $strict=true) {

        // Check the first character first.
        if ($classFormat === false) {
            $legalFirstChar = '';

            if ($strict === false) {
                // Can either start with a lowercase letter,
                // or multiple uppercase
                // in a row, representing an acronym.
                $legalFirstChar .= '([A-Z]{2,}|[a-z])';
            } else {
                $legalFirstChar .= '[a-z]';
            }
        } else {
            $legalFirstChar = '[A-Z]';
        }

        if (preg_match("/^$legalFirstChar/", $string) === 0) {
            return false;
        }

        // Check that the name only contains legal characters.
        $legalChars = 'a-zA-Z0-9';
        if (preg_match("|[^$legalChars]|", substr($string, 1)) > 0) {
            return false;
        }

        if ($strict === true) {
            // Check that there are not two capital letters
            // next to each other.
            $length          = strlen($string);
            $lastCharWasCaps = $classFormat;

            for ($i = 1; $i < $length; $i++) {
                $ascii = ord($string{$i});
                if ($ascii >= 48 && $ascii <= 57) {
                    // The character is a number, so it cant be a capital.
                    $isCaps = false;
                } else {
                    if (strtoupper($string{$i}) === $string{$i}) {
                        $isCaps = true;
                    } else {
                        $isCaps = false;
                    }
                }

                if ($isCaps === true && $lastCharWasCaps === true) {
                    return false;
                }

                $lastCharWasCaps = $isCaps;
            }
        }//end if

        return true;

    }//end isCamelCaps()


    public static function camelCaseToUnderScore($input) {
        preg_match_all('!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!', $input, $matches);
        $ret = $matches[0];
        foreach ($ret as &$match) {
            $match = $match == strtoupper($match) ? strtolower($match) : lcfirst($match);
        }
        return implode('_', $ret);
    }

    /**
     * Get the version from the beginning of a file name.
     *
     * @param string $fileName File Name
     * @return string
     */
    public static function getVersionFromFileName($fileName)
    {
        $matches = [];
        preg_match('/^[0-9]+/', basename($fileName), $matches);

        return $matches[0];
    }

    /**
     * Turn file names like '12345678901234_something_toggle.php' into class
     * names like 'SomethingToggle'.
     *
     * @param string $fileName File Name
     * @return string
     */
    public static function mapFileNameToClassName($fileName)
    {
        $matches = [];
        if (preg_match(static::MIGRATION_FILE_NAME_PATTERN, $fileName, $matches)) {
            $fileName = $matches[1];
        }

        return str_replace(' ', '', ucwords(str_replace('_', ' ', $fileName)));
    }
}<?php

namespace linways\cli\utls;

use com\linways\nucleus\core\dto\College;
use Phinx\Console\PhinxApplication;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Command\Command;
use com\linways\nucleus\core\service\CollegeService;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\StreamOutput;

class MigrationUtils{

  /**
   * To ask the password for the database for migrations
   * @param  Command          $command [description]
   * @param  InputInterface  $input   [description]
   * @param  OutputInterface $output  [description]
   */
  public static function askDbPassword(Command $command, InputInterface $input, OutputInterface $output){
    $helper = $command->getHelper('question');
    $question = new Question('Database password: ');
    $question->setHidden(true);
    $question->setHiddenFallback(false);
    $pass = $helper->ask($input, $output, $question);
    return $pass;
  }

  /**
   * returns an array of database details for running migrations migrations
   * @param  string $collegeCode
   * @param  boolean $migrateAll  true if migrating all databases
   * @param  object $db
   * @return College[]
   */
  public static function getDbDetailsForMigration($collegeCode,$migrateAll, $db ){
    $dbsToMigrate = [];
    if(!empty($collegeCode)){
      //get the details about college
      $singleDb = CollegeService::getInstance()->getCollegeByCollegeCode($collegeCode);
      $dbsToMigrate[] = $singleDb;
    }else if (!empty($migrateAll)){
      //get all college info
      $dbsToMigrate = CollegeService::getInstance()->getAllColleges();
    }else if (!empty($db->name)){
      //migrate for a single database
      $singleDb = new College();
      $singleDb->collegeDb = $db->name;
      $singleDb->dbHost = $db->host;
      $singleDb->dbUsername = $db->username;
      $singleDb->dbPassword = $db->password;
      $dbsToMigrate[] = $singleDb;
    }
    return $dbsToMigrate;
  }

  /**
   * To execue a phinx command
   * @param  array            $command [description]
   * @param  PhinxApplication $app     [description]
   * @return [type]                    [description]
   */
  public static function executeRun(array $command, PhinxApplication $app)
  {
      // Output will be written to a temporary stream, so that it can be
      // collected after running the command.
      $stream = fopen('php://temp', 'w+');
      // Execute the command, capturing the output in the temporary stream
      // and storing the exit code for debugging purposes.
      $exit_code = $app->doRun(new ArrayInput($command), new StreamOutput($stream));
      // Get the output of the command and close the stream, which will
      // destroy the temporary file.
      $result = stream_get_contents($stream, -1, 0);
      fclose($stream);
      return $result;
  }
}
<?php
/**
 * Created by PhpStorm.
 * User: rohithb
 * Date: 16/10/18
 * Time: 2:30 PM
 */

namespace linways\cli\commands\toggles;

use com\linways\base\util\StringUtil;
use linways\cli\service\ToggleManagerService;
use linways\cli\utils\FileNameUtils;
use Phar;
use Exception;
use linways\cli\utls\MigrationUtils;
use Phinx\Console\PhinxApplication;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;

class CreatePermissionToggleCommand extends Command{

    protected function configure(){
        $this->setName("toggles:create-permission")
            ->setDescription("To create a new permission toggle JSON.")
            ->setHelp("
Examples:
`<fg=blue;options=bold>toggles:create-permission NameOfPermissionToggleInCamelCase</>` For creating a new migration file
             ")
            ->addArgument('toggle_name', InputArgument::REQUIRED, 'Name of the permission toggle in camel case.');
    }

    protected function execute(InputInterface $input, OutputInterface $output){
        $toggleManagerService = new ToggleManagerService();
        $toggleName = $input->getArgument('toggle_name');
        if(!FileNameUtils::isCamelCase($toggleName)){
            $output->writeln("<error>The permission toggle name name \"$toggleName\" is invalid. Please use CamelCase format. </error>");
            exit(0);
        }
        $toggleFileName =  date("YmdHis").'_'. FileNameUtils::camelCaseToUnderScore($toggleName) . '.php';
        if(Phar::running(true)){
            $templatePath = Phar::running(true) ."/Toggle.php.template";
        }
        else{
            $templatePath = "./Toggle.php.template";
        }
        $directory = $toggleManagerService->getToggleDirectory();
        if (!file_exists($directory)) {
            mkdir( $directory, 0777, true);
        }
        $fullFilePath = $directory.'/'.$toggleFileName;
        if(file_exists($fullFilePath)){
            $output->writeln("<error>$toggleFileName Already Exists. </error>");
            exit(0);
        }
        $template = file_get_contents($templatePath);
        $params = [
            "name" => preg_replace( '/([a-z0-9])([A-Z])/', "$1 $2", $toggleName ),
            "code" => strtoupper(FileNameUtils::camelCaseToUnderScore($toggleName)),
            "useAbstractToggle" => "\\com\\linways\\base\\toggles\\AbstractToggleMigration",
            "useToggleDTO" => "\\com\\linways\\base\\dto\\Toggle",
            "ToggleClass" => "Toggle",
            "className" => "$toggleName",
            "baseClassName" => "AbstractToggleMigration"
        ];
        file_put_contents($fullFilePath, StringUtil::replace_tags($template, $params) );
        $output->writeln("$toggleFileName Created Successfully");
    }
}
<?php
/**
 * Created by PhpStorm.
 * User: rohithb
 * Date: 16/10/18
 * Time: 2:30 PM
 */

namespace linways\cli\commands\toggles;

use com\linways\base\util\StringUtil;
use com\linways\nucleus\core\service\PermissionToggleService;
use linways\cli\service\ToggleManagerService;
use linways\cli\utils\FileNameUtils;
use Phar;
use Exception;
use linways\cli\utls\MigrationUtils;
use Phinx\Console\PhinxApplication;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;

class MigrateTogglesCommand extends Command{

    protected function configure(){
        $this->setName("toggles:migrate")
            ->setDescription("To add non-migrated toggles in database.")
            ->setHelp("
Examples:
`<fg=blue;options=bold>toggles:migrate </>` To add non-migrated toggles in database
             ");
    }

    protected function execute(InputInterface $input, OutputInterface $output){
        /**
         * 1. Get all toggle objects to be executed (Excluding already migrated toggles)
         * 2. Sort the toggles based on the timestamp/version
         * 3. For each toggle migrate
         * 4. created entry for $version in permission_toggle_migrations
         */
        $toggleManagerService = new ToggleManagerService();
        $toggleMigrations = $toggleManagerService->getMigrations();
        $alreadyMigratedVersions = PermissionToggleService::getInstance()->getAllmigratedToggleVersions();
        if(empty(array_diff(array_keys($toggleMigrations), $alreadyMigratedVersions))){
            $output->writeln("[Toggles] Nothing to migrate ðŸ˜Š!");
            return;
        }
        foreach ($toggleMigrations as $version => $toggleMigration) {
            if (!in_array($version, $alreadyMigratedVersions)) {
                try{
                    $toggles = $toggleMigration->getToggles();
                    foreach ($toggles as $toggle){
                        $response = $toggleManagerService->migrateSingleToggle($version, $toggle);
                        $output->writeln($response);
                    }
                    PermissionToggleService::getInstance()->insertPermissionToggleMigration($version, get_class($toggleMigration));
                }catch (Exception $e){
                    $output->writeln($e->getMessage());
                    $output->writeln("<error>Reverting toggle migration: $version</error>");
                    $toggleManagerService->rollBackToggle($version, $toggleMigration->getToggles());
                    exit(0);
                }
            }
        }
    }
}
<?php

namespace linways\cli\command\db;

use linways\cli\utls\MigrationUtils;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;

use linways\cli\service\db\MigrateService;

class MigrateCommand extends Command{

    protected function configure(){
        $this->setName("db:migrate")
             ->setDescription("To run migrations against required databases.")
             ->setHelp("
Examples:
`<fg=blue;options=bold>db:migrate PRO</>` For running migrations for a college with code PRO
`<fg=blue;options=bold>db:migrate --all</>` For running migrations on all colleges using nucleus
`<fg=blue;options=bold>db:migrate --db=pro_db -u db_username -p</>` For running migrations on a specific database
`<fg=blue;options=bold>db:migrate --all --target=20171018082229</>` For migrating till a particular migration.
             ")
             ->addArgument('college_code', InputArgument::OPTIONAL, 'College code for migration. Works only with nucleus.')
             ->addOption('all','a', InputOption::VALUE_NONE, 'For executing migrations against all colleges in nucleus.')
             ->addOption('db', 'd', InputOption::VALUE_REQUIRED, 'for executing migrations against a single db (Nucleus not required)')
             ->addOption('target', 't', InputOption::VALUE_REQUIRED, 'If you want to run upto a particular migration. Target version is the time stamp in the migration file name (first part of the name)')
             ->addOption('host', null, InputOption::VALUE_REQUIRED, 'Database host name/ip.', 'localhost')
             ->addOption('user', 'u', InputOption::VALUE_REQUIRED, 'Database user name.', 'root')
             ->addOption('pass', 'p', InputOption::VALUE_NONE, 'Prompt for database password. <comment>[default: "root"]</comment>');
    }

    protected function execute(InputInterface $input, OutputInterface $output){
        $collegeCode = $input->getArgument('college_code');
        $migrateAll = $input->getOption('all');
        $target = $input->getOption('target');
        $db = new \stdClass();
        $db->name = $input->getOption('db');
        $db->host = $input->getOption('host');
        $db->username = $input->getOption('user');
        $db->password = $input->getOption('pass');
        if(empty($collegeCode) && empty($migrateAll) && empty($db->name)){
          $output->writeln("Run `db:migrate --help` for usage");
          exit(1);
        }
        if(!$db->password )
          $db->password  = 'root'; //for setting the default password
        else
          $db->password = MigrationUtils::askDbPassword($this, $input, $output);
        $dbsToMigrate = MigrationUtils::getDbDetailsForMigration($collegeCode, $migrateAll, $db);
        try{
          foreach ($dbsToMigrate as $db) {
            $output->writeln('<question>============= Migrating :<options=bold;fg=black;bg=yellow>'.$db->collegeDb .' ['. $db->code.']</>==========</question>');
            $response = MigrateService::migrateDb($db->collegeDb, $target, $db->dbHost, $db->dbUsername, $db->dbPassword);
            print_r($response);
            $output->writeln('<options=bold;fg=black;bg=green>âœ“ DONE</>');
          }
        }catch(\Exception $e){
          $output->writeln('<error>'. $e->getMessage(). '</error>');
        }
    }
}
<?php

namespace linways\cli\command\db;

use linways\cli\service\db\MigrateFakeService;
use linways\cli\utls\MigrationUtils;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;

class MigrateFakeCommand extends Command{

    protected function configure(){
        $this->setName("db:migrate-fake")
             ->setDescription("To fake a migration (mark migration as done)")
             ->setHelp("
Examples:
`<fg=blue;options=bold>db:migrate-fake 20171018082229_scheduler_migrations PRO </>` For faking 'scheduler_migrations' for a college with code PRO
`<fg=blue;options=bold>db:migrate-fake 20171018082229_scheduler_migrations --all </>` For faking 'scheduler_migrations' on all colleges using nucleus
`<fg=blue;options=bold>db:migrate-fake 20171018082229_scheduler_migrations --db=pro_db -u db_username -p </>` For faking 'scheduler_migrations' on a specific database
`<fg=blue;options=bold>db:migrate-fake 20171018082229_scheduler_migrations PRO --revert</>` For removing the particular migration from migration table. Effectively setting this migration as 'not migrated'
             ")
             ->addArgument('migration_file_name', InputArgument::REQUIRED, 'File name of the migration to be faked. <fg=red;>eg: 20171018082229_scheduler_migrations</>')
             ->addArgument('college_code', InputArgument::OPTIONAL, 'College code for faking the migration. Works only with nucleus.')
             ->addOption('all','a', InputOption::VALUE_NONE, 'For faking migrations against all colleges in nucleus.')
             ->addOption('db', 'd', InputOption::VALUE_REQUIRED, 'for faking migrations against a single db (Nucleus not required)')
             ->addOption('revert','r', InputOption::VALUE_NONE, 'For setting a migrations as NOT DONE. NB: This will not revert the actual database changes.')
             ->addOption('host', null, InputOption::VALUE_REQUIRED, 'Database host name/ip.', 'localhost')
             ->addOption('user', 'u', InputOption::VALUE_REQUIRED, 'Database user name.', 'root')
             ->addOption('pass', 'p', InputOption::VALUE_NONE, 'promt for database password. <comment>[default: "root"]</comment>');
    }

    protected function execute(InputInterface $input, OutputInterface $output){
        $migrationFile = $input->getArgument('migration_file_name');
        $collegeCode = $input->getArgument('college_code');
        $migrateAll = $input->getOption('all');
        $revert = $input->getOption('revert');
        $db = new \stdClass();
        $db->name = $input->getOption('db');
        $db->host = $input->getOption('host');
        $db->username = $input->getOption('user');
        $db->password = $input->getOption('pass');

        if(empty($collegeCode) && empty($migrateAll) && empty($db->name)){
          $output->writeln("Run `db:migrate-fake --help` for usage");
          exit(1);
        }
        if(!$db->password)
          $db->password = 'root'; //for setting the default password
        else
          $db->password = MigrationUtils::askDbPassword($this, $input, $output);
        $dbsToMigrate = MigrationUtils::getDbDetailsForMigration($collegeCode, $migrateAll, $db);
        list($version, $migarationName) = explode ('_', $migrationFile, 2);
        $migarationName .= '-faked';
        try{
          foreach ($dbsToMigrate as $db) {
            if($revert){
              $response = MigrateFakeService::fakeRevert($version, $migarationName, $db);
              $output->writeln('<question>============= Reverting Migration(FAKE) :<options=bold;fg=black;bg=yellow>'.$db->collegeDb .' ['. $db->code.']['.$migrationFile.']</>==========</question>');
              $output->writeln('Note that this will not revert the actual database changes caused by this migration.');
            }
            else{
              $output->writeln('<question>============= Migrating(FAKE) :<options=bold;fg=black;bg=yellow>'.$db->collegeDb .' ['. $db->code.']['.$migrationFile.']</>==========</question>');
              $response = MigrateFakeService::fakeMigration($version, $migarationName, $db);
            }
            if($response == false)
              $output->writeln("<comment>$version Already migrated</comment>");
            $output->writeln('<options=bold;fg=black;bg=green>âœ“ DONE</>');
          }
        }catch(\Exception $e){
          $output->writeln('<error>'. $e->getMessage(). '</error>');
        }
    }
}
<?php

namespace linways\cli\command\db;

use Phar;
use Exception;
use linways\cli\utls\MigrationUtils;
use Phinx\Console\PhinxApplication;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;

class CreateMigrationCommand extends Command{

    protected function configure(){
        $this->setName("db:create-migration")
             ->setDescription("To create a new migration file.")
             ->setHelp("
Examples:
`<fg=blue;options=bold>db:create-migration NameForMigrationInCamelCase</>` For creating a new migration file
             ")
             ->addArgument('migration_name', InputArgument::REQUIRED, 'Name of the migration in camel case.');
    }

    protected function execute(InputInterface $input, OutputInterface $output){
        $collegeCode = $input->getArgument('migration_name');
        $app = new PhinxApplication();
        $command = ['create'];
        $command += ["name" => $collegeCode];
        if(Phar::running(true))
          $command += ["--template" => Phar::running(true) ."/MigrationCustom.template.php.dist"];
        else
        $command += ["--template" => "./MigrationCustom.template.php.dist"];
        try{
          $response = MigrationUtils::executeRun($command, $app);
          print_r($response);
          $output->writeln('<options=bold;fg=black;bg=green>âœ“ DONE</>');
        }catch(Exception $e){
          $output->writeln('<error>'. $e->getMessage(). '</error>');
          exit(1);
        }
    }
}
<?php

namespace linways\cli\command\git;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;

class UpdateStyleGuideCommand extends Command{

    protected function configure(){
        $this->setName("git:update-styleguide")
             ->setDescription("To fetch the new version of the style guide from gitlab")
             ->setHelp("
Examples:
`<fg=blue;options=bold>git:update-styleguide</>` For updating the style guide 
`<fg=blue;options=bold>git:update-styleguide -p public/libcommon/styleguide</>` Update the styleguide which is at different location
             ")
             ->addOption('path', 'p', InputOption::VALUE_REQUIRED, 'Path to which the styleguide repo to be pulled', 'libcommon/styleguide')
             ->addOption('repo', 'r', InputOption::VALUE_REQUIRED, 'SSH repository url of style guide', 'git@gitlab.com:linways/styleguide.git')
             ->addOption('branch', 'b', InputOption::VALUE_REQUIRED, 'Branch name to pull', 'master');
    }

    protected function execute(InputInterface $input, OutputInterface $output){
        $path = $input->getOption('path');
        $repo = $input->getOption('repo');
        $branch = $input->getOption('branch');
        $isDirty = !empty(exec("git status --porcelain"));
        if($isDirty){
            $output->writeln('<error> Git is dirty. Please commit all changes for pulling the styleguide</error>');
            exit(1);
        }
        $command = "git subtree pull --prefix " . $path . " " . $repo . " " . $branch . " --squash";
        $output->writeln("Executing " . $command );
        echo exec($command);
    }
}
<?php
namespace linways\cli\service;


use com\linways\base\toggles\AbstractToggleMigration;
use com\linways\nucleus\core\exception\NucleusException;
use com\linways\nucleus\core\service\CollegeService;
use com\linways\nucleus\core\service\PermissionToggleService;
use com\linways\nucleus\core\service\ProductVariantService;
use linways\cli\utils\FileNameUtils;

class ToggleManagerService
{

    /**
     * @param string $version  version of the toggleMigration
     * @param \com\linways\base\dto\Toggle $toggle
     * @return string response message
     * @throws NucleusException
     */
    public function migrateSingleToggle($version, $toggle){
        /**
         * 1 Create the toggle record in nucleus.permission_toggle
         * 2 Create enabled entry for colleges
         * 3 Create disabled entry for colleges
         * 4 Prepare an array of enabled product variant codes from enabledProductVariants and disabledProductVariants
         * 5 Add entry for this toggle against enabled product variants.
         */
        $response = "";
        //1
        $toggle = PermissionToggleService::getInstance()->createPermissionToggle($toggle);
        $response .= "\nâœ… Permission toggle created: $toggle->code => $toggle->description";
        // TODO: 2nd and 3rd steps.
        // Need to find away to execute tenant level service from nucleus

        // 4
        $enabledVariants = $this->getEnabledProductVariants($toggle->enabledProductVariants, $toggle->disabledProductVariants);
        //5
        foreach ($enabledVariants as $enVariant){
            try{
                PermissionToggleService::getInstance()->enableToggleForProductVariant($toggle->code, $enVariant);
                $response .= "\nEnabled Toggle $toggle->code for $enVariant âœ…";
            }catch (\Exception $e){
                $response .= "\nâ�Œ <error>Failed to enable $toggle->code for $enVariant</error>\n";
                $response .= $e->getMessage();
                throw new NucleusException(NucleusException::FAILED_TO_MIGRATE_TOGGLE, $response);
            }
        }
        $response .= "\n\n<options=bold>ðŸ‘�ðŸ�»  $toggle->code Completed</>";
        return $response;
    }

    public function rollBackToggle($version, $toggle){
        //TODO: implement
    }


    private function setPermissionToggleValueForTenant($collegeCode, $toggleCode, $isEnabled){
        $tenant = CollegeService::getInstance()->getCollegeByCollegeCode($collegeCode);
//        putenv('')
    }

    /**
     * Returns enabled and disabled array of product variants.
     * @param array $enabledRaw enabledProductVariant array from the Toggle file
     * @param array $disabledRaw disabledProductVariant array from the Toggle file
     * @return array of enabled product variants
     * @throws NucleusException
     */
    public function getEnabledProductVariants($enabledRaw, $disabledRaw){
        $enabled = [];
        $allActiveProductVariant = ProductVariantService::getInstance()->getAllActiveProductVariant();
        //Creating an array of product variant codes for easy manipulation
        $allActiveProductVariant = array_map(function($productVariant){
            return $productVariant->code;
        }, $allActiveProductVariant);

        if(in_array("ALL", $enabledRaw) && in_array("ALL", $disabledRaw)){
            throw new NucleusException(NucleusException::CANNOT_ADD_ALL_FOR_BOTH_ENABLED_AND_DISABLED_PRODUCT_VARIANTS,
                "Cannot add \'ALL\' for both enabled and disabled product variants");
        }
        // Preparing Enabled product variant
        if(in_array("ALL", $enabledRaw)){
            if(count($enabledRaw) !== 1){
                throw new NucleusException(NucleusException::CANNOT_USE_ALL_ALONG_WITH_OTHER_PRODUCT_VARIANTS,
                    "Cannot use \'ALL\' along with other product variants in enabledProductVariants");
            }
            $enabled = array_diff($allActiveProductVariant, $disabledRaw);
        }
        else{
            //To throw error if invalid product variants are present
            if(!empty($undefined = array_diff($enabledRaw, $allActiveProductVariant))){
                throw new NucleusException(NucleusException::INVALID_PRODUCT_VARIANT,
                    "Invalid product variants: json_encode($undefined)");
            }
            $enabled = $enabledRaw;
        }
        return $enabled;
    }

    /**
     * Gets an array of the toggle migrations, indexed by creation time and sorted in ascending
     * order
     *
     * @throws \InvalidArgumentException
     * @return \com\linways\base\toggles\AbstractToggleMigration[]
     */
    public function getMigrations(){
        $phpFiles = $this->getMigrationFiles();

        $fileNames = [];
        /** @var \com\linways\base\toggles\AbstractToggleMigration[] $versions */
        $versions = [];
        foreach ($phpFiles as $filePath) {
                $version = FileNameUtils::getVersionFromFileName(basename($filePath));

                if (isset($versions[$version])) {
                    throw new \InvalidArgumentException(sprintf('Duplicate migration - "%s"', $filePath));
                }

                // convert the filename to a class name
                $class =  FileNameUtils::mapFileNameToClassName(basename($filePath));

                if (isset($fileNames[$class])) {
                    throw new \InvalidArgumentException(sprintf(
                        'Toggle: "%s" is defined on "%s" and "%s". Toggle Migrator class name should be unique',
                        $class,
                        basename($filePath),
                        $fileNames[$class]
                    ));
                }
                $fileNames[$class] = basename($filePath);

                // load the migration file
                /** @noinspection PhpIncludeInspection */
                require_once $filePath;
                if (!class_exists($class)) {
                    throw new \InvalidArgumentException(sprintf(
                        'Could not find class "%s" in file "%s"',
                        $class,
                        $filePath
                    ));
                }
                // instantiate it
                $toggle = new $class();

                if (!($toggle instanceof AbstractToggleMigration)) {
                    throw new \InvalidArgumentException(sprintf(
                        'The class "%s" in file "%s" must extend \com\linways\base\toggles\AbstractToggle',
                        $class,
                        $filePath
                    ));
                }
                $versions[$version] = $toggle;
        }
        ksort($versions);
        return $versions;
    }
    /**
     * Returns a list of toggles files found in the provided toggle paths.
     * @return string[]
     */
    public function getMigrationFiles(){
        $path = $this->getToggleDirectory();
        $files = glob($path . '/*.php');

        // glob() can return the same file multiple times
        // This will cause the migration to fail
        // http://php.net/manual/en/function.glob.php#110340
        $files = array_unique($files);
        $files = array_map("realpath", $files);
        return $files;
    }

    /**
     * Returns the directory where the toggles are defined
     * @return string
     */
    public function getToggleDirectory(){
        $dir = getenv('TOGGLE_DIRECTORY')? getenv('TOGGLE_DIRECTORY'): 'toggles';
        return "./". $dir;
    }
}<?php
namespace linways\cli\service\db;
use Exception;
use Symfony\Component\Yaml\Yaml;

class MigrateFakeService{
  /**
   * creates an entry in migration table with $version and $name
   * @param  string $version       version of the migration (timestamp prefix of the migration file)
   * @param  string $migrationName name of the migration
   * @param  array  $dbDetails     database connection details
   * @return boolean
   */
  public static function fakeMigration($version, $migrationName, $dbDetails){
    $db = new \mysqli($dbDetails->dbHost, $dbDetails->dbUsername, $dbDetails->dbPassword, $dbDetails->collegeDb);
    if($db->connect_errno > 0){
        $e = new \Exception('Unable to connect to database [' . $db->connect_error . ']');
        throw $e;
        exit(0);
    }

    // read migration table name from .yml configuration
    if(empty(getenv('PHINX_CONF')))
      throw new \Exception("Environment variable `PHINX_CONF` not found. define it in `.cli.env` file on project root.");

    $phixConf = Yaml::parseFile(getenv('PHINX_CONF'));
    $migrationTableName = $phixConf ["environments"]["default_migration_table"];
    $createTable = "CREATE TABLE IF NOT EXISTS `$migrationTableName` (
    `version` bigint(20) NOT NULL,
    `migration_name` varchar(100) DEFAULT NULL,
    `start_time` timestamp NULL DEFAULT NULL,
    `end_time` timestamp NULL DEFAULT NULL,
    `breakpoint` tinyint(1) NOT NULL DEFAULT '0',
    PRIMARY KEY (`version`))";
    $db->query($createTable);

    $sqlCheckMigrated = "SELECT * from `$migrationTableName` where version='$version'";
    $result = $db->query($sqlCheckMigrated);
    if($result->num_rows > 0){
      return false;
    }else{
      $sql = "INSERT INTO `$migrationTableName`
      (`version`, `migration_name`, `start_time`, `end_time`)
      VALUES ('$version', '$migrationName', NOW(), NOW())";
      if(!$result = $db->query($sql)){
        $e = new \Exception("There was an error running the query [$db->error]\n");
        throw $e;
        exit(0);
      }
      else
        return true;
    }
  }

  /**
   * Removes the entry from migration table.
   * @param  string $version        version of the migration (timestamp prefix of the migration file)
   * @param  string $migrationName  name of the migration
   * @param  array  $dbDetails      database connection details
   * @return true|Exception
   */
  public static function fakeRevert($version, $migrationName, $dbDetails){
    $db = new \mysqli($dbDetails->dbHost, $dbDetails->dbUsername, $dbDetails->dbPassword, $dbDetails->collegeDb);
    if($db->connect_errno > 0){
        $e = new \Exception('Unable to connect to database [' . $db->connect_error . ']');
        throw $e;
        exit(0);
    }

    // read migration table name from .yml configuration
    if(empty(getenv('PHINX_CONF')))
      throw new \Exception("Environment variable `PHINX_CONF` not found. define it in `.cli.env` file on project root.");

    $phixConf = Yaml::parseFile(getenv('PHINX_CONF'));
    $migrationTableName = $phixConf ["environments"]["default_migration_table"];
    $sql = "delete from `$migrationTableName` where version='$version'";
    if(!$result = $db->query($sql)){
      throw new \Exception("There was an error running the query [$db->error]\n");
      exit(0);
    }
    else
      return true;
  }
}
<?php
namespace linways\cli\service\db;
use Phinx\Console\PhinxApplication;

class MigrateService{
  /**
   * migrate a single database using username and password
   * @param  string $dbName   [description]
   * @param  string $target   target migration version
   * @param  string $host     [description]
   * @param  string $username [description]
   * @param  string $password [description]
   * @return [type]           [description]
   */
  public static function migrateDb($dbName, $target, $host, $username, $password){
    $app = new PhinxApplication();
    $_SERVER['PHINX_DBNAME'] = $dbName;
    $_SERVER['PHINX_DBHOST'] = $host;
    $_SERVER['PHINX_DBUSER'] = $username;
    $_SERVER['PHINX_DBPASS'] = $password;
    $_SERVER['PHINX_CONFIG_DIR'] = dirname (getenv('PHINX_CONF'));
    if(empty($_SERVER['PHINX_CONFIG_DIR']))
      throw new \Exception("Environment variable `PHINX_CONF` not found. define it in `.cli.env` file on project root.");

    $wrap = new \Phinx\Wrapper\TextWrapper($app, array(
                // 'config_path' => __DIR__. '/../../../phinx.yml',
                'parser' => 'yaml'
    ));
    try{
      if($target)
        $response = $wrap->getMigrate(null, $target);
      else
        $response = $wrap->getMigrate();
    }catch(\Exception $e){
      throw $e;
      exit(1);
    }
    unset($wrap);
    unset($app);
    return $response;
  }
}
<?php

require_once __DIR__ . '/vendor/autoload.php';

use Symfony\Component\Console\Application;
use linways\cli\command\db\MigrateCommand;
use linways\cli\command\db\CreateMigrationCommand;
use linways\cli\command\db\MigrateFakeCommand;
use linways\cli\command\git\UpdateStyleGuideCommand;
use Symfony\Component\Dotenv\Dotenv;
use linways\cli\commands\toggles\CreatePermissionToggleCommand;
use linways\cli\commands\toggles\MigrateTogglesCommand;

error_reporting(E_ALL ^ E_NOTICE);  

$app = new Application();
$dotenv = new Dotenv();
$dotenv->load('.cli.env');
$app->add(new MigrateCommand());
$app->add(new MigrateFakeCommand());
$app->add(new CreateMigrationCommand());
$app->add(new UpdateStyleGuideCommand());
$app->add(new CreatePermissionToggleCommand());
$app->add(new MigrateTogglesCommand());
$app->run();
<?php
$namespaceDefinition

use $useClassName;

class $className extends $baseClassName
{
    /**
     * Change Method.
     *
     * Write your reversible migrations using this method.
     *
     * More information on writing migrations is available here:
     * http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
     *
     * The following commands can be used in this method and Phinx will
     * automatically reverse them when rolling back:
     *
     *    createTable
     *    renameTable
     *    addColumn
     *    renameColumn
     *    addIndex
     *    addForeignKey
     *
     * Remember to call "create()" or "update()" and NOT "save()" when working
     * with the Table class.
     */
    public function change()
    {
      // write your SQL inside the double quotes
      $this->execute("

      ");
    }
}
<?php

use {{useAbstractToggle}};
use {{useToggleDTO}};

class {{className}} extends {{baseClassName}}
{

    public function setToggles(){
        $toggle = new {{ToggleClass}}();
        $toggle->name = "{{name}}";
        $toggle->code = "{{code}}";
        $toggle->description = "";
        $toggle->parentToggleCode = "";
        $toggle->enabledProductVariants = [];
        $toggle->disabledProductVariants = [];
        // enabledCollegeCodes and disabledCollegeCodes not implemented yet
        // $toggle->enabledCollegeCodes = [];
        // $toggle->disabledCollegeCodes = [];

        // If you want to create multiple toggles create instances of {{useToggleDTO}}
        // and return an array of toggles eg: [$toggle1, $toggle2, ... ]
        return [$toggle];
    }

}



 ,#8xêö§ÛEÓã°ç"	%i¦û   GBMB